This adds a flag to |mach robocop| that does everything to run a Robocop test except launch the actual test. Instead of launching the test, it starts the mochi.test server and launches Fennec with a test profile; then it sits and waits forever. This allows regular Java IDEs (IntelliJ, but previously Eclipse) to run Robocop tests like regular instrumentation tests, "injecting" them into the prepared testing environment. It's quite nice!
257 lines
9.7 KiB
Java
257 lines
9.7 KiB
Java
/* This Source Code Form is subject to the terms of the Mozilla Public
|
|
* License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
|
|
|
|
package org.mozilla.gecko.tests;
|
|
|
|
import android.app.Activity;
|
|
import android.content.Context;
|
|
import android.content.Intent;
|
|
import android.os.PowerManager;
|
|
import android.test.ActivityInstrumentationTestCase2;
|
|
import android.text.TextUtils;
|
|
import android.util.Log;
|
|
import com.jayway.android.robotium.solo.Solo;
|
|
import org.apache.http.HttpResponse;
|
|
import org.apache.http.client.HttpClient;
|
|
import org.apache.http.client.methods.HttpGet;
|
|
import org.apache.http.impl.client.DefaultHttpClient;
|
|
import org.mozilla.gecko.Actions;
|
|
import org.mozilla.gecko.AppConstants;
|
|
import org.mozilla.gecko.Assert;
|
|
import org.mozilla.gecko.Driver;
|
|
import org.mozilla.gecko.FennecInstrumentationTestRunner;
|
|
import org.mozilla.gecko.FennecMochitestAssert;
|
|
import org.mozilla.gecko.FennecNativeActions;
|
|
import org.mozilla.gecko.FennecNativeDriver;
|
|
import org.mozilla.gecko.FennecTalosAssert;
|
|
import org.mozilla.gecko.GeckoAppShell;
|
|
import org.mozilla.gecko.GeckoEvent;
|
|
import org.mozilla.gecko.updater.UpdateServiceHelper;
|
|
|
|
import java.util.Map;
|
|
|
|
@SuppressWarnings("unchecked")
|
|
public abstract class BaseRobocopTest extends ActivityInstrumentationTestCase2<Activity> {
|
|
public static final String LOGTAG = "BaseTest";
|
|
|
|
public enum Type {
|
|
MOCHITEST,
|
|
TALOS
|
|
}
|
|
|
|
public static final String DEFAULT_ROOT_PATH = "/mnt/sdcard/tests";
|
|
|
|
// How long to wait for a Robocop:Quit message to actually kill Fennec.
|
|
private static final int ROBOCOP_QUIT_WAIT_MS = 180000;
|
|
|
|
/**
|
|
* The Java Class instance that launches the browser.
|
|
* <p>
|
|
* This should always agree with {@link AppConstants#MOZ_ANDROID_BROWSER_INTENT_CLASS}.
|
|
*/
|
|
public static final Class<? extends Activity> BROWSER_INTENT_CLASS;
|
|
|
|
// Use reflection here so we don't have to preprocess this file.
|
|
static {
|
|
Class<? extends Activity> cl;
|
|
try {
|
|
cl = (Class<? extends Activity>) Class.forName(AppConstants.MOZ_ANDROID_BROWSER_INTENT_CLASS);
|
|
} catch (ClassNotFoundException e) {
|
|
// Oh well.
|
|
cl = Activity.class;
|
|
}
|
|
BROWSER_INTENT_CLASS = cl;
|
|
}
|
|
|
|
protected Assert mAsserter;
|
|
protected String mLogFile;
|
|
|
|
protected String mBaseHostnameUrl;
|
|
protected String mBaseIpUrl;
|
|
|
|
protected Map<String, String> mConfig;
|
|
protected String mRootPath;
|
|
|
|
protected Solo mSolo;
|
|
protected Driver mDriver;
|
|
protected Actions mActions;
|
|
|
|
protected String mProfile;
|
|
|
|
protected StringHelper mStringHelper;
|
|
|
|
/**
|
|
* The browser is started at the beginning of this test. A single test is a
|
|
* class inheriting from <code>BaseRobocopTest</code> that contains test
|
|
* methods.
|
|
* <p>
|
|
* If a test should not start the browser at the beginning of a test,
|
|
* specify a different activity class to the one-argument constructor. To do
|
|
* as little as possible, specify <code>Activity.class</code>.
|
|
*/
|
|
public BaseRobocopTest() {
|
|
this((Class<Activity>) BROWSER_INTENT_CLASS);
|
|
}
|
|
|
|
/**
|
|
* Start the given activity class at the beginning of this test.
|
|
* <p>
|
|
* <b>You should use the no-argument constructor in almost all cases.</b>
|
|
*
|
|
* @param activityClass to start before this test.
|
|
*/
|
|
protected BaseRobocopTest(Class<Activity> activityClass) {
|
|
super(activityClass);
|
|
}
|
|
|
|
/**
|
|
* Returns the test type: mochitest or talos.
|
|
* <p>
|
|
* By default tests are mochitests, but a test can override this method in
|
|
* order to change its type. Most Robocop tests are mochitests.
|
|
*/
|
|
protected Type getTestType() {
|
|
return Type.MOCHITEST;
|
|
}
|
|
|
|
// Member function to allow specialization.
|
|
protected Intent createActivityIntent() {
|
|
return BaseRobocopTest.createActivityIntent(mConfig);
|
|
}
|
|
|
|
// Static function to allow re-use.
|
|
public static Intent createActivityIntent(Map<String, String> config) {
|
|
final Intent intent = new Intent(Intent.ACTION_MAIN);
|
|
intent.putExtra("args", "-no-remote -profile " + config.get("profile"));
|
|
// Don't show the first run experience.
|
|
intent.putExtra(BrowserApp.EXTRA_SKIP_STARTPANE, true);
|
|
|
|
final String envString = config.get("envvars");
|
|
if (!TextUtils.isEmpty(envString)) {
|
|
final String[] envStrings = envString.split(",");
|
|
|
|
for (int iter = 0; iter < envStrings.length; iter++) {
|
|
intent.putExtra("env" + iter, envStrings[iter]);
|
|
}
|
|
}
|
|
|
|
return intent;
|
|
}
|
|
|
|
@Override
|
|
protected void setUp() throws Exception {
|
|
// Disable the updater.
|
|
UpdateServiceHelper.setEnabled(false);
|
|
|
|
// Load config file from root path (set up by Python script).
|
|
mRootPath = FennecInstrumentationTestRunner.getFennecArguments().getString("deviceroot");
|
|
if (mRootPath == null) {
|
|
Log.w("Robocop", "Did not find deviceroot in arguments; falling back to: " + DEFAULT_ROOT_PATH);
|
|
mRootPath = DEFAULT_ROOT_PATH;
|
|
}
|
|
String configFile = FennecNativeDriver.getFile(mRootPath + "/robotium.config");
|
|
mConfig = FennecNativeDriver.convertTextToTable(configFile);
|
|
mLogFile = mConfig.get("logfile");
|
|
mProfile = mConfig.get("profile");
|
|
mBaseHostnameUrl = mConfig.get("host").replaceAll("(/$)", "");
|
|
mBaseIpUrl = mConfig.get("rawhost").replaceAll("(/$)", "");
|
|
|
|
// Initialize the asserter.
|
|
if (getTestType() == Type.TALOS) {
|
|
mAsserter = new FennecTalosAssert();
|
|
} else {
|
|
mAsserter = new FennecMochitestAssert();
|
|
}
|
|
mAsserter.setLogFile(mLogFile);
|
|
mAsserter.setTestName(getClass().getName());
|
|
|
|
// Start the activity.
|
|
final Intent intent = createActivityIntent();
|
|
setActivityIntent(intent);
|
|
|
|
// Set up Robotium.solo and Driver objects
|
|
Activity tempActivity = getActivity();
|
|
|
|
StringHelper.initialize(tempActivity.getResources());
|
|
mStringHelper = StringHelper.get();
|
|
|
|
mSolo = new Solo(getInstrumentation(), tempActivity);
|
|
mDriver = new FennecNativeDriver(tempActivity, mSolo, mRootPath);
|
|
mActions = new FennecNativeActions(tempActivity, mSolo, getInstrumentation(), mAsserter);
|
|
}
|
|
|
|
@Override
|
|
public void tearDown() throws Exception {
|
|
try {
|
|
mAsserter.endTest();
|
|
|
|
// By default, we don't kill Fennec on finish, and we don't finish
|
|
// all opened activities. Not killing Fennec entirely is intended to
|
|
// make life better for local testers, who might want to alter a
|
|
// test that is under development rather than Fennec itself. Not
|
|
// finishing activities is intended to allow local testers to
|
|
// manually inspect an activity's state after a test
|
|
// run. runtestsremote.py sets this to "1". Testers running via an
|
|
// IDE will not have this set at all.
|
|
final String killAndFinish = FennecInstrumentationTestRunner.getFennecArguments()
|
|
.getString("kill_and_finish"); // null means not specified.
|
|
if ("1".equals(killAndFinish)) {
|
|
// Request the browser force quit and wait for it to take effect.
|
|
Log.i(LOGTAG, "Requesting force quit.");
|
|
GeckoAppShell.sendEventToGecko(GeckoEvent.createBroadcastEvent("Robocop:Quit", null));
|
|
mSolo.sleep(ROBOCOP_QUIT_WAIT_MS);
|
|
|
|
// If still running, finish activities as recommended by Robotium.
|
|
Log.i(LOGTAG, "Finishing all opened activities.");
|
|
mSolo.finishOpenedActivities();
|
|
} else {
|
|
// This has the effect of keeping the activity-under-test
|
|
// around; if we don't set it to null, it is killed, either by
|
|
// finishOpenedActivities above or super.tearDown below.
|
|
Log.i(LOGTAG, "Not requesting force quit and trying to keep started activity alive.");
|
|
setActivity(null);
|
|
}
|
|
} catch (Throwable e) {
|
|
e.printStackTrace();
|
|
}
|
|
super.tearDown();
|
|
}
|
|
|
|
/**
|
|
* Function to early abort if we can't reach the given HTTP server. Provides local testers
|
|
* with diagnostic information. Not currently available for TALOS tests, which are rarely run
|
|
* locally in any case.
|
|
*/
|
|
public void throwIfHttpGetFails() {
|
|
if (getTestType() == Type.TALOS) {
|
|
return;
|
|
}
|
|
|
|
// rawURL to test fetching from. This should be a raw (IP) URL, not an alias
|
|
// (like mochi.test). We can't (easily) test fetching from the aliases, since
|
|
// those are managed by Fennec's proxy settings.
|
|
final String rawUrl = ((String) mConfig.get("rawhost")).replaceAll("(/$)", "");
|
|
|
|
try {
|
|
final HttpClient httpclient = new DefaultHttpClient();
|
|
final HttpResponse response = httpclient.execute(new HttpGet(rawUrl));
|
|
final int statusCode = response.getStatusLine().getStatusCode();
|
|
if (200 != statusCode) {
|
|
throw new IllegalStateException("Status code: " + statusCode);
|
|
}
|
|
} catch (Exception e) {
|
|
mAsserter.ok(false, "Robocop tests on your device need network/wifi access to reach: [" + rawUrl + "].", e.toString());
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Ensure that the screen on the test device is powered on during tests.
|
|
*/
|
|
public void throwIfScreenNotOn() {
|
|
final PowerManager pm = (PowerManager) getActivity().getSystemService(Context.POWER_SERVICE);
|
|
mAsserter.ok(pm.isScreenOn(),
|
|
"Robocop tests need the test device screen to be powered on.", "");
|
|
}
|
|
}
|