1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/mobile/android/base/tests/BaseTest.java Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,948 @@ 1.4 +/* This Source Code Form is subject to the terms of the Mozilla Public 1.5 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.6 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.7 + 1.8 +package org.mozilla.gecko.tests; 1.9 + 1.10 +import java.io.File; 1.11 +import java.io.IOException; 1.12 +import java.io.InputStream; 1.13 +import java.io.PrintWriter; 1.14 +import java.io.StringWriter; 1.15 +import java.util.ArrayList; 1.16 + 1.17 +import org.json.JSONArray; 1.18 +import org.json.JSONException; 1.19 +import org.json.JSONObject; 1.20 +import org.mozilla.gecko.Actions; 1.21 +import org.mozilla.gecko.Driver; 1.22 +import org.mozilla.gecko.Element; 1.23 +import org.mozilla.gecko.FennecNativeActions; 1.24 +import org.mozilla.gecko.FennecNativeDriver; 1.25 +import org.mozilla.gecko.GeckoAppShell; 1.26 +import org.mozilla.gecko.GeckoEvent; 1.27 +import org.mozilla.gecko.GeckoThread; 1.28 +import org.mozilla.gecko.GeckoThread.LaunchState; 1.29 +import org.mozilla.gecko.R; 1.30 +import org.mozilla.gecko.RobocopUtils; 1.31 +import org.mozilla.gecko.Tab; 1.32 +import org.mozilla.gecko.Tabs; 1.33 + 1.34 +import android.app.Activity; 1.35 +import android.content.ContentValues; 1.36 +import android.content.Intent; 1.37 +import android.content.pm.ActivityInfo; 1.38 +import android.content.res.AssetManager; 1.39 +import android.database.Cursor; 1.40 +import android.os.Build; 1.41 +import android.os.SystemClock; 1.42 +import android.support.v4.app.Fragment; 1.43 +import android.support.v4.app.FragmentActivity; 1.44 +import android.support.v4.app.FragmentManager; 1.45 +import android.text.TextUtils; 1.46 +import android.util.DisplayMetrics; 1.47 +import android.view.View; 1.48 +import android.view.inputmethod.InputMethodManager; 1.49 +import android.widget.AdapterView; 1.50 +import android.widget.Button; 1.51 +import android.widget.EditText; 1.52 +import android.widget.ListAdapter; 1.53 +import android.widget.TextView; 1.54 + 1.55 +import com.jayway.android.robotium.solo.Condition; 1.56 +import com.jayway.android.robotium.solo.Solo; 1.57 + 1.58 +/** 1.59 + * A convenient base class suitable for most Robocop tests. 1.60 + */ 1.61 +@SuppressWarnings("unchecked") 1.62 +abstract class BaseTest extends BaseRobocopTest { 1.63 + private static final int VERIFY_URL_TIMEOUT = 2000; 1.64 + private static final int MAX_WAIT_ENABLED_TEXT_MS = 10000; 1.65 + private static final int MAX_WAIT_HOME_PAGER_HIDDEN_MS = 15000; 1.66 + public static final int MAX_WAIT_MS = 4500; 1.67 + public static final int LONG_PRESS_TIME = 6000; 1.68 + private static final int GECKO_READY_WAIT_MS = 180000; 1.69 + public static final int MAX_WAIT_BLOCK_FOR_EVENT_DATA_MS = 90000; 1.70 + 1.71 + private Activity mActivity; 1.72 + private int mPreferenceRequestID = 0; 1.73 + protected Solo mSolo; 1.74 + protected Driver mDriver; 1.75 + protected Actions mActions; 1.76 + protected String mBaseUrl; 1.77 + protected String mRawBaseUrl; 1.78 + protected String mProfile; 1.79 + public Device mDevice; 1.80 + protected DatabaseHelper mDatabaseHelper; 1.81 + protected StringHelper mStringHelper; 1.82 + protected int mScreenMidWidth; 1.83 + protected int mScreenMidHeight; 1.84 + 1.85 + protected void blockForGeckoReady() { 1.86 + try { 1.87 + Actions.EventExpecter geckoReadyExpector = mActions.expectGeckoEvent("Gecko:Ready"); 1.88 + if (!GeckoThread.checkLaunchState(LaunchState.GeckoRunning)) { 1.89 + geckoReadyExpector.blockForEvent(GECKO_READY_WAIT_MS, true); 1.90 + } 1.91 + geckoReadyExpector.unregisterListener(); 1.92 + } catch (Exception e) { 1.93 + mAsserter.dumpLog("Exception in blockForGeckoReady", e); 1.94 + } 1.95 + } 1.96 + 1.97 + @Override 1.98 + public void setUp() throws Exception { 1.99 + super.setUp(); 1.100 + 1.101 + // Create the intent to be used with all the important arguments. 1.102 + mBaseUrl = ((String) mConfig.get("host")).replaceAll("(/$)", ""); 1.103 + mRawBaseUrl = ((String) mConfig.get("rawhost")).replaceAll("(/$)", ""); 1.104 + Intent i = new Intent(Intent.ACTION_MAIN); 1.105 + mProfile = (String) mConfig.get("profile"); 1.106 + i.putExtra("args", "-no-remote -profile " + mProfile); 1.107 + String envString = (String) mConfig.get("envvars"); 1.108 + if (envString != "") { 1.109 + String[] envStrings = envString.split(","); 1.110 + for (int iter = 0; iter < envStrings.length; iter++) { 1.111 + i.putExtra("env" + iter, envStrings[iter]); 1.112 + } 1.113 + } 1.114 + // Start the activity 1.115 + setActivityIntent(i); 1.116 + mActivity = getActivity(); 1.117 + // Set up Robotium.solo and Driver objects 1.118 + mSolo = new Solo(getInstrumentation(), mActivity); 1.119 + mDriver = new FennecNativeDriver(mActivity, mSolo, mRootPath); 1.120 + mActions = new FennecNativeActions(mActivity, mSolo, getInstrumentation(), mAsserter); 1.121 + mDevice = new Device(); 1.122 + mDatabaseHelper = new DatabaseHelper(mActivity, mAsserter); 1.123 + mStringHelper = new StringHelper(); 1.124 + } 1.125 + 1.126 + @Override 1.127 + protected void runTest() throws Throwable { 1.128 + try { 1.129 + super.runTest(); 1.130 + } catch (Throwable t) { 1.131 + // save screenshot -- written to /mnt/sdcard/Robotium-Screenshots 1.132 + // as <filename>.jpg 1.133 + mSolo.takeScreenshot("robocop-screenshot"); 1.134 + if (mAsserter != null) { 1.135 + mAsserter.dumpLog("Exception caught during test!", t); 1.136 + mAsserter.ok(false, "Exception caught", t.toString()); 1.137 + } 1.138 + // re-throw to continue bail-out 1.139 + throw t; 1.140 + } 1.141 + } 1.142 + 1.143 + @Override 1.144 + public void tearDown() throws Exception { 1.145 + try { 1.146 + mAsserter.endTest(); 1.147 + // request a force quit of the browser and wait for it to take effect 1.148 + GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Robocop:Quit", null)); 1.149 + mSolo.sleep(7000); 1.150 + // if still running, finish activities as recommended by Robotium 1.151 + mSolo.finishOpenedActivities(); 1.152 + } catch (Throwable e) { 1.153 + e.printStackTrace(); 1.154 + } 1.155 + super.tearDown(); 1.156 + } 1.157 + 1.158 + public void assertMatches(String value, String regex, String name) { 1.159 + if (value == null) { 1.160 + mAsserter.ok(false, name, "Expected /" + regex + "/, got null"); 1.161 + return; 1.162 + } 1.163 + mAsserter.ok(value.matches(regex), name, "Expected /" + regex +"/, got \"" + value + "\""); 1.164 + } 1.165 + 1.166 + /** 1.167 + * Click on the URL bar to focus it and enter editing mode. 1.168 + */ 1.169 + protected final void focusUrlBar() { 1.170 + // Click on the browser toolbar to enter editing mode 1.171 + final View toolbarView = mSolo.getView(R.id.browser_toolbar); 1.172 + mSolo.clickOnView(toolbarView); 1.173 + 1.174 + // Wait for highlighed text to gain focus 1.175 + boolean success = waitForCondition(new Condition() { 1.176 + @Override 1.177 + public boolean isSatisfied() { 1.178 + EditText urlEditText = (EditText) mSolo.getView(R.id.url_edit_text); 1.179 + if (urlEditText.isInputMethodTarget()) { 1.180 + return true; 1.181 + } 1.182 + return false; 1.183 + } 1.184 + }, MAX_WAIT_ENABLED_TEXT_MS); 1.185 + 1.186 + mAsserter.ok(success, "waiting for urlbar text to gain focus", "urlbar text gained focus"); 1.187 + } 1.188 + 1.189 + protected final void enterUrl(String url) { 1.190 + final EditText urlEditView = (EditText) mSolo.getView(R.id.url_edit_text); 1.191 + 1.192 + focusUrlBar(); 1.193 + 1.194 + // Send the keys for the URL we want to enter 1.195 + mSolo.clearEditText(urlEditView); 1.196 + mSolo.enterText(urlEditView, url); 1.197 + 1.198 + // Get the URL text from the URL bar EditText view 1.199 + final String urlBarText = urlEditView.getText().toString(); 1.200 + mAsserter.is(url, urlBarText, "URL typed properly"); 1.201 + } 1.202 + 1.203 + protected final Fragment getBrowserSearch() { 1.204 + final FragmentManager fm = ((FragmentActivity) getActivity()).getSupportFragmentManager(); 1.205 + return fm.findFragmentByTag("browser_search"); 1.206 + } 1.207 + 1.208 + protected final void hitEnterAndWait() { 1.209 + Actions.EventExpecter contentEventExpecter = mActions.expectGeckoEvent("DOMContentLoaded"); 1.210 + mActions.sendSpecialKey(Actions.SpecialKey.ENTER); 1.211 + // wait for screen to load 1.212 + contentEventExpecter.blockForEvent(); 1.213 + contentEventExpecter.unregisterListener(); 1.214 + } 1.215 + 1.216 + /** 1.217 + * Load <code>url</code> by sending key strokes to the URL bar UI. 1.218 + * 1.219 + * This method waits synchronously for the <code>DOMContentLoaded</code> 1.220 + * message from Gecko before returning. 1.221 + */ 1.222 + protected final void inputAndLoadUrl(String url) { 1.223 + enterUrl(url); 1.224 + hitEnterAndWait(); 1.225 + } 1.226 + 1.227 + /** 1.228 + * Load <code>url</code> using reflection and the internal 1.229 + * <code>org.mozilla.gecko.Tabs</code> API. 1.230 + * 1.231 + * This method does not wait for any confirmation from Gecko before 1.232 + * returning. 1.233 + */ 1.234 + protected final void loadUrl(final String url) { 1.235 + try { 1.236 + Tabs.getInstance().loadUrl(url); 1.237 + } catch (Exception e) { 1.238 + mAsserter.dumpLog("Exception in loadUrl", e); 1.239 + throw new RuntimeException(e); 1.240 + } 1.241 + } 1.242 + 1.243 + protected final void closeTab(int tabId) { 1.244 + Tabs tabs = Tabs.getInstance(); 1.245 + Tab tab = tabs.getTab(tabId); 1.246 + tabs.closeTab(tab); 1.247 + } 1.248 + 1.249 + public final void verifyUrl(String url) { 1.250 + final EditText urlEditText = (EditText) mSolo.getView(R.id.url_edit_text); 1.251 + String urlBarText = null; 1.252 + if (urlEditText != null) { 1.253 + // wait for a short time for the expected text, in case there is a delay 1.254 + // in updating the view 1.255 + waitForCondition(new VerifyTextViewText(urlEditText, url), VERIFY_URL_TIMEOUT); 1.256 + urlBarText = urlEditText.getText().toString(); 1.257 + 1.258 + } 1.259 + mAsserter.is(urlBarText, url, "Browser toolbar URL stayed the same"); 1.260 + } 1.261 + 1.262 + class VerifyTextViewText implements Condition { 1.263 + private TextView mTextView; 1.264 + private String mExpected; 1.265 + public VerifyTextViewText(TextView textView, String expected) { 1.266 + mTextView = textView; 1.267 + mExpected = expected; 1.268 + } 1.269 + 1.270 + @Override 1.271 + public boolean isSatisfied() { 1.272 + String textValue = mTextView.getText().toString(); 1.273 + return mExpected.equals(textValue); 1.274 + } 1.275 + } 1.276 + 1.277 + protected final String getAbsoluteUrl(String url) { 1.278 + return mBaseUrl + "/" + url.replaceAll("(^/)", ""); 1.279 + } 1.280 + 1.281 + protected final String getAbsoluteRawUrl(String url) { 1.282 + return mRawBaseUrl + "/" + url.replaceAll("(^/)", ""); 1.283 + } 1.284 + 1.285 + /* 1.286 + * Wrapper method for mSolo.waitForCondition with additional logging. 1.287 + */ 1.288 + protected final boolean waitForCondition(Condition condition, int timeout) { 1.289 + boolean result = mSolo.waitForCondition(condition, timeout); 1.290 + if (!result) { 1.291 + // Log timeout failure for diagnostic purposes only; a failed wait may 1.292 + // be normal and does not necessarily warrant a test asssertion/failure. 1.293 + mAsserter.dumpLog("waitForCondition timeout after " + timeout + " ms."); 1.294 + } 1.295 + return result; 1.296 + } 1.297 + 1.298 + // TODO: With Robotium 4.2, we should use Condition and waitForCondition instead. 1.299 + // Future boolean tests should not use this method. 1.300 + protected final boolean waitForTest(BooleanTest t, int timeout) { 1.301 + long end = SystemClock.uptimeMillis() + timeout; 1.302 + while (SystemClock.uptimeMillis() < end) { 1.303 + if (t.test()) { 1.304 + return true; 1.305 + } 1.306 + mSolo.sleep(100); 1.307 + } 1.308 + // log out wait failure for diagnostic purposes only; 1.309 + // a failed wait may be normal and does not necessarily 1.310 + // warrant a test assertion/failure 1.311 + mAsserter.dumpLog("waitForTest timeout after "+timeout+" ms"); 1.312 + return false; 1.313 + } 1.314 + 1.315 + // TODO: With Robotium 4.2, we should use Condition and waitForCondition instead. 1.316 + // Future boolean tests should not implement this interface. 1.317 + protected interface BooleanTest { 1.318 + public boolean test(); 1.319 + } 1.320 + 1.321 + public void SqliteCompare(String dbName, String sqlCommand, ContentValues[] cvs) { 1.322 + File profile = new File(mProfile); 1.323 + String dbPath = new File(profile, dbName).getPath(); 1.324 + 1.325 + Cursor c = mActions.querySql(dbPath, sqlCommand); 1.326 + SqliteCompare(c, cvs); 1.327 + } 1.328 + 1.329 + public void SqliteCompare(Cursor c, ContentValues[] cvs) { 1.330 + mAsserter.is(c.getCount(), cvs.length, "List is correct length"); 1.331 + if (c.moveToFirst()) { 1.332 + do { 1.333 + boolean found = false; 1.334 + for (int i = 0; !found && i < cvs.length; i++) { 1.335 + if (CursorMatches(c, cvs[i])) { 1.336 + found = true; 1.337 + } 1.338 + } 1.339 + mAsserter.is(found, true, "Password was found"); 1.340 + } while (c.moveToNext()); 1.341 + } 1.342 + } 1.343 + 1.344 + public boolean CursorMatches(Cursor c, ContentValues cv) { 1.345 + for (int i = 0; i < c.getColumnCount(); i++) { 1.346 + String column = c.getColumnName(i); 1.347 + if (cv.containsKey(column)) { 1.348 + mAsserter.info("Comparing", "Column values for: " + column); 1.349 + Object value = cv.get(column); 1.350 + if (value == null) { 1.351 + if (!c.isNull(i)) { 1.352 + return false; 1.353 + } 1.354 + } else { 1.355 + if (c.isNull(i) || !value.toString().equals(c.getString(i))) { 1.356 + return false; 1.357 + } 1.358 + } 1.359 + } 1.360 + } 1.361 + return true; 1.362 + } 1.363 + 1.364 + public InputStream getAsset(String filename) throws IOException { 1.365 + AssetManager assets = getInstrumentation().getContext().getAssets(); 1.366 + return assets.open(filename); 1.367 + } 1.368 + 1.369 + public boolean waitForText(String text) { 1.370 + boolean rc = mSolo.waitForText(text); 1.371 + if (!rc) { 1.372 + // log out failed wait for diagnostic purposes only; 1.373 + // waitForText failures are sometimes expected/normal 1.374 + mAsserter.dumpLog("waitForText timeout on "+text); 1.375 + } 1.376 + return rc; 1.377 + } 1.378 + 1.379 + // waitForText usually scrolls down in a view when text is not visible. 1.380 + // For PreferenceScreens and dialogs, Solo.waitForText scrolling does not 1.381 + // work, so we use this hack to do the same thing. 1.382 + protected boolean waitForPreferencesText(String txt) { 1.383 + boolean foundText = waitForText(txt); 1.384 + if (!foundText) { 1.385 + if ((mScreenMidWidth == 0) || (mScreenMidHeight == 0)) { 1.386 + mScreenMidWidth = mDriver.getGeckoWidth()/2; 1.387 + mScreenMidHeight = mDriver.getGeckoHeight()/2; 1.388 + } 1.389 + 1.390 + // If we don't see the item, scroll down once in case it's off-screen. 1.391 + // Hacky way to scroll down. solo.scroll* does not work in dialogs. 1.392 + MotionEventHelper meh = new MotionEventHelper(getInstrumentation(), mDriver.getGeckoLeft(), mDriver.getGeckoTop()); 1.393 + meh.dragSync(mScreenMidWidth, mScreenMidHeight+100, mScreenMidWidth, mScreenMidHeight-100); 1.394 + 1.395 + foundText = mSolo.waitForText(txt); 1.396 + } 1.397 + return foundText; 1.398 + } 1.399 + 1.400 + /** 1.401 + * Wait for <text> to be visible and also be enabled/clickable. 1.402 + */ 1.403 + public boolean waitForEnabledText(String text) { 1.404 + final String testText = text; 1.405 + boolean rc = waitForCondition(new Condition() { 1.406 + @Override 1.407 + public boolean isSatisfied() { 1.408 + // Solo.getText() could be used here, except that it sometimes 1.409 + // hits an assertion when the requested text is not found. 1.410 + ArrayList<View> views = mSolo.getCurrentViews(); 1.411 + for (View view : views) { 1.412 + if (view instanceof TextView) { 1.413 + TextView tv = (TextView)view; 1.414 + String viewText = tv.getText().toString(); 1.415 + if (tv.isEnabled() && viewText != null && viewText.matches(testText)) { 1.416 + return true; 1.417 + } 1.418 + } 1.419 + } 1.420 + return false; 1.421 + } 1.422 + }, MAX_WAIT_ENABLED_TEXT_MS); 1.423 + if (!rc) { 1.424 + // log out failed wait for diagnostic purposes only; 1.425 + // failures are sometimes expected/normal 1.426 + mAsserter.dumpLog("waitForEnabledText timeout on "+text); 1.427 + } 1.428 + return rc; 1.429 + } 1.430 + 1.431 + 1.432 + /** 1.433 + * Select <item> from Menu > "Settings" > <section>. 1.434 + */ 1.435 + public void selectSettingsItem(String section, String item) { 1.436 + String[] itemPath = { "Settings", section, item }; 1.437 + selectMenuItemByPath(itemPath); 1.438 + } 1.439 + 1.440 + /** 1.441 + * Traverses the items in listItems in order in the menu. 1.442 + */ 1.443 + public void selectMenuItemByPath(String[] listItems) { 1.444 + int listLength = listItems.length; 1.445 + if (listLength > 0) { 1.446 + selectMenuItem(listItems[0]); 1.447 + } 1.448 + if (listLength > 1) { 1.449 + for (int i = 1; i < listLength; i++) { 1.450 + String itemName = "^" + listItems[i] + "$"; 1.451 + mAsserter.ok(waitForPreferencesText(itemName), "Waiting for and scrolling once to find item " + itemName, itemName + " found"); 1.452 + mAsserter.ok(waitForEnabledText(itemName), "Waiting for enabled text " + itemName, itemName + " option is present and enabled"); 1.453 + mSolo.clickOnText(itemName); 1.454 + } 1.455 + } 1.456 + } 1.457 + 1.458 + public final void selectMenuItem(String menuItemName) { 1.459 + // build the item name ready to be used 1.460 + String itemName = "^" + menuItemName + "$"; 1.461 + mActions.sendSpecialKey(Actions.SpecialKey.MENU); 1.462 + if (waitForText(itemName)) { 1.463 + mSolo.clickOnText(itemName); 1.464 + } else { 1.465 + // Older versions of Android have additional settings under "More", 1.466 + // including settings that newer versions have under "Tools." 1.467 + if (mSolo.searchText("(^More$|^Tools$)")) { 1.468 + mSolo.clickOnText("(^More$|^Tools$)"); 1.469 + } 1.470 + waitForText(itemName); 1.471 + mSolo.clickOnText(itemName); 1.472 + } 1.473 + } 1.474 + 1.475 + public final void verifyHomePagerHidden() { 1.476 + final View homePagerContainer = mSolo.getView(R.id.home_pager_container); 1.477 + 1.478 + boolean rc = waitForCondition(new Condition() { 1.479 + @Override 1.480 + public boolean isSatisfied() { 1.481 + return homePagerContainer.getVisibility() != View.VISIBLE; 1.482 + } 1.483 + }, MAX_WAIT_HOME_PAGER_HIDDEN_MS); 1.484 + 1.485 + if (!rc) { 1.486 + mAsserter.ok(rc, "Verify HomePager is hidden", "HomePager is hidden"); 1.487 + } 1.488 + } 1.489 + 1.490 + public final void verifyPageTitle(String title) { 1.491 + final TextView urlBarTitle = (TextView) mSolo.getView(R.id.url_bar_title); 1.492 + String pageTitle = null; 1.493 + if (urlBarTitle != null) { 1.494 + // Wait for the title to make sure it has been displayed in case the view 1.495 + // does not update fast enough 1.496 + waitForCondition(new VerifyTextViewText(urlBarTitle, title), MAX_WAIT_MS); 1.497 + pageTitle = urlBarTitle.getText().toString(); 1.498 + } 1.499 + mAsserter.is(pageTitle, title, "Page title is correct"); 1.500 + } 1.501 + 1.502 + public final void verifyTabCount(int expectedTabCount) { 1.503 + Element tabCount = mDriver.findElement(getActivity(), R.id.tabs_counter); 1.504 + String tabCountText = tabCount.getText(); 1.505 + int tabCountInt = Integer.parseInt(tabCountText); 1.506 + mAsserter.is(tabCountInt, expectedTabCount, "The correct number of tabs are opened"); 1.507 + } 1.508 + 1.509 + // Used to perform clicks on pop-up buttons without having to close the virtual keyboard 1.510 + public void clickOnButton(String label) { 1.511 + final Button button = mSolo.getButton(label); 1.512 + try { 1.513 + runTestOnUiThread(new Runnable() { 1.514 + @Override 1.515 + public void run() { 1.516 + button.performClick(); 1.517 + } 1.518 + }); 1.519 + } catch (Throwable throwable) { 1.520 + mAsserter.ok(false, "Unable to click the button","Was unable to click button "); 1.521 + } 1.522 + } 1.523 + 1.524 + // Used to hide/show the virtual keyboard 1.525 + public void toggleVKB() { 1.526 + InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE); 1.527 + imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0); 1.528 + } 1.529 + 1.530 + public void addTab() { 1.531 + mSolo.clickOnView(mSolo.getView(R.id.tabs)); 1.532 + // wait for addTab to appear (this is usually immediate) 1.533 + boolean success = waitForCondition(new Condition() { 1.534 + @Override 1.535 + public boolean isSatisfied() { 1.536 + View addTabView = mSolo.getView(R.id.add_tab); 1.537 + if (addTabView == null) { 1.538 + return false; 1.539 + } 1.540 + return true; 1.541 + } 1.542 + }, MAX_WAIT_MS); 1.543 + mAsserter.ok(success, "waiting for add tab view", "add tab view available"); 1.544 + Actions.EventExpecter pageShowExpecter = mActions.expectGeckoEvent("Content:PageShow"); 1.545 + mSolo.clickOnView(mSolo.getView(R.id.add_tab)); 1.546 + pageShowExpecter.blockForEvent(); 1.547 + pageShowExpecter.unregisterListener(); 1.548 + } 1.549 + 1.550 + public void addTab(String url) { 1.551 + addTab(); 1.552 + 1.553 + // Adding a new tab opens about:home, so now we just need to load the url in it. 1.554 + inputAndLoadUrl(url); 1.555 + } 1.556 + 1.557 + /** 1.558 + * Gets the AdapterView of the tabs list. 1.559 + * 1.560 + * @return List view in the tabs tray 1.561 + */ 1.562 + private final AdapterView<ListAdapter> getTabsList() { 1.563 + Element tabs = mDriver.findElement(getActivity(), R.id.tabs); 1.564 + tabs.click(); 1.565 + return (AdapterView<ListAdapter>) getActivity().findViewById(R.id.normal_tabs); 1.566 + } 1.567 + 1.568 + /** 1.569 + * Gets the view in the tabs tray at the specified index. 1.570 + * 1.571 + * @return View at index 1.572 + */ 1.573 + private View getTabViewAt(final int index) { 1.574 + final View[] childView = { null }; 1.575 + 1.576 + final AdapterView<ListAdapter> view = getTabsList(); 1.577 + 1.578 + runOnUiThreadSync(new Runnable() { 1.579 + @Override 1.580 + public void run() { 1.581 + view.setSelection(index); 1.582 + 1.583 + // The selection isn't updated synchronously; posting a 1.584 + // runnable to the view's queue guarantees we'll run after the 1.585 + // layout pass. 1.586 + view.post(new Runnable() { 1.587 + @Override 1.588 + public void run() { 1.589 + // getChildAt() is relative to the list of visible 1.590 + // views, but our index is relative to all views in the 1.591 + // list. Subtract the first visible list position for 1.592 + // the correct offset. 1.593 + childView[0] = view.getChildAt(index - view.getFirstVisiblePosition()); 1.594 + } 1.595 + }); 1.596 + } 1.597 + }); 1.598 + 1.599 + boolean result = waitForCondition(new Condition() { 1.600 + @Override 1.601 + public boolean isSatisfied() { 1.602 + return childView[0] != null; 1.603 + } 1.604 + }, MAX_WAIT_MS); 1.605 + 1.606 + mAsserter.ok(result, "list item at index " + index + " exists", null); 1.607 + 1.608 + return childView[0]; 1.609 + } 1.610 + 1.611 + /** 1.612 + * Selects the tab at the specified index. 1.613 + * 1.614 + * @param index Index of tab to select 1.615 + */ 1.616 + public void selectTabAt(final int index) { 1.617 + mSolo.clickOnView(getTabViewAt(index)); 1.618 + } 1.619 + 1.620 + /** 1.621 + * Closes the tab at the specified index. 1.622 + * 1.623 + * @param index Index of tab to close 1.624 + */ 1.625 + public void closeTabAt(final int index) { 1.626 + View closeButton = getTabViewAt(index).findViewById(R.id.close); 1.627 + 1.628 + mSolo.clickOnView(closeButton); 1.629 + } 1.630 + 1.631 + public final void runOnUiThreadSync(Runnable runnable) { 1.632 + RobocopUtils.runOnUiThreadSync(mActivity, runnable); 1.633 + } 1.634 + 1.635 + /* Tap the "star" (bookmark) button to bookmark or un-bookmark the current page */ 1.636 + public void toggleBookmark() { 1.637 + mActions.sendSpecialKey(Actions.SpecialKey.MENU); 1.638 + waitForText("Settings"); 1.639 + 1.640 + // On ICS+ phones, there is no button labeled "Bookmarks" 1.641 + // instead we have to just dig through every button on the screen 1.642 + ArrayList<View> images = mSolo.getCurrentViews(); 1.643 + for (int i = 0; i < images.size(); i++) { 1.644 + final View view = images.get(i); 1.645 + boolean found = false; 1.646 + found = "Bookmark".equals(view.getContentDescription()); 1.647 + 1.648 + // on older android versions, try looking at the button's text 1.649 + if (!found) { 1.650 + if (view instanceof TextView) { 1.651 + found = "Bookmark".equals(((TextView)view).getText()); 1.652 + } 1.653 + } 1.654 + 1.655 + if (found) { 1.656 + int[] xy = new int[2]; 1.657 + view.getLocationOnScreen(xy); 1.658 + 1.659 + final int viewWidth = view.getWidth(); 1.660 + final int viewHeight = view.getHeight(); 1.661 + final float x = xy[0] + (viewWidth / 2.0f); 1.662 + float y = xy[1] + (viewHeight / 2.0f); 1.663 + 1.664 + mSolo.clickOnScreen(x, y); 1.665 + } 1.666 + } 1.667 + } 1.668 + 1.669 + public void clearPrivateData() { 1.670 + selectSettingsItem(StringHelper.PRIVACY_SECTION_LABEL, StringHelper.CLEAR_PRIVATE_DATA_LABEL); 1.671 + Actions.EventExpecter clearData = mActions.expectGeckoEvent("Sanitize:Finished"); 1.672 + mSolo.clickOnText("Clear data"); 1.673 + clearData.blockForEvent(); 1.674 + clearData.unregisterListener(); 1.675 + } 1.676 + 1.677 + class Device { 1.678 + public final String version; // 2.x or 3.x or 4.x 1.679 + public String type; // "tablet" or "phone" 1.680 + public final int width; 1.681 + public final int height; 1.682 + public final float density; 1.683 + 1.684 + public Device() { 1.685 + // Determine device version 1.686 + int sdk = Build.VERSION.SDK_INT; 1.687 + if (sdk < Build.VERSION_CODES.HONEYCOMB) { 1.688 + version = "2.x"; 1.689 + } else { 1.690 + if (sdk > Build.VERSION_CODES.HONEYCOMB_MR2) { 1.691 + version = "4.x"; 1.692 + } else { 1.693 + version = "3.x"; 1.694 + } 1.695 + } 1.696 + // Determine with and height 1.697 + DisplayMetrics dm = new DisplayMetrics(); 1.698 + getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm); 1.699 + height = dm.heightPixels; 1.700 + width = dm.widthPixels; 1.701 + density = dm.density; 1.702 + // Determine device type 1.703 + type = "phone"; 1.704 + try { 1.705 + if (GeckoAppShell.isTablet()) { 1.706 + type = "tablet"; 1.707 + } 1.708 + } catch (Exception e) { 1.709 + mAsserter.dumpLog("Exception in detectDevice", e); 1.710 + } 1.711 + } 1.712 + 1.713 + public void rotate() { 1.714 + if (getActivity().getRequestedOrientation () == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) { 1.715 + mSolo.setActivityOrientation(Solo.PORTRAIT); 1.716 + } else { 1.717 + mSolo.setActivityOrientation(Solo.LANDSCAPE); 1.718 + } 1.719 + } 1.720 + } 1.721 + 1.722 + class Navigation { 1.723 + private String devType; 1.724 + private String osVersion; 1.725 + 1.726 + public Navigation(Device mDevice) { 1.727 + devType = mDevice.type; 1.728 + osVersion = mDevice.version; 1.729 + } 1.730 + 1.731 + public void back() { 1.732 + Actions.EventExpecter pageShowExpecter = mActions.expectGeckoEvent("Content:PageShow"); 1.733 + 1.734 + if (devType.equals("tablet")) { 1.735 + Element backBtn = mDriver.findElement(getActivity(), R.id.back); 1.736 + backBtn.click(); 1.737 + } else { 1.738 + mActions.sendSpecialKey(Actions.SpecialKey.BACK); 1.739 + } 1.740 + 1.741 + pageShowExpecter.blockForEvent(); 1.742 + pageShowExpecter.unregisterListener(); 1.743 + } 1.744 + 1.745 + public void forward() { 1.746 + Actions.EventExpecter pageShowExpecter = mActions.expectGeckoEvent("Content:PageShow"); 1.747 + 1.748 + if (devType.equals("tablet")) { 1.749 + Element fwdBtn = mDriver.findElement(getActivity(), R.id.forward); 1.750 + fwdBtn.click(); 1.751 + } else { 1.752 + mActions.sendSpecialKey(Actions.SpecialKey.MENU); 1.753 + waitForText("^New Tab$"); 1.754 + if (!osVersion.equals("2.x")) { 1.755 + Element fwdBtn = mDriver.findElement(getActivity(), R.id.forward); 1.756 + fwdBtn.click(); 1.757 + } else { 1.758 + mSolo.clickOnText("^Forward$"); 1.759 + } 1.760 + ensureMenuClosed(); 1.761 + } 1.762 + 1.763 + pageShowExpecter.blockForEvent(); 1.764 + pageShowExpecter.unregisterListener(); 1.765 + } 1.766 + 1.767 + public void reload() { 1.768 + if (devType.equals("tablet")) { 1.769 + Element reloadBtn = mDriver.findElement(getActivity(), R.id.reload); 1.770 + reloadBtn.click(); 1.771 + } else { 1.772 + mActions.sendSpecialKey(Actions.SpecialKey.MENU); 1.773 + waitForText("^New Tab$"); 1.774 + if (!osVersion.equals("2.x")) { 1.775 + Element reloadBtn = mDriver.findElement(getActivity(), R.id.reload); 1.776 + reloadBtn.click(); 1.777 + } else { 1.778 + mSolo.clickOnText("^Reload$"); 1.779 + } 1.780 + ensureMenuClosed(); 1.781 + } 1.782 + } 1.783 + 1.784 + // DEPRECATED! 1.785 + // Use BaseTest.toggleBookmark() in new code. 1.786 + public void bookmark() { 1.787 + mActions.sendSpecialKey(Actions.SpecialKey.MENU); 1.788 + waitForText("^New Tab$"); 1.789 + if (mSolo.searchText("^Bookmark$")) { 1.790 + // This is the Android 2.x so the button has text 1.791 + mSolo.clickOnText("^Bookmark$"); 1.792 + } else { 1.793 + Element bookmarkBtn = mDriver.findElement(getActivity(), R.id.bookmark); 1.794 + if (bookmarkBtn != null) { 1.795 + // We are on Android 4.x so the button is an image button 1.796 + bookmarkBtn.click(); 1.797 + } 1.798 + } 1.799 + ensureMenuClosed(); 1.800 + } 1.801 + 1.802 + // On some devices, the menu may not be dismissed after clicking on an 1.803 + // item. Close it here. 1.804 + private void ensureMenuClosed() { 1.805 + if (mSolo.searchText("^New Tab$")) { 1.806 + mActions.sendSpecialKey(Actions.SpecialKey.BACK); 1.807 + } 1.808 + } 1.809 + } 1.810 + 1.811 + /** 1.812 + * Gets the string representation of a stack trace. 1.813 + * 1.814 + * @param t Throwable to get stack trace for 1.815 + * @return Stack trace as a string 1.816 + */ 1.817 + public static String getStackTraceString(Throwable t) { 1.818 + StringWriter sw = new StringWriter(); 1.819 + t.printStackTrace(new PrintWriter(sw)); 1.820 + return sw.toString(); 1.821 + } 1.822 + 1.823 + /** 1.824 + * Condition class that waits for a view, and allows callers access it when done. 1.825 + */ 1.826 + private class DescriptionCondition<T extends View> implements Condition { 1.827 + public T mView; 1.828 + private String mDescr; 1.829 + private Class<T> mCls; 1.830 + 1.831 + public DescriptionCondition(Class<T> cls, String descr) { 1.832 + mDescr = descr; 1.833 + mCls = cls; 1.834 + } 1.835 + 1.836 + @Override 1.837 + public boolean isSatisfied() { 1.838 + mView = findViewWithContentDescription(mCls, mDescr); 1.839 + return (mView != null); 1.840 + } 1.841 + } 1.842 + 1.843 + /** 1.844 + * Wait for a view with the specified description . 1.845 + */ 1.846 + public <T extends View> T waitForViewWithDescription(Class<T> cls, String description) { 1.847 + DescriptionCondition<T> c = new DescriptionCondition<T>(cls, description); 1.848 + waitForCondition(c, MAX_WAIT_ENABLED_TEXT_MS); 1.849 + return c.mView; 1.850 + } 1.851 + 1.852 + /** 1.853 + * Get an active view with the specified description . 1.854 + */ 1.855 + public <T extends View> T findViewWithContentDescription(Class<T> cls, String description) { 1.856 + for (T view : mSolo.getCurrentViews(cls)) { 1.857 + final String descr = (String) view.getContentDescription(); 1.858 + if (TextUtils.isEmpty(descr)) { 1.859 + continue; 1.860 + } 1.861 + 1.862 + if (TextUtils.equals(description, descr)) { 1.863 + return view; 1.864 + } 1.865 + } 1.866 + 1.867 + return null; 1.868 + } 1.869 + 1.870 + /** 1.871 + * Abstract class for running small test cases within a BaseTest. 1.872 + */ 1.873 + abstract class TestCase implements Runnable { 1.874 + /** 1.875 + * Implement tests here. setUp and tearDown for the test case 1.876 + * should be handled by the parent test. This is so we can avoid the 1.877 + * overhead of starting Gecko and creating profiles. 1.878 + */ 1.879 + protected abstract void test() throws Exception; 1.880 + 1.881 + @Override 1.882 + public void run() { 1.883 + try { 1.884 + test(); 1.885 + } catch (Exception e) { 1.886 + mAsserter.ok(false, 1.887 + "Test " + this.getClass().getName() + " threw exception: " + e, 1.888 + ""); 1.889 + } 1.890 + } 1.891 + } 1.892 + 1.893 + /** 1.894 + * Set the preference and wait for it to change before proceeding with the test. 1.895 + */ 1.896 + public void setPreferenceAndWaitForChange(final JSONObject jsonPref) { 1.897 + mActions.sendGeckoEvent("Preferences:Set", jsonPref.toString()); 1.898 + 1.899 + // Get the preference name from the json and store it in an array. This array 1.900 + // will be used later while fetching the preference data. 1.901 + String[] prefNames = new String[1]; 1.902 + try { 1.903 + prefNames[0] = jsonPref.getString("name"); 1.904 + } catch (JSONException e) { 1.905 + mAsserter.ok(false, "Exception in setPreferenceAndWaitForChange", getStackTraceString(e)); 1.906 + } 1.907 + 1.908 + // Wait for confirmation of the pref change before proceeding with the test. 1.909 + final int ourRequestID = mPreferenceRequestID--; 1.910 + final Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data"); 1.911 + mActions.sendPreferencesGetEvent(ourRequestID, prefNames); 1.912 + 1.913 + // Wait until we get the correct "Preferences:Data" event 1.914 + waitForCondition(new Condition() { 1.915 + final long endTime = SystemClock.elapsedRealtime() + MAX_WAIT_BLOCK_FOR_EVENT_DATA_MS; 1.916 + 1.917 + @Override 1.918 + public boolean isSatisfied() { 1.919 + try { 1.920 + long timeout = endTime - SystemClock.elapsedRealtime(); 1.921 + if (timeout < 0) { 1.922 + timeout = 0; 1.923 + } 1.924 + 1.925 + JSONObject data = new JSONObject(eventExpecter.blockForEventDataWithTimeout(timeout)); 1.926 + int requestID = data.getInt("requestId"); 1.927 + if (requestID != ourRequestID) { 1.928 + return false; 1.929 + } 1.930 + 1.931 + JSONArray preferences = data.getJSONArray("preferences"); 1.932 + mAsserter.is(preferences.length(), 1, "Expecting preference array to have one element"); 1.933 + JSONObject prefs = (JSONObject) preferences.get(0); 1.934 + mAsserter.is(prefs.getString("name"), jsonPref.getString("name"), 1.935 + "Expecting returned preference name to be the same as the set name"); 1.936 + mAsserter.is(prefs.getString("type"), jsonPref.getString("type"), 1.937 + "Expecting returned preference type to be the same as the set type"); 1.938 + mAsserter.is(prefs.get("value"), jsonPref.get("value"), 1.939 + "Expecting returned preference value to be the same as the set value"); 1.940 + return true; 1.941 + } catch(JSONException e) { 1.942 + mAsserter.ok(false, "Exception in setPreferenceAndWaitForChange", getStackTraceString(e)); 1.943 + // Please the java compiler 1.944 + return false; 1.945 + } 1.946 + } 1.947 + }, MAX_WAIT_BLOCK_FOR_EVENT_DATA_MS); 1.948 + 1.949 + eventExpecter.unregisterListener(); 1.950 + } 1.951 +}