gecko/mobile/android/base/tests/BaseTest.java.in
Chris Peterson c6764e29f4 Bug 855146 - Part 2: Extract runOnUiThreadSync() test method. r=gbrown
--HG--
extra : rebase_source : 8014e5438596d8981e074ad847a675bacb18eb2c
2013-03-21 18:43:04 -07:00

765 lines
28 KiB
Java

#filter substitution
package @ANDROID_PACKAGE_NAME@.tests;
import com.jayway.android.robotium.solo.Solo;
import @ANDROID_PACKAGE_NAME@.*;
import android.app.Activity;
import android.app.Instrumentation;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.ContentUris;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.res.AssetManager;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.os.SystemClock;
import android.test.ActivityInstrumentationTestCase2;
import android.util.DisplayMetrics;
import android.view.View;
import android.widget.ListView;
import java.io.File;
import java.io.InputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
/**
* A convenient base class suitable for most Robocop tests.
*/
abstract class BaseTest extends ActivityInstrumentationTestCase2<Activity> {
public static final int TEST_MOCHITEST = 0;
public static final int TEST_TALOS = 1;
private static final String TARGET_PACKAGE_ID = "org.mozilla.gecko";
private static final String LAUNCH_ACTIVITY_FULL_CLASSNAME="@ANDROID_PACKAGE_NAME@.App";
private static final int VERIFY_URL_TIMEOUT = 2000;
private static final int MAX_LIST_ATTEMPTS = 3;
public static final int MAX_WAIT_MS = 3000;
private static Class<Activity> mLauncherActivityClass;
private Activity mActivity;
protected Solo mSolo;
protected Driver mDriver;
protected Assert mAsserter;
protected Actions mActions;
protected String mBaseUrl;
protected String mRawBaseUrl;
private String mLogFile;
protected String mProfile;
protected void blockForGeckoReady() {
try {
Actions.EventExpecter geckoReadyExpector = mActions.expectGeckoEvent("Gecko:Ready");
ClassLoader classLoader = getActivity().getClassLoader();
Class appsCls = classLoader.loadClass("org.mozilla.gecko.GeckoThread");
Class launchStateCls = classLoader.loadClass("org.mozilla.gecko.GeckoThread$LaunchState");
Method checkLaunchState = appsCls.getMethod("checkLaunchState", launchStateCls);
Object states[] = launchStateCls.getEnumConstants();
Boolean ret = (Boolean)checkLaunchState.invoke(null, states[3]);
if (!ret.booleanValue()) {
geckoReadyExpector.blockForEvent();
}
} catch (Exception e) {
mAsserter.dumpLog("Exception in blockForGeckoReady", e);
}
}
static {
try {
mLauncherActivityClass = (Class<Activity>)Class.forName(LAUNCH_ACTIVITY_FULL_CLASSNAME);
} catch (ClassNotFoundException e) {
throw new RuntimeException(e);
}
}
public BaseTest() {
super(TARGET_PACKAGE_ID, mLauncherActivityClass);
}
protected abstract int getTestType();
@Override
protected void setUp() throws Exception {
// Load config file from root path (setup by python script)
String rootPath = FennecInstrumentationTestRunner.getArguments().getString("deviceroot");
String configFile = FennecNativeDriver.getFile(rootPath + "/robotium.config");
HashMap config = FennecNativeDriver.convertTextToTable(configFile);
// Create the intent to be used with all the important arguments.
Intent i = new Intent(Intent.ACTION_MAIN);
mProfile = (String)config.get("profile");
i.putExtra("args", "-no-remote -profile " + mProfile);
String envString = (String)config.get("envvars");
if (envString != "") {
String[] envStrings = envString.split(",");
for (int iter = 0; iter < envStrings.length; iter++) {
i.putExtra("env" + iter, envStrings[iter]);
}
}
// Start the activity
setActivityIntent(i);
mActivity = getActivity();
mLogFile = (String)config.get("logfile");
mBaseUrl = ((String)config.get("host")).replaceAll("(/$)", "");
mRawBaseUrl = ((String)config.get("rawhost")).replaceAll("(/$)", "");
// Initialize the asserter
if (getTestType() == TEST_TALOS) {
mAsserter = new FennecTalosAssert();
} else {
mAsserter = new FennecMochitestAssert();
}
mAsserter.setLogFile(mLogFile);
mAsserter.setTestName(this.getClass().getName());
// Set up Robotium.solo and Driver objects
mSolo = new Solo(getInstrumentation(), mActivity);
mDriver = new FennecNativeDriver(mActivity, mSolo, rootPath);
mActions = new FennecNativeActions(mActivity, mSolo, getInstrumentation(), mAsserter);
}
@Override
protected void runTest() throws Throwable {
try {
super.runTest();
} catch (Throwable t) {
// save screenshot -- written to /mnt/sdcard/Robotium-Screenshots
// as <filename>.jpg
mSolo.takeScreenshot("robocop-screenshot");
if (mAsserter != null) {
mAsserter.dumpLog("Exception caught during test!", t);
mAsserter.ok(false, "Exception caught", t.toString());
}
// re-throw to continue bail-out
throw t;
}
}
@Override
public void tearDown() throws Exception {
try {
mAsserter.endTest();
mSolo.finishOpenedActivities();
} catch (Throwable e) {
e.printStackTrace();
}
super.tearDown();
}
public void assertMatches(String value, String regex, String name) {
if (value == null) {
mAsserter.ok(false, name, "Expected /" + regex + "/, got null");
return;
}
mAsserter.ok(value.matches(regex), name, "Expected /" + regex +"/, got \"" + value + "\"");
}
/**
* Click on the specified element and return the resulting activity.
* @return The created activity, or null if the element cannot be clicked.
*/
protected final Activity getActivityFromClick(Element element) {
Instrumentation inst = getInstrumentation();
Instrumentation.ActivityMonitor monitor = inst.addMonitor((String)null, null, false);
boolean clicked = element.click();
if (!clicked) {
mAsserter.ok(clicked != false, "checking that awesome bar clicked", "awesome bar was clicked");
return null;
}
// Wait for click to take effect before waiting for activity
// (otherwise we sometimes get the previous activity).
// Previously, waitForIdleSync was used here but it was found
// to hang very occasionally.
mSolo.sleep(2000);
Activity activity = inst.waitForMonitor(monitor);
// Give the activity time to render itself and initialize views
// before continuing, so that views are created before access
// attempts are made. Again, waitForIdleSync was used here
// previously, but replaced with a sleep to avoid hangs.
// TODO: Investigate and document why these pauses are required.
mSolo.sleep(2000);
return activity;
}
/**
* Click on the awesome bar element and return the resulting activity.
* @return The created activity, or null if the awesome bar cannot be clicked.
*/
protected final Activity clickOnAwesomeBar() {
Activity activity = null;
Element awesomebar = mDriver.findElement(mActivity, "awesome_bar");
if (awesomebar != null) {
activity = getActivityFromClick(awesomebar);
if (activity == null) {
mAsserter.dumpLog("failed to click on awesome bar!");
}
}
return activity;
}
protected final void enterUrl(String url) {
Activity awesomeBarActivity = clickOnAwesomeBar();
Element urlbar = mDriver.findElement(awesomeBarActivity, "awesomebar_text");
mActions.sendKeys(url);
String urlbarText = null;
if (urlbar != null) {
urlbarText = urlbar.getText();
}
mAsserter.is(urlbarText, url, "Awesomebar URL typed properly");
}
protected final void hitEnterAndWait() {
Actions.EventExpecter contentEventExpecter = mActions.expectGeckoEvent("DOMContentLoaded");
mActions.sendSpecialKey(Actions.SpecialKey.ENTER);
// wait for screen to load
contentEventExpecter.blockForEvent();
}
protected final void loadUrl(String url) {
enterUrl(url);
hitEnterAndWait();
}
public final void verifyUrl(String url) {
Activity awesomeBarActivity = clickOnAwesomeBar();
Element urlbar = mDriver.findElement(awesomeBarActivity, "awesomebar_text");
String urlbarText = null;
if (urlbar != null) {
// wait for a short time for the expected text, in case there is a delay
// in updating the view
waitForTest(new VerifyUrlTest(urlbar, url), VERIFY_URL_TIMEOUT);
urlbarText = urlbar.getText();
}
mAsserter.is(urlbarText, url, "Awesomebar URL stayed the same");
}
class VerifyUrlTest implements BooleanTest {
private Element mUrlbar;
private String mUrl;
public VerifyUrlTest(Element urlbar, String url) {
mUrlbar = urlbar;
mUrl = url;
}
@Override
public boolean test() {
String urlbarText = mUrlbar.getText();
if (urlbarText.equals(mUrl)) {
return true;
}
return false;
}
}
protected final String getAbsoluteUrl(String url) {
return mBaseUrl + "/" + url.replaceAll("(^/)", "");
}
protected final String getAbsoluteRawUrl(String url) {
return mRawBaseUrl + "/" + url.replaceAll("(^/)", "");
}
protected final boolean waitForTest(BooleanTest t, int timeout) {
long end = SystemClock.uptimeMillis() + timeout;
while (SystemClock.uptimeMillis() < end) {
if (t.test()) {
return true;
}
mSolo.sleep(100);
}
// log out wait failure for diagnostic purposes only;
// a failed wait may be normal and does not necessarily
// warrant a test assertion/failure
mAsserter.dumpLog("waitForTest timeout after "+timeout+" ms");
return false;
}
protected interface BooleanTest {
public boolean test();
}
@SuppressWarnings({"unchecked", "non-varargs"})
public void SqliteCompare(String dbName, String sqlCommand, ContentValues[] cvs) {
File profile = new File(mProfile);
String dbPath = new File(profile, dbName).getPath();
Cursor c = mActions.querySql(dbPath, sqlCommand);
SqliteCompare(c, cvs);
}
private boolean CursorMatches(Cursor c, String[] columns, ContentValues cv) {
for (int i = 0; i < columns.length; i++) {
String column = columns[i];
if (cv.containsKey(column)) {
mAsserter.info("Comparing", "Column values for: " + column);
Object value = cv.get(column);
if (value == null) {
if (!c.isNull(i)) {
return false;
}
} else {
if (c.isNull(i) || !value.toString().equals(c.getString(i))) {
return false;
}
}
}
}
return true;
}
@SuppressWarnings({"unchecked", "non-varargs"})
public void SqliteCompare(Cursor c, ContentValues[] cvs) {
mAsserter.is(c.getCount(), cvs.length, "List is correct length");
if (c.moveToFirst()) {
do {
boolean found = false;
for (int i = 0; !found && i < cvs.length; i++) {
if (CursorMatches(c, cvs[i])) {
found = true;
}
}
mAsserter.is(found, true, "Password was found");
} while(c.moveToNext());
}
}
public boolean CursorMatches(Cursor c, ContentValues cv) {
for (int i = 0; i < c.getColumnCount(); i++) {
String column = c.getColumnName(i);
if (cv.containsKey(column)) {
mAsserter.info("Comparing", "Column values for: " + column);
Object value = cv.get(column);
if (value == null) {
if (!c.isNull(i)) {
return false;
}
} else {
if (c.isNull(i) || !value.toString().equals(c.getString(i))) {
return false;
}
}
}
}
return true;
}
public InputStream getAsset(String filename) throws IOException {
AssetManager assets = getInstrumentation().getContext().getAssets();
return assets.open(filename);
}
public boolean waitForText(String text) {
boolean rc = mSolo.waitForText(text);
if (!rc) {
// log out failed wait for diagnostic purposes only;
// waitForText failures are sometimes expected/normal
mAsserter.dumpLog("waitForText timeout on "+text);
}
return rc;
}
public final void selectMenuItem(String menuItemName) {
// build the item name ready to be used
String itemName = "^" + menuItemName + "$";
mActions.sendSpecialKey(Actions.SpecialKey.MENU);
if (waitForText(itemName)) {
mSolo.clickOnText(itemName);
} else {
if (mSolo.searchText("(^More$|^Tools$)")) {
mSolo.clickOnText("(^More$|^Tools$)");
}
waitForText(itemName);
mSolo.clickOnText(itemName);
}
}
public final void verifyPageTitle(String title) {
Element awesomebar = mDriver.findElement(getActivity(), "awesome_bar_title");
String pageTitle = null;
if (awesomebar != null) {
// Wait for the title to make sure it has been displayed in case the view
// does not update fast enough
waitForTest(new VerifyTitle(awesomebar, title), MAX_WAIT_MS);
pageTitle = awesomebar.getText();
}
mAsserter.is(pageTitle, title, "Page title is correct");
}
class VerifyTitle implements BooleanTest {
private Element mAwesomebar;
private String mTitle;
public VerifyTitle(Element awesomebar, String title) {
mAwesomebar = awesomebar;
mTitle = title;
}
@Override
public boolean test() {
String pageTitle = mAwesomebar.getText();
if (pageTitle.equals(mTitle)) {
return true;
}
return false;
}
}
public final void verifyTabCount(int expectedTabCount) {
Activity activity = getActivity();
Element tabCount = mDriver.findElement(activity, "tabs_count");
String tabCountText = tabCount.getText();
int tabCountInt = Integer.parseInt(tabCountText);
mAsserter.is(tabCountInt, expectedTabCount, "The correct number of tabs are opened");
}
private ListView getAwesomeList(String waitText, int expectedChildCount,
String clickText, String tagName, String callerName) {
ArrayList<ListView> views;
ListView tabView = null;
int childCount = 0;
for (int i = 0; i < MAX_LIST_ATTEMPTS; i++) {
tabView = null;
childCount = 0;
Activity awesomeBarActivity = clickOnAwesomeBar();
mSolo.clickOnText(clickText);
if (waitForText(waitText)) {
views = mSolo.getCurrentListViews();
for (ListView view : views) {
if (view.getTag().equals(tagName)) {
tabView = view;
childCount = view.getAdapter().getCount();
if (expectedChildCount < 0 || expectedChildCount == childCount) {
return view;
}
}
}
}
}
if (tabView == null) {
mAsserter.dumpLog(callerName+" did not find ListView");
} else if (expectedChildCount >= 0) {
mAsserter.dumpLog(callerName+" found ListView with "+childCount+" children");
}
return null;
}
/**
* Click on the awesome bar, click on the Top Sites tab, and return
* the ListView for the All Pages ("Top Sites") tab.
*/
protected ListView getAllPagesList(String waitText, int expectedChildCount) {
return getAwesomeList(waitText, expectedChildCount,
"Top Sites", "allPages", "getAllPagesList");
}
protected ListView getAllPagesList(String waitText) {
return getAllPagesList(waitText, -1);
}
/**
* Click on the awesome bar, click on the Bookmarks tab, and return
* the ListView for the Bookmarks tab.
*/
protected ListView getBookmarksList(String waitText, int expectedChildCount) {
return getAwesomeList(waitText, expectedChildCount,
"Bookmarks", "bookmarks", "getBookmarksList");
}
protected ListView getBookmarksList(String waitText) {
return getBookmarksList(waitText, -1);
}
/**
* Click on the awesome bar, click on the History tab, and return
* the ListView for the History tab.
*/
protected ListView getHistoryList(String waitText, int expectedChildCount) {
return getAwesomeList(waitText, expectedChildCount,
"History", "history", "getHistoryList");
}
protected ListView getHistoryList(String waitText) {
return getHistoryList(waitText, -1);
}
public long addOrUpdateBookmark(String title, String url, boolean bookmarklet) {
ContentResolver resolver = getActivity().getContentResolver();
Uri bookmarksUri = Uri.parse("content://@ANDROID_PACKAGE_NAME@.db.browser/bookmarks");
bookmarksUri = bookmarksUri.buildUpon().appendQueryParameter("profile", "default").build();
long folderId = -1;
Cursor c = null;
// NOTE: this is hardcoded to toolbar/mobile and triggered from bookmarklet because the existing
// testcases were written this way
String location = "toolbar";
if (bookmarklet) {
location = "mobile";
}
try {
c = resolver.query(bookmarksUri,
new String[] { "_id" },
"guid = ?",
new String[] { location },
null);
if (c.moveToFirst()) {
folderId = c.getLong(c.getColumnIndexOrThrow("_id"));
}
} finally {
if (c != null) {
c.close();
}
}
ContentValues values = new ContentValues();
values.put("title", title);
values.put("url", url);
values.put("parent", folderId);
long now = System.currentTimeMillis();
values.put("modified", now);
if (!bookmarklet) {
values.put("type", 1);
values.put("guid", url);
values.put("position", 10);
values.put("created", now);
}
int updated = resolver.update(bookmarksUri,
values,
"url = ?",
new String[] { url });
if (updated == 0) {
Uri uri = resolver.insert(bookmarksUri, values);
mAsserter.ok(true, "Inserted at: ", uri.toString());
return ContentUris.parseId(uri);
}
return ContentUris.parseId(bookmarksUri);
}
public void deleteBookmark(String title) {
ContentResolver resolver = getActivity().getContentResolver();
Uri uri = Uri.parse("content://@ANDROID_PACKAGE_NAME@.db.browser/bookmarks");
uri = uri.buildUpon().appendQueryParameter("profile", "default")
.appendQueryParameter("sync", "true").build();
resolver.delete(uri, "title = ?", new String[] { title });
}
public void addTab(String url) {
Element tabs = null;
Element addTab = null;
Activity activity = getActivity();
tabs = mDriver.findElement(activity, "tabs");
addTab = mDriver.findElement(activity, "add_tab");
final int addTabId = addTab.getId();
mAsserter.ok(tabs.click(), "checking that tabs clicked", "tabs element clicked");
// wait for addTab to appear (this is usually immediate)
boolean success = waitForTest(new BooleanTest() {
@Override
public boolean test() {
View addTabView = getActivity().findViewById(addTabId);
if (addTabView == null) {
return false;
}
return true;
}
}, MAX_WAIT_MS);
mAsserter.ok(success, "waiting for add tab view", "add tab view available");
mAsserter.ok(addTab.click(), "checking that add_tab clicked", "add_tab element clicked");
// must pause before sending keys, until awesome bar is displayed; waiting for known text is simple
waitForText("History");
// cannot use loadUrl(): getText fails because we are using a different urlbar
mActions.sendKeys(url);
hitEnterAndWait();
}
public final void runOnUiThreadSync(Runnable runnable) {
RobocopUtils.runOnUiThreadSync(mActivity, runnable);
}
/**
* This method will edit the bookmark with index = bookmarkIndex from the list of bookmarks
* For the field index:
* fieldIndex = 1 - the Bookmark name
* fieldIndex = 2 - the Bookmark url
* fieldIndex = 3 - the Bookmark keyword
*/
public void editBookmark(int bookmarkIndex, int fieldIndex, String addedText, ListView list) {
// Open the Edit Bookmark context menu
View child;
mSolo.clickOnText("Bookmarks");
child = list.getChildAt(bookmarkIndex);
mAsserter.ok(child != null, "edit item can be retrieved", child != null ? child.toString() : "null!");
waitForText("about:home|Firefox");
mSolo.clickLongOnView(child);
waitForText("Open in New Tab");
mSolo.clickOnText("Edit");
waitForText("Edit Bookmark");
// Clear the Field
mSolo.clearEditText(fieldIndex);
// Enter the new text
mSolo.clickOnEditText(fieldIndex);
mActions.sendKeys(addedText);
mSolo.clickOnText("OK");
waitForText("Bookmark updated");
mActions.sendSpecialKey(Actions.SpecialKey.BACK); // close the VKB
}
public boolean checkBookmarkEdit(int bookmarkIndex, String addedText, ListView list) {
Device mDevice = new Device();
// Open the Edit Bookmark context menu
View child;
mSolo.clickOnText("Bookmarks");
child = list.getChildAt(bookmarkIndex);
mAsserter.ok(child != null, "check item can be retrieved", child != null ? child.toString() : "null!");
waitForText("about:home|Firefox");
mSolo.clickLongOnView(child);
waitForText("Open in New Tab");
mSolo.clickOnText("Edit");
waitForText("Edit Bookmark");
// If the OS is not Gingerbread the vkb will be opened so we need to close it in order to press the "Cancel" button
if (!(mDevice.version.equals("2.x"))) {
mActions.sendSpecialKey(Actions.SpecialKey.BACK); // close the VKB
}
// Check if the new text was added
if (mSolo.searchText(addedText)) {
mSolo.clickOnText("Cancel");
waitForText("about:home");
mActions.sendSpecialKey(Actions.SpecialKey.BACK); // close the VKB
return true;
} else {
mSolo.clickOnText("Cancel");
waitForText("about:home");
mActions.sendSpecialKey(Actions.SpecialKey.BACK); // close the VKB
return false;
}
}
class Device {
Build.VERSION mBuildVersion;
public String version; // 2.x or 3.x or 4.x
public String type; // "tablet" or "phone"
public int width;
public int height;
public Device() {
detectDevice();
}
private void detectDevice() {
// Determine device version
mBuildVersion = new Build.VERSION();
int mSDK = mBuildVersion.SDK_INT;
if (mSDK < Build.VERSION_CODES.HONEYCOMB) {
version = "2.x";
} else {
if (mSDK > Build.VERSION_CODES.HONEYCOMB_MR2) {
version = "4.x";
} else {
version = "3.x";
}
}
// Determine with and height
DisplayMetrics dm = new DisplayMetrics();
getActivity().getWindowManager().getDefaultDisplay().getMetrics(dm);
height = dm.heightPixels;
width = dm.widthPixels;
// Determine device type
type = "phone";
try {
ClassLoader classLoader = getActivity().getClassLoader();
Class appsCls = classLoader.loadClass("org.mozilla.gecko.GeckoAppShell");
Method isTabletMethod = appsCls.getMethod("isTablet", (Class[]) null);
boolean isTablet = (Boolean)isTabletMethod.invoke(null);
if (isTablet) {
type = "tablet";
}
} catch (Exception e) {
mAsserter.dumpLog("Exception in detectDevice", e);
}
}
public void rotate() {
if (getActivity().getRequestedOrientation () == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {
mSolo.setActivityOrientation(Solo.PORTRAIT);
} else {
mSolo.setActivityOrientation(Solo.LANDSCAPE);
}
}
}
class Navigation {
private String devType;
private String osVersion;
public Navigation(Device mDevice) {
devType = mDevice.type;
osVersion = mDevice.version;
}
public void back() {
if (devType.equals("tablet")) {
Element backBtn = mDriver.findElement(getActivity(), "back");
backBtn.click();
} else {
mActions.sendSpecialKey(Actions.SpecialKey.BACK);
}
}
public void forward() {
if (devType.equals("tablet")) {
Element fwdBtn = mDriver.findElement(getActivity(), "forward");
fwdBtn.click();
} else {
mActions.sendSpecialKey(Actions.SpecialKey.MENU);
waitForText("^New Tab$");
if (!osVersion.equals("2.x")) {
Element fwdBtn = mDriver.findElement(getActivity(), "forward");
fwdBtn.click();
} else {
mSolo.clickOnText("^Forward$");
}
}
}
public void reload() {
if (devType.equals("tablet")) {
Element reloadBtn = mDriver.findElement(getActivity(), "reload");
reloadBtn.click();
} else {
mActions.sendSpecialKey(Actions.SpecialKey.MENU);
waitForText("^New Tab$");
if (!osVersion.equals("2.x")) {
Element reloadBtn = mDriver.findElement(getActivity(), "reload");
reloadBtn.click();
} else {
mSolo.clickOnText("^Reload$");
}
}
}
public void bookmark() {
if (devType.equals("tablet")) {
if (!osVersion.equals("4.x")){
Element bookmarkBtn = mDriver.findElement(getActivity(), "bookmark");
bookmarkBtn.click();
}
else {
mActions.sendSpecialKey(Actions.SpecialKey.MENU);
mSolo.waitForText("^New Tab$");
mSolo.clickOnText("^Bookmark$");
}
}
else {
mActions.sendSpecialKey(Actions.SpecialKey.MENU);
mSolo.waitForText("^New Tab$");
if (!osVersion.equals("2.x")) {
Element bookmarkBtn = mDriver.findElement(getActivity(), "bookmark");
bookmarkBtn.click();
}
else {
mSolo.clickOnText("^Bookmark$");
}
}
}
}
}