Wed, 31 Dec 2014 06:09:35 +0100
Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.
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 | } |