mobile/android/base/tests/BaseTest.java

Wed, 31 Dec 2014 07:22:50 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 07:22:50 +0100
branch
TOR_BUG_3246
changeset 4
fc2d59ddac77
permissions
-rw-r--r--

Correct previous dual key logic pending first delivery installment.

michael@0 1 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 2 * License, v. 2.0. If a copy of the MPL was not distributed with this
michael@0 3 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 4
michael@0 5 package org.mozilla.gecko.tests;
michael@0 6
michael@0 7 import java.io.File;
michael@0 8 import java.io.IOException;
michael@0 9 import java.io.InputStream;
michael@0 10 import java.io.PrintWriter;
michael@0 11 import java.io.StringWriter;
michael@0 12 import java.util.ArrayList;
michael@0 13
michael@0 14 import org.json.JSONArray;
michael@0 15 import org.json.JSONException;
michael@0 16 import org.json.JSONObject;
michael@0 17 import org.mozilla.gecko.Actions;
michael@0 18 import org.mozilla.gecko.Driver;
michael@0 19 import org.mozilla.gecko.Element;
michael@0 20 import org.mozilla.gecko.FennecNativeActions;
michael@0 21 import org.mozilla.gecko.FennecNativeDriver;
michael@0 22 import org.mozilla.gecko.GeckoAppShell;
michael@0 23 import org.mozilla.gecko.GeckoEvent;
michael@0 24 import org.mozilla.gecko.GeckoThread;
michael@0 25 import org.mozilla.gecko.GeckoThread.LaunchState;
michael@0 26 import org.mozilla.gecko.R;
michael@0 27 import org.mozilla.gecko.RobocopUtils;
michael@0 28 import org.mozilla.gecko.Tab;
michael@0 29 import org.mozilla.gecko.Tabs;
michael@0 30
michael@0 31 import android.app.Activity;
michael@0 32 import android.content.ContentValues;
michael@0 33 import android.content.Intent;
michael@0 34 import android.content.pm.ActivityInfo;
michael@0 35 import android.content.res.AssetManager;
michael@0 36 import android.database.Cursor;
michael@0 37 import android.os.Build;
michael@0 38 import android.os.SystemClock;
michael@0 39 import android.support.v4.app.Fragment;
michael@0 40 import android.support.v4.app.FragmentActivity;
michael@0 41 import android.support.v4.app.FragmentManager;
michael@0 42 import android.text.TextUtils;
michael@0 43 import android.util.DisplayMetrics;
michael@0 44 import android.view.View;
michael@0 45 import android.view.inputmethod.InputMethodManager;
michael@0 46 import android.widget.AdapterView;
michael@0 47 import android.widget.Button;
michael@0 48 import android.widget.EditText;
michael@0 49 import android.widget.ListAdapter;
michael@0 50 import android.widget.TextView;
michael@0 51
michael@0 52 import com.jayway.android.robotium.solo.Condition;
michael@0 53 import com.jayway.android.robotium.solo.Solo;
michael@0 54
michael@0 55 /**
michael@0 56 * A convenient base class suitable for most Robocop tests.
michael@0 57 */
michael@0 58 @SuppressWarnings("unchecked")
michael@0 59 abstract class BaseTest extends BaseRobocopTest {
michael@0 60 private static final int VERIFY_URL_TIMEOUT = 2000;
michael@0 61 private static final int MAX_WAIT_ENABLED_TEXT_MS = 10000;
michael@0 62 private static final int MAX_WAIT_HOME_PAGER_HIDDEN_MS = 15000;
michael@0 63 public static final int MAX_WAIT_MS = 4500;
michael@0 64 public static final int LONG_PRESS_TIME = 6000;
michael@0 65 private static final int GECKO_READY_WAIT_MS = 180000;
michael@0 66 public static final int MAX_WAIT_BLOCK_FOR_EVENT_DATA_MS = 90000;
michael@0 67
michael@0 68 private Activity mActivity;
michael@0 69 private int mPreferenceRequestID = 0;
michael@0 70 protected Solo mSolo;
michael@0 71 protected Driver mDriver;
michael@0 72 protected Actions mActions;
michael@0 73 protected String mBaseUrl;
michael@0 74 protected String mRawBaseUrl;
michael@0 75 protected String mProfile;
michael@0 76 public Device mDevice;
michael@0 77 protected DatabaseHelper mDatabaseHelper;
michael@0 78 protected StringHelper mStringHelper;
michael@0 79 protected int mScreenMidWidth;
michael@0 80 protected int mScreenMidHeight;
michael@0 81
michael@0 82 protected void blockForGeckoReady() {
michael@0 83 try {
michael@0 84 Actions.EventExpecter geckoReadyExpector = mActions.expectGeckoEvent("Gecko:Ready");
michael@0 85 if (!GeckoThread.checkLaunchState(LaunchState.GeckoRunning)) {
michael@0 86 geckoReadyExpector.blockForEvent(GECKO_READY_WAIT_MS, true);
michael@0 87 }
michael@0 88 geckoReadyExpector.unregisterListener();
michael@0 89 } catch (Exception e) {
michael@0 90 mAsserter.dumpLog("Exception in blockForGeckoReady", e);
michael@0 91 }
michael@0 92 }
michael@0 93
michael@0 94 @Override
michael@0 95 public void setUp() throws Exception {
michael@0 96 super.setUp();
michael@0 97
michael@0 98 // Create the intent to be used with all the important arguments.
michael@0 99 mBaseUrl = ((String) mConfig.get("host")).replaceAll("(/$)", "");
michael@0 100 mRawBaseUrl = ((String) mConfig.get("rawhost")).replaceAll("(/$)", "");
michael@0 101 Intent i = new Intent(Intent.ACTION_MAIN);
michael@0 102 mProfile = (String) mConfig.get("profile");
michael@0 103 i.putExtra("args", "-no-remote -profile " + mProfile);
michael@0 104 String envString = (String) mConfig.get("envvars");
michael@0 105 if (envString != "") {
michael@0 106 String[] envStrings = envString.split(",");
michael@0 107 for (int iter = 0; iter < envStrings.length; iter++) {
michael@0 108 i.putExtra("env" + iter, envStrings[iter]);
michael@0 109 }
michael@0 110 }
michael@0 111 // Start the activity
michael@0 112 setActivityIntent(i);
michael@0 113 mActivity = getActivity();
michael@0 114 // Set up Robotium.solo and Driver objects
michael@0 115 mSolo = new Solo(getInstrumentation(), mActivity);
michael@0 116 mDriver = new FennecNativeDriver(mActivity, mSolo, mRootPath);
michael@0 117 mActions = new FennecNativeActions(mActivity, mSolo, getInstrumentation(), mAsserter);
michael@0 118 mDevice = new Device();
michael@0 119 mDatabaseHelper = new DatabaseHelper(mActivity, mAsserter);
michael@0 120 mStringHelper = new StringHelper();
michael@0 121 }
michael@0 122
michael@0 123 @Override
michael@0 124 protected void runTest() throws Throwable {
michael@0 125 try {
michael@0 126 super.runTest();
michael@0 127 } catch (Throwable t) {
michael@0 128 // save screenshot -- written to /mnt/sdcard/Robotium-Screenshots
michael@0 129 // as <filename>.jpg
michael@0 130 mSolo.takeScreenshot("robocop-screenshot");
michael@0 131 if (mAsserter != null) {
michael@0 132 mAsserter.dumpLog("Exception caught during test!", t);
michael@0 133 mAsserter.ok(false, "Exception caught", t.toString());
michael@0 134 }
michael@0 135 // re-throw to continue bail-out
michael@0 136 throw t;
michael@0 137 }
michael@0 138 }
michael@0 139
michael@0 140 @Override
michael@0 141 public void tearDown() throws Exception {
michael@0 142 try {
michael@0 143 mAsserter.endTest();
michael@0 144 // request a force quit of the browser and wait for it to take effect
michael@0 145 GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Robocop:Quit", null));
michael@0 146 mSolo.sleep(7000);
michael@0 147 // if still running, finish activities as recommended by Robotium
michael@0 148 mSolo.finishOpenedActivities();
michael@0 149 } catch (Throwable e) {
michael@0 150 e.printStackTrace();
michael@0 151 }
michael@0 152 super.tearDown();
michael@0 153 }
michael@0 154
michael@0 155 public void assertMatches(String value, String regex, String name) {
michael@0 156 if (value == null) {
michael@0 157 mAsserter.ok(false, name, "Expected /" + regex + "/, got null");
michael@0 158 return;
michael@0 159 }
michael@0 160 mAsserter.ok(value.matches(regex), name, "Expected /" + regex +"/, got \"" + value + "\"");
michael@0 161 }
michael@0 162
michael@0 163 /**
michael@0 164 * Click on the URL bar to focus it and enter editing mode.
michael@0 165 */
michael@0 166 protected final void focusUrlBar() {
michael@0 167 // Click on the browser toolbar to enter editing mode
michael@0 168 final View toolbarView = mSolo.getView(R.id.browser_toolbar);
michael@0 169 mSolo.clickOnView(toolbarView);
michael@0 170
michael@0 171 // Wait for highlighed text to gain focus
michael@0 172 boolean success = waitForCondition(new Condition() {
michael@0 173 @Override
michael@0 174 public boolean isSatisfied() {
michael@0 175 EditText urlEditText = (EditText) mSolo.getView(R.id.url_edit_text);
michael@0 176 if (urlEditText.isInputMethodTarget()) {
michael@0 177 return true;
michael@0 178 }
michael@0 179 return false;
michael@0 180 }
michael@0 181 }, MAX_WAIT_ENABLED_TEXT_MS);
michael@0 182
michael@0 183 mAsserter.ok(success, "waiting for urlbar text to gain focus", "urlbar text gained focus");
michael@0 184 }
michael@0 185
michael@0 186 protected final void enterUrl(String url) {
michael@0 187 final EditText urlEditView = (EditText) mSolo.getView(R.id.url_edit_text);
michael@0 188
michael@0 189 focusUrlBar();
michael@0 190
michael@0 191 // Send the keys for the URL we want to enter
michael@0 192 mSolo.clearEditText(urlEditView);
michael@0 193 mSolo.enterText(urlEditView, url);
michael@0 194
michael@0 195 // Get the URL text from the URL bar EditText view
michael@0 196 final String urlBarText = urlEditView.getText().toString();
michael@0 197 mAsserter.is(url, urlBarText, "URL typed properly");
michael@0 198 }
michael@0 199
michael@0 200 protected final Fragment getBrowserSearch() {
michael@0 201 final FragmentManager fm = ((FragmentActivity) getActivity()).getSupportFragmentManager();
michael@0 202 return fm.findFragmentByTag("browser_search");
michael@0 203 }
michael@0 204
michael@0 205 protected final void hitEnterAndWait() {
michael@0 206 Actions.EventExpecter contentEventExpecter = mActions.expectGeckoEvent("DOMContentLoaded");
michael@0 207 mActions.sendSpecialKey(Actions.SpecialKey.ENTER);
michael@0 208 // wait for screen to load
michael@0 209 contentEventExpecter.blockForEvent();
michael@0 210 contentEventExpecter.unregisterListener();
michael@0 211 }
michael@0 212
michael@0 213 /**
michael@0 214 * Load <code>url</code> by sending key strokes to the URL bar UI.
michael@0 215 *
michael@0 216 * This method waits synchronously for the <code>DOMContentLoaded</code>
michael@0 217 * message from Gecko before returning.
michael@0 218 */
michael@0 219 protected final void inputAndLoadUrl(String url) {
michael@0 220 enterUrl(url);
michael@0 221 hitEnterAndWait();
michael@0 222 }
michael@0 223
michael@0 224 /**
michael@0 225 * Load <code>url</code> using reflection and the internal
michael@0 226 * <code>org.mozilla.gecko.Tabs</code> API.
michael@0 227 *
michael@0 228 * This method does not wait for any confirmation from Gecko before
michael@0 229 * returning.
michael@0 230 */
michael@0 231 protected final void loadUrl(final String url) {
michael@0 232 try {
michael@0 233 Tabs.getInstance().loadUrl(url);
michael@0 234 } catch (Exception e) {
michael@0 235 mAsserter.dumpLog("Exception in loadUrl", e);
michael@0 236 throw new RuntimeException(e);
michael@0 237 }
michael@0 238 }
michael@0 239
michael@0 240 protected final void closeTab(int tabId) {
michael@0 241 Tabs tabs = Tabs.getInstance();
michael@0 242 Tab tab = tabs.getTab(tabId);
michael@0 243 tabs.closeTab(tab);
michael@0 244 }
michael@0 245
michael@0 246 public final void verifyUrl(String url) {
michael@0 247 final EditText urlEditText = (EditText) mSolo.getView(R.id.url_edit_text);
michael@0 248 String urlBarText = null;
michael@0 249 if (urlEditText != null) {
michael@0 250 // wait for a short time for the expected text, in case there is a delay
michael@0 251 // in updating the view
michael@0 252 waitForCondition(new VerifyTextViewText(urlEditText, url), VERIFY_URL_TIMEOUT);
michael@0 253 urlBarText = urlEditText.getText().toString();
michael@0 254
michael@0 255 }
michael@0 256 mAsserter.is(urlBarText, url, "Browser toolbar URL stayed the same");
michael@0 257 }
michael@0 258
michael@0 259 class VerifyTextViewText implements Condition {
michael@0 260 private TextView mTextView;
michael@0 261 private String mExpected;
michael@0 262 public VerifyTextViewText(TextView textView, String expected) {
michael@0 263 mTextView = textView;
michael@0 264 mExpected = expected;
michael@0 265 }
michael@0 266
michael@0 267 @Override
michael@0 268 public boolean isSatisfied() {
michael@0 269 String textValue = mTextView.getText().toString();
michael@0 270 return mExpected.equals(textValue);
michael@0 271 }
michael@0 272 }
michael@0 273
michael@0 274 protected final String getAbsoluteUrl(String url) {
michael@0 275 return mBaseUrl + "/" + url.replaceAll("(^/)", "");
michael@0 276 }
michael@0 277
michael@0 278 protected final String getAbsoluteRawUrl(String url) {
michael@0 279 return mRawBaseUrl + "/" + url.replaceAll("(^/)", "");
michael@0 280 }
michael@0 281
michael@0 282 /*
michael@0 283 * Wrapper method for mSolo.waitForCondition with additional logging.
michael@0 284 */
michael@0 285 protected final boolean waitForCondition(Condition condition, int timeout) {
michael@0 286 boolean result = mSolo.waitForCondition(condition, timeout);
michael@0 287 if (!result) {
michael@0 288 // Log timeout failure for diagnostic purposes only; a failed wait may
michael@0 289 // be normal and does not necessarily warrant a test asssertion/failure.
michael@0 290 mAsserter.dumpLog("waitForCondition timeout after " + timeout + " ms.");
michael@0 291 }
michael@0 292 return result;
michael@0 293 }
michael@0 294
michael@0 295 // TODO: With Robotium 4.2, we should use Condition and waitForCondition instead.
michael@0 296 // Future boolean tests should not use this method.
michael@0 297 protected final boolean waitForTest(BooleanTest t, int timeout) {
michael@0 298 long end = SystemClock.uptimeMillis() + timeout;
michael@0 299 while (SystemClock.uptimeMillis() < end) {
michael@0 300 if (t.test()) {
michael@0 301 return true;
michael@0 302 }
michael@0 303 mSolo.sleep(100);
michael@0 304 }
michael@0 305 // log out wait failure for diagnostic purposes only;
michael@0 306 // a failed wait may be normal and does not necessarily
michael@0 307 // warrant a test assertion/failure
michael@0 308 mAsserter.dumpLog("waitForTest timeout after "+timeout+" ms");
michael@0 309 return false;
michael@0 310 }
michael@0 311
michael@0 312 // TODO: With Robotium 4.2, we should use Condition and waitForCondition instead.
michael@0 313 // Future boolean tests should not implement this interface.
michael@0 314 protected interface BooleanTest {
michael@0 315 public boolean test();
michael@0 316 }
michael@0 317
michael@0 318 public void SqliteCompare(String dbName, String sqlCommand, ContentValues[] cvs) {
michael@0 319 File profile = new File(mProfile);
michael@0 320 String dbPath = new File(profile, dbName).getPath();
michael@0 321
michael@0 322 Cursor c = mActions.querySql(dbPath, sqlCommand);
michael@0 323 SqliteCompare(c, cvs);
michael@0 324 }
michael@0 325
michael@0 326 public void SqliteCompare(Cursor c, ContentValues[] cvs) {
michael@0 327 mAsserter.is(c.getCount(), cvs.length, "List is correct length");
michael@0 328 if (c.moveToFirst()) {
michael@0 329 do {
michael@0 330 boolean found = false;
michael@0 331 for (int i = 0; !found && i < cvs.length; i++) {
michael@0 332 if (CursorMatches(c, cvs[i])) {
michael@0 333 found = true;
michael@0 334 }
michael@0 335 }
michael@0 336 mAsserter.is(found, true, "Password was found");
michael@0 337 } while (c.moveToNext());
michael@0 338 }
michael@0 339 }
michael@0 340
michael@0 341 public boolean CursorMatches(Cursor c, ContentValues cv) {
michael@0 342 for (int i = 0; i < c.getColumnCount(); i++) {
michael@0 343 String column = c.getColumnName(i);
michael@0 344 if (cv.containsKey(column)) {
michael@0 345 mAsserter.info("Comparing", "Column values for: " + column);
michael@0 346 Object value = cv.get(column);
michael@0 347 if (value == null) {
michael@0 348 if (!c.isNull(i)) {
michael@0 349 return false;
michael@0 350 }
michael@0 351 } else {
michael@0 352 if (c.isNull(i) || !value.toString().equals(c.getString(i))) {
michael@0 353 return false;
michael@0 354 }
michael@0 355 }
michael@0 356 }
michael@0 357 }
michael@0 358 return true;
michael@0 359 }
michael@0 360
michael@0 361 public InputStream getAsset(String filename) throws IOException {
michael@0 362 AssetManager assets = getInstrumentation().getContext().getAssets();
michael@0 363 return assets.open(filename);
michael@0 364 }
michael@0 365
michael@0 366 public boolean waitForText(String text) {
michael@0 367 boolean rc = mSolo.waitForText(text);
michael@0 368 if (!rc) {
michael@0 369 // log out failed wait for diagnostic purposes only;
michael@0 370 // waitForText failures are sometimes expected/normal
michael@0 371 mAsserter.dumpLog("waitForText timeout on "+text);
michael@0 372 }
michael@0 373 return rc;
michael@0 374 }
michael@0 375
michael@0 376 // waitForText usually scrolls down in a view when text is not visible.
michael@0 377 // For PreferenceScreens and dialogs, Solo.waitForText scrolling does not
michael@0 378 // work, so we use this hack to do the same thing.
michael@0 379 protected boolean waitForPreferencesText(String txt) {
michael@0 380 boolean foundText = waitForText(txt);
michael@0 381 if (!foundText) {
michael@0 382 if ((mScreenMidWidth == 0) || (mScreenMidHeight == 0)) {
michael@0 383 mScreenMidWidth = mDriver.getGeckoWidth()/2;
michael@0 384 mScreenMidHeight = mDriver.getGeckoHeight()/2;
michael@0 385 }
michael@0 386
michael@0 387 // If we don't see the item, scroll down once in case it's off-screen.
michael@0 388 // Hacky way to scroll down. solo.scroll* does not work in dialogs.
michael@0 389 MotionEventHelper meh = new MotionEventHelper(getInstrumentation(), mDriver.getGeckoLeft(), mDriver.getGeckoTop());
michael@0 390 meh.dragSync(mScreenMidWidth, mScreenMidHeight+100, mScreenMidWidth, mScreenMidHeight-100);
michael@0 391
michael@0 392 foundText = mSolo.waitForText(txt);
michael@0 393 }
michael@0 394 return foundText;
michael@0 395 }
michael@0 396
michael@0 397 /**
michael@0 398 * Wait for <text> to be visible and also be enabled/clickable.
michael@0 399 */
michael@0 400 public boolean waitForEnabledText(String text) {
michael@0 401 final String testText = text;
michael@0 402 boolean rc = waitForCondition(new Condition() {
michael@0 403 @Override
michael@0 404 public boolean isSatisfied() {
michael@0 405 // Solo.getText() could be used here, except that it sometimes
michael@0 406 // hits an assertion when the requested text is not found.
michael@0 407 ArrayList<View> views = mSolo.getCurrentViews();
michael@0 408 for (View view : views) {
michael@0 409 if (view instanceof TextView) {
michael@0 410 TextView tv = (TextView)view;
michael@0 411 String viewText = tv.getText().toString();
michael@0 412 if (tv.isEnabled() && viewText != null && viewText.matches(testText)) {
michael@0 413 return true;
michael@0 414 }
michael@0 415 }
michael@0 416 }
michael@0 417 return false;
michael@0 418 }
michael@0 419 }, MAX_WAIT_ENABLED_TEXT_MS);
michael@0 420 if (!rc) {
michael@0 421 // log out failed wait for diagnostic purposes only;
michael@0 422 // failures are sometimes expected/normal
michael@0 423 mAsserter.dumpLog("waitForEnabledText timeout on "+text);
michael@0 424 }
michael@0 425 return rc;
michael@0 426 }
michael@0 427
michael@0 428
michael@0 429 /**
michael@0 430 * Select <item> from Menu > "Settings" > <section>.
michael@0 431 */
michael@0 432 public void selectSettingsItem(String section, String item) {
michael@0 433 String[] itemPath = { "Settings", section, item };
michael@0 434 selectMenuItemByPath(itemPath);
michael@0 435 }
michael@0 436
michael@0 437 /**
michael@0 438 * Traverses the items in listItems in order in the menu.
michael@0 439 */
michael@0 440 public void selectMenuItemByPath(String[] listItems) {
michael@0 441 int listLength = listItems.length;
michael@0 442 if (listLength > 0) {
michael@0 443 selectMenuItem(listItems[0]);
michael@0 444 }
michael@0 445 if (listLength > 1) {
michael@0 446 for (int i = 1; i < listLength; i++) {
michael@0 447 String itemName = "^" + listItems[i] + "$";
michael@0 448 mAsserter.ok(waitForPreferencesText(itemName), "Waiting for and scrolling once to find item " + itemName, itemName + " found");
michael@0 449 mAsserter.ok(waitForEnabledText(itemName), "Waiting for enabled text " + itemName, itemName + " option is present and enabled");
michael@0 450 mSolo.clickOnText(itemName);
michael@0 451 }
michael@0 452 }
michael@0 453 }
michael@0 454
michael@0 455 public final void selectMenuItem(String menuItemName) {
michael@0 456 // build the item name ready to be used
michael@0 457 String itemName = "^" + menuItemName + "$";
michael@0 458 mActions.sendSpecialKey(Actions.SpecialKey.MENU);
michael@0 459 if (waitForText(itemName)) {
michael@0 460 mSolo.clickOnText(itemName);
michael@0 461 } else {
michael@0 462 // Older versions of Android have additional settings under "More",
michael@0 463 // including settings that newer versions have under "Tools."
michael@0 464 if (mSolo.searchText("(^More$|^Tools$)")) {
michael@0 465 mSolo.clickOnText("(^More$|^Tools$)");
michael@0 466 }
michael@0 467 waitForText(itemName);
michael@0 468 mSolo.clickOnText(itemName);
michael@0 469 }
michael@0 470 }
michael@0 471
michael@0 472 public final void verifyHomePagerHidden() {
michael@0 473 final View homePagerContainer = mSolo.getView(R.id.home_pager_container);
michael@0 474
michael@0 475 boolean rc = waitForCondition(new Condition() {
michael@0 476 @Override
michael@0 477 public boolean isSatisfied() {
michael@0 478 return homePagerContainer.getVisibility() != View.VISIBLE;
michael@0 479 }
michael@0 480 }, MAX_WAIT_HOME_PAGER_HIDDEN_MS);
michael@0 481
michael@0 482 if (!rc) {
michael@0 483 mAsserter.ok(rc, "Verify HomePager is hidden", "HomePager is hidden");
michael@0 484 }
michael@0 485 }
michael@0 486
michael@0 487 public final void verifyPageTitle(String title) {
michael@0 488 final TextView urlBarTitle = (TextView) mSolo.getView(R.id.url_bar_title);
michael@0 489 String pageTitle = null;
michael@0 490 if (urlBarTitle != null) {
michael@0 491 // Wait for the title to make sure it has been displayed in case the view
michael@0 492 // does not update fast enough
michael@0 493 waitForCondition(new VerifyTextViewText(urlBarTitle, title), MAX_WAIT_MS);
michael@0 494 pageTitle = urlBarTitle.getText().toString();
michael@0 495 }
michael@0 496 mAsserter.is(pageTitle, title, "Page title is correct");
michael@0 497 }
michael@0 498
michael@0 499 public final void verifyTabCount(int expectedTabCount) {
michael@0 500 Element tabCount = mDriver.findElement(getActivity(), R.id.tabs_counter);
michael@0 501 String tabCountText = tabCount.getText();
michael@0 502 int tabCountInt = Integer.parseInt(tabCountText);
michael@0 503 mAsserter.is(tabCountInt, expectedTabCount, "The correct number of tabs are opened");
michael@0 504 }
michael@0 505
michael@0 506 // Used to perform clicks on pop-up buttons without having to close the virtual keyboard
michael@0 507 public void clickOnButton(String label) {
michael@0 508 final Button button = mSolo.getButton(label);
michael@0 509 try {
michael@0 510 runTestOnUiThread(new Runnable() {
michael@0 511 @Override
michael@0 512 public void run() {
michael@0 513 button.performClick();
michael@0 514 }
michael@0 515 });
michael@0 516 } catch (Throwable throwable) {
michael@0 517 mAsserter.ok(false, "Unable to click the button","Was unable to click button ");
michael@0 518 }
michael@0 519 }
michael@0 520
michael@0 521 // Used to hide/show the virtual keyboard
michael@0 522 public void toggleVKB() {
michael@0 523 InputMethodManager imm = (InputMethodManager) getActivity().getSystemService(Activity.INPUT_METHOD_SERVICE);
michael@0 524 imm.toggleSoftInput(InputMethodManager.HIDE_IMPLICIT_ONLY, 0);
michael@0 525 }
michael@0 526
michael@0 527 public void addTab() {
michael@0 528 mSolo.clickOnView(mSolo.getView(R.id.tabs));
michael@0 529 // wait for addTab to appear (this is usually immediate)
michael@0 530 boolean success = waitForCondition(new Condition() {
michael@0 531 @Override
michael@0 532 public boolean isSatisfied() {
michael@0 533 View addTabView = mSolo.getView(R.id.add_tab);
michael@0 534 if (addTabView == null) {
michael@0 535 return false;
michael@0 536 }
michael@0 537 return true;
michael@0 538 }
michael@0 539 }, MAX_WAIT_MS);
michael@0 540 mAsserter.ok(success, "waiting for add tab view", "add tab view available");
michael@0 541 Actions.EventExpecter pageShowExpecter = mActions.expectGeckoEvent("Content:PageShow");
michael@0 542 mSolo.clickOnView(mSolo.getView(R.id.add_tab));
michael@0 543 pageShowExpecter.blockForEvent();
michael@0 544 pageShowExpecter.unregisterListener();
michael@0 545 }
michael@0 546
michael@0 547 public void addTab(String url) {
michael@0 548 addTab();
michael@0 549
michael@0 550 // Adding a new tab opens about:home, so now we just need to load the url in it.
michael@0 551 inputAndLoadUrl(url);
michael@0 552 }
michael@0 553
michael@0 554 /**
michael@0 555 * Gets the AdapterView of the tabs list.
michael@0 556 *
michael@0 557 * @return List view in the tabs tray
michael@0 558 */
michael@0 559 private final AdapterView<ListAdapter> getTabsList() {
michael@0 560 Element tabs = mDriver.findElement(getActivity(), R.id.tabs);
michael@0 561 tabs.click();
michael@0 562 return (AdapterView<ListAdapter>) getActivity().findViewById(R.id.normal_tabs);
michael@0 563 }
michael@0 564
michael@0 565 /**
michael@0 566 * Gets the view in the tabs tray at the specified index.
michael@0 567 *
michael@0 568 * @return View at index
michael@0 569 */
michael@0 570 private View getTabViewAt(final int index) {
michael@0 571 final View[] childView = { null };
michael@0 572
michael@0 573 final AdapterView<ListAdapter> view = getTabsList();
michael@0 574
michael@0 575 runOnUiThreadSync(new Runnable() {
michael@0 576 @Override
michael@0 577 public void run() {
michael@0 578 view.setSelection(index);
michael@0 579
michael@0 580 // The selection isn't updated synchronously; posting a
michael@0 581 // runnable to the view's queue guarantees we'll run after the
michael@0 582 // layout pass.
michael@0 583 view.post(new Runnable() {
michael@0 584 @Override
michael@0 585 public void run() {
michael@0 586 // getChildAt() is relative to the list of visible
michael@0 587 // views, but our index is relative to all views in the
michael@0 588 // list. Subtract the first visible list position for
michael@0 589 // the correct offset.
michael@0 590 childView[0] = view.getChildAt(index - view.getFirstVisiblePosition());
michael@0 591 }
michael@0 592 });
michael@0 593 }
michael@0 594 });
michael@0 595
michael@0 596 boolean result = waitForCondition(new Condition() {
michael@0 597 @Override
michael@0 598 public boolean isSatisfied() {
michael@0 599 return childView[0] != null;
michael@0 600 }
michael@0 601 }, MAX_WAIT_MS);
michael@0 602
michael@0 603 mAsserter.ok(result, "list item at index " + index + " exists", null);
michael@0 604
michael@0 605 return childView[0];
michael@0 606 }
michael@0 607
michael@0 608 /**
michael@0 609 * Selects the tab at the specified index.
michael@0 610 *
michael@0 611 * @param index Index of tab to select
michael@0 612 */
michael@0 613 public void selectTabAt(final int index) {
michael@0 614 mSolo.clickOnView(getTabViewAt(index));
michael@0 615 }
michael@0 616
michael@0 617 /**
michael@0 618 * Closes the tab at the specified index.
michael@0 619 *
michael@0 620 * @param index Index of tab to close
michael@0 621 */
michael@0 622 public void closeTabAt(final int index) {
michael@0 623 View closeButton = getTabViewAt(index).findViewById(R.id.close);
michael@0 624
michael@0 625 mSolo.clickOnView(closeButton);
michael@0 626 }
michael@0 627
michael@0 628 public final void runOnUiThreadSync(Runnable runnable) {
michael@0 629 RobocopUtils.runOnUiThreadSync(mActivity, runnable);
michael@0 630 }
michael@0 631
michael@0 632 /* Tap the "star" (bookmark) button to bookmark or un-bookmark the current page */
michael@0 633 public void toggleBookmark() {
michael@0 634 mActions.sendSpecialKey(Actions.SpecialKey.MENU);
michael@0 635 waitForText("Settings");
michael@0 636
michael@0 637 // On ICS+ phones, there is no button labeled "Bookmarks"
michael@0 638 // instead we have to just dig through every button on the screen
michael@0 639 ArrayList<View> images = mSolo.getCurrentViews();
michael@0 640 for (int i = 0; i < images.size(); i++) {
michael@0 641 final View view = images.get(i);
michael@0 642 boolean found = false;
michael@0 643 found = "Bookmark".equals(view.getContentDescription());
michael@0 644
michael@0 645 // on older android versions, try looking at the button's text
michael@0 646 if (!found) {
michael@0 647 if (view instanceof TextView) {
michael@0 648 found = "Bookmark".equals(((TextView)view).getText());
michael@0 649 }
michael@0 650 }
michael@0 651
michael@0 652 if (found) {
michael@0 653 int[] xy = new int[2];
michael@0 654 view.getLocationOnScreen(xy);
michael@0 655
michael@0 656 final int viewWidth = view.getWidth();
michael@0 657 final int viewHeight = view.getHeight();
michael@0 658 final float x = xy[0] + (viewWidth / 2.0f);
michael@0 659 float y = xy[1] + (viewHeight / 2.0f);
michael@0 660
michael@0 661 mSolo.clickOnScreen(x, y);
michael@0 662 }
michael@0 663 }
michael@0 664 }
michael@0 665
michael@0 666 public void clearPrivateData() {
michael@0 667 selectSettingsItem(StringHelper.PRIVACY_SECTION_LABEL, StringHelper.CLEAR_PRIVATE_DATA_LABEL);
michael@0 668 Actions.EventExpecter clearData = mActions.expectGeckoEvent("Sanitize:Finished");
michael@0 669 mSolo.clickOnText("Clear data");
michael@0 670 clearData.blockForEvent();
michael@0 671 clearData.unregisterListener();
michael@0 672 }
michael@0 673
michael@0 674 class Device {
michael@0 675 public final String version; // 2.x or 3.x or 4.x
michael@0 676 public String type; // "tablet" or "phone"
michael@0 677 public final int width;
michael@0 678 public final int height;
michael@0 679 public final float density;
michael@0 680
michael@0 681 public Device() {
michael@0 682 // Determine device version
michael@0 683 int sdk = Build.VERSION.SDK_INT;
michael@0 684 if (sdk < Build.VERSION_CODES.HONEYCOMB) {
michael@0 685 version = "2.x";
michael@0 686 } else {
michael@0 687 if (sdk > Build.VERSION_CODES.HONEYCOMB_MR2) {
michael@0 688 version = "4.x";
michael@0 689 } else {
michael@0 690 version = "3.x";
michael@0 691 }
michael@0 692 }
michael@0 693 // Determine with and height
michael@0 694 DisplayMetrics dm = new DisplayMetrics();
michael@0 695 getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
michael@0 696 height = dm.heightPixels;
michael@0 697 width = dm.widthPixels;
michael@0 698 density = dm.density;
michael@0 699 // Determine device type
michael@0 700 type = "phone";
michael@0 701 try {
michael@0 702 if (GeckoAppShell.isTablet()) {
michael@0 703 type = "tablet";
michael@0 704 }
michael@0 705 } catch (Exception e) {
michael@0 706 mAsserter.dumpLog("Exception in detectDevice", e);
michael@0 707 }
michael@0 708 }
michael@0 709
michael@0 710 public void rotate() {
michael@0 711 if (getActivity().getRequestedOrientation () == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
michael@0 712 mSolo.setActivityOrientation(Solo.PORTRAIT);
michael@0 713 } else {
michael@0 714 mSolo.setActivityOrientation(Solo.LANDSCAPE);
michael@0 715 }
michael@0 716 }
michael@0 717 }
michael@0 718
michael@0 719 class Navigation {
michael@0 720 private String devType;
michael@0 721 private String osVersion;
michael@0 722
michael@0 723 public Navigation(Device mDevice) {
michael@0 724 devType = mDevice.type;
michael@0 725 osVersion = mDevice.version;
michael@0 726 }
michael@0 727
michael@0 728 public void back() {
michael@0 729 Actions.EventExpecter pageShowExpecter = mActions.expectGeckoEvent("Content:PageShow");
michael@0 730
michael@0 731 if (devType.equals("tablet")) {
michael@0 732 Element backBtn = mDriver.findElement(getActivity(), R.id.back);
michael@0 733 backBtn.click();
michael@0 734 } else {
michael@0 735 mActions.sendSpecialKey(Actions.SpecialKey.BACK);
michael@0 736 }
michael@0 737
michael@0 738 pageShowExpecter.blockForEvent();
michael@0 739 pageShowExpecter.unregisterListener();
michael@0 740 }
michael@0 741
michael@0 742 public void forward() {
michael@0 743 Actions.EventExpecter pageShowExpecter = mActions.expectGeckoEvent("Content:PageShow");
michael@0 744
michael@0 745 if (devType.equals("tablet")) {
michael@0 746 Element fwdBtn = mDriver.findElement(getActivity(), R.id.forward);
michael@0 747 fwdBtn.click();
michael@0 748 } else {
michael@0 749 mActions.sendSpecialKey(Actions.SpecialKey.MENU);
michael@0 750 waitForText("^New Tab$");
michael@0 751 if (!osVersion.equals("2.x")) {
michael@0 752 Element fwdBtn = mDriver.findElement(getActivity(), R.id.forward);
michael@0 753 fwdBtn.click();
michael@0 754 } else {
michael@0 755 mSolo.clickOnText("^Forward$");
michael@0 756 }
michael@0 757 ensureMenuClosed();
michael@0 758 }
michael@0 759
michael@0 760 pageShowExpecter.blockForEvent();
michael@0 761 pageShowExpecter.unregisterListener();
michael@0 762 }
michael@0 763
michael@0 764 public void reload() {
michael@0 765 if (devType.equals("tablet")) {
michael@0 766 Element reloadBtn = mDriver.findElement(getActivity(), R.id.reload);
michael@0 767 reloadBtn.click();
michael@0 768 } else {
michael@0 769 mActions.sendSpecialKey(Actions.SpecialKey.MENU);
michael@0 770 waitForText("^New Tab$");
michael@0 771 if (!osVersion.equals("2.x")) {
michael@0 772 Element reloadBtn = mDriver.findElement(getActivity(), R.id.reload);
michael@0 773 reloadBtn.click();
michael@0 774 } else {
michael@0 775 mSolo.clickOnText("^Reload$");
michael@0 776 }
michael@0 777 ensureMenuClosed();
michael@0 778 }
michael@0 779 }
michael@0 780
michael@0 781 // DEPRECATED!
michael@0 782 // Use BaseTest.toggleBookmark() in new code.
michael@0 783 public void bookmark() {
michael@0 784 mActions.sendSpecialKey(Actions.SpecialKey.MENU);
michael@0 785 waitForText("^New Tab$");
michael@0 786 if (mSolo.searchText("^Bookmark$")) {
michael@0 787 // This is the Android 2.x so the button has text
michael@0 788 mSolo.clickOnText("^Bookmark$");
michael@0 789 } else {
michael@0 790 Element bookmarkBtn = mDriver.findElement(getActivity(), R.id.bookmark);
michael@0 791 if (bookmarkBtn != null) {
michael@0 792 // We are on Android 4.x so the button is an image button
michael@0 793 bookmarkBtn.click();
michael@0 794 }
michael@0 795 }
michael@0 796 ensureMenuClosed();
michael@0 797 }
michael@0 798
michael@0 799 // On some devices, the menu may not be dismissed after clicking on an
michael@0 800 // item. Close it here.
michael@0 801 private void ensureMenuClosed() {
michael@0 802 if (mSolo.searchText("^New Tab$")) {
michael@0 803 mActions.sendSpecialKey(Actions.SpecialKey.BACK);
michael@0 804 }
michael@0 805 }
michael@0 806 }
michael@0 807
michael@0 808 /**
michael@0 809 * Gets the string representation of a stack trace.
michael@0 810 *
michael@0 811 * @param t Throwable to get stack trace for
michael@0 812 * @return Stack trace as a string
michael@0 813 */
michael@0 814 public static String getStackTraceString(Throwable t) {
michael@0 815 StringWriter sw = new StringWriter();
michael@0 816 t.printStackTrace(new PrintWriter(sw));
michael@0 817 return sw.toString();
michael@0 818 }
michael@0 819
michael@0 820 /**
michael@0 821 * Condition class that waits for a view, and allows callers access it when done.
michael@0 822 */
michael@0 823 private class DescriptionCondition<T extends View> implements Condition {
michael@0 824 public T mView;
michael@0 825 private String mDescr;
michael@0 826 private Class<T> mCls;
michael@0 827
michael@0 828 public DescriptionCondition(Class<T> cls, String descr) {
michael@0 829 mDescr = descr;
michael@0 830 mCls = cls;
michael@0 831 }
michael@0 832
michael@0 833 @Override
michael@0 834 public boolean isSatisfied() {
michael@0 835 mView = findViewWithContentDescription(mCls, mDescr);
michael@0 836 return (mView != null);
michael@0 837 }
michael@0 838 }
michael@0 839
michael@0 840 /**
michael@0 841 * Wait for a view with the specified description .
michael@0 842 */
michael@0 843 public <T extends View> T waitForViewWithDescription(Class<T> cls, String description) {
michael@0 844 DescriptionCondition<T> c = new DescriptionCondition<T>(cls, description);
michael@0 845 waitForCondition(c, MAX_WAIT_ENABLED_TEXT_MS);
michael@0 846 return c.mView;
michael@0 847 }
michael@0 848
michael@0 849 /**
michael@0 850 * Get an active view with the specified description .
michael@0 851 */
michael@0 852 public <T extends View> T findViewWithContentDescription(Class<T> cls, String description) {
michael@0 853 for (T view : mSolo.getCurrentViews(cls)) {
michael@0 854 final String descr = (String) view.getContentDescription();
michael@0 855 if (TextUtils.isEmpty(descr)) {
michael@0 856 continue;
michael@0 857 }
michael@0 858
michael@0 859 if (TextUtils.equals(description, descr)) {
michael@0 860 return view;
michael@0 861 }
michael@0 862 }
michael@0 863
michael@0 864 return null;
michael@0 865 }
michael@0 866
michael@0 867 /**
michael@0 868 * Abstract class for running small test cases within a BaseTest.
michael@0 869 */
michael@0 870 abstract class TestCase implements Runnable {
michael@0 871 /**
michael@0 872 * Implement tests here. setUp and tearDown for the test case
michael@0 873 * should be handled by the parent test. This is so we can avoid the
michael@0 874 * overhead of starting Gecko and creating profiles.
michael@0 875 */
michael@0 876 protected abstract void test() throws Exception;
michael@0 877
michael@0 878 @Override
michael@0 879 public void run() {
michael@0 880 try {
michael@0 881 test();
michael@0 882 } catch (Exception e) {
michael@0 883 mAsserter.ok(false,
michael@0 884 "Test " + this.getClass().getName() + " threw exception: " + e,
michael@0 885 "");
michael@0 886 }
michael@0 887 }
michael@0 888 }
michael@0 889
michael@0 890 /**
michael@0 891 * Set the preference and wait for it to change before proceeding with the test.
michael@0 892 */
michael@0 893 public void setPreferenceAndWaitForChange(final JSONObject jsonPref) {
michael@0 894 mActions.sendGeckoEvent("Preferences:Set", jsonPref.toString());
michael@0 895
michael@0 896 // Get the preference name from the json and store it in an array. This array
michael@0 897 // will be used later while fetching the preference data.
michael@0 898 String[] prefNames = new String[1];
michael@0 899 try {
michael@0 900 prefNames[0] = jsonPref.getString("name");
michael@0 901 } catch (JSONException e) {
michael@0 902 mAsserter.ok(false, "Exception in setPreferenceAndWaitForChange", getStackTraceString(e));
michael@0 903 }
michael@0 904
michael@0 905 // Wait for confirmation of the pref change before proceeding with the test.
michael@0 906 final int ourRequestID = mPreferenceRequestID--;
michael@0 907 final Actions.RepeatedEventExpecter eventExpecter = mActions.expectGeckoEvent("Preferences:Data");
michael@0 908 mActions.sendPreferencesGetEvent(ourRequestID, prefNames);
michael@0 909
michael@0 910 // Wait until we get the correct "Preferences:Data" event
michael@0 911 waitForCondition(new Condition() {
michael@0 912 final long endTime = SystemClock.elapsedRealtime() + MAX_WAIT_BLOCK_FOR_EVENT_DATA_MS;
michael@0 913
michael@0 914 @Override
michael@0 915 public boolean isSatisfied() {
michael@0 916 try {
michael@0 917 long timeout = endTime - SystemClock.elapsedRealtime();
michael@0 918 if (timeout < 0) {
michael@0 919 timeout = 0;
michael@0 920 }
michael@0 921
michael@0 922 JSONObject data = new JSONObject(eventExpecter.blockForEventDataWithTimeout(timeout));
michael@0 923 int requestID = data.getInt("requestId");
michael@0 924 if (requestID != ourRequestID) {
michael@0 925 return false;
michael@0 926 }
michael@0 927
michael@0 928 JSONArray preferences = data.getJSONArray("preferences");
michael@0 929 mAsserter.is(preferences.length(), 1, "Expecting preference array to have one element");
michael@0 930 JSONObject prefs = (JSONObject) preferences.get(0);
michael@0 931 mAsserter.is(prefs.getString("name"), jsonPref.getString("name"),
michael@0 932 "Expecting returned preference name to be the same as the set name");
michael@0 933 mAsserter.is(prefs.getString("type"), jsonPref.getString("type"),
michael@0 934 "Expecting returned preference type to be the same as the set type");
michael@0 935 mAsserter.is(prefs.get("value"), jsonPref.get("value"),
michael@0 936 "Expecting returned preference value to be the same as the set value");
michael@0 937 return true;
michael@0 938 } catch(JSONException e) {
michael@0 939 mAsserter.ok(false, "Exception in setPreferenceAndWaitForChange", getStackTraceString(e));
michael@0 940 // Please the java compiler
michael@0 941 return false;
michael@0 942 }
michael@0 943 }
michael@0 944 }, MAX_WAIT_BLOCK_FOR_EVENT_DATA_MS);
michael@0 945
michael@0 946 eventExpecter.unregisterListener();
michael@0 947 }
michael@0 948 }

mercurial