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