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.

     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 }

mercurial