Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Use ADB helpers to wait for activities #425

Merged
merged 13 commits into from
Apr 1, 2019
Original file line number Diff line number Diff line change
Expand Up @@ -29,11 +29,9 @@ class CreateSession : RequestHandler<SessionParams, Session> {
override fun handle(params: SessionParams): Session {
val appiumSession = Session.createGlobalSession(params.desiredCapabilities)
val activityName = params.desiredCapabilities.appActivity
val waitActivityName = params.desiredCapabilities.appWaitActivity
val appWaitDuration = params.desiredCapabilities.appWaitDuration
try {
activityName?.let {
startActivity(it, waitActivityName, appWaitDuration)
startActivity(it)
}
} catch (e: Exception) {
throw SessionNotCreatedException(e)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ class StartActivity : RequestHandler<StartActivityParams, Void?> {

@Throws(AppiumException::class)
override fun handle(params: StartActivityParams): Void? {
startActivity(params.appActivity, params.appWaitActivity)
startActivity(params.appActivity)
return null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,17 +23,12 @@

import java.lang.reflect.Field;

import javax.annotation.Nullable;

import androidx.test.platform.app.InstrumentationRegistry;
import io.appium.espressoserver.lib.handlers.exceptions.AppiumException;

import static io.appium.espressoserver.lib.helpers.AndroidLogger.logger;
import static io.appium.espressoserver.lib.helpers.StringHelpers.isBlank;

public class ActivityHelper {
private static final long ACTIVITY_STARTUP_TIMEOUT = 60 * 1000;

// https://androidreclib.wordpress.com/2014/11/22/getting-the-current-activity/
public static Activity getCurrentActivity() throws AppiumException {
try {
Expand Down Expand Up @@ -65,32 +60,13 @@ private static String getFullyQualifiedActivityName(Instrumentation instrumentat
return activity.startsWith(".") ? pkg + activity : activity;
}

public static void startActivity(String activity, @Nullable String waitActivity) {
startActivity(activity, waitActivity, ACTIVITY_STARTUP_TIMEOUT);
}

public static void startActivity(String activity, @Nullable String waitActivity,
@Nullable Long waitDuration) {
public static void startActivity(String activity) {
logger.info(String.format("Starting activity '%s'", activity));
Instrumentation instrumentation = InstrumentationRegistry.getInstrumentation();
String fullyQualifiedAppActivity = getFullyQualifiedActivityName(instrumentation, activity);
String fullyQualifiedWaitActivity = isBlank(waitActivity)
? fullyQualifiedAppActivity
: getFullyQualifiedActivityName(instrumentation, waitActivity);
Intent intent = new Intent(Intent.ACTION_MAIN);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName(instrumentation.getTargetContext(), fullyQualifiedAppActivity);
Instrumentation.ActivityMonitor activityStateMonitor = instrumentation
.addMonitor(fullyQualifiedWaitActivity, null, false);
instrumentation.startActivitySync(intent);
if (waitDuration == null) {
waitDuration = ACTIVITY_STARTUP_TIMEOUT;
}
Activity currentActivity = instrumentation.waitForMonitorWithTimeout(activityStateMonitor, waitDuration);
if (currentActivity == null) {
throw new IllegalStateException(String.format("Activity '%s' was unable to start within %sms timeout",
fullyQualifiedWaitActivity, waitDuration));
}
logger.info(String.format("Activity '%s' started", currentActivity.getLocalClassName()));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,5 @@ package io.appium.espressoserver.lib.model
data class SessionParams(
val desiredCapabilities: DesiredCapabilities
) : AppiumParams() {
data class DesiredCapabilities(var appActivity : String?,
var appWaitActivity : String?,
var appWaitDuration: Long?)
data class DesiredCapabilities(var appActivity : String?)
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package io.appium.espressoserver.lib.model

data class StartActivityParams(
val appActivity: String? = null,
val appWaitActivity: String? = null
val appActivity: String? = null
) : AppiumParams()
Original file line number Diff line number Diff line change
Expand Up @@ -68,12 +68,9 @@ class `KReflectionUtils Test` {

@Test
fun `should extract declared properties from an instance`() {
val sessionParams = SessionParams.DesiredCapabilities(
"appActivity", "appWaitActivity", 1)
val sessionParams = SessionParams.DesiredCapabilities("appActivity")
val extractedProps = KReflectionUtils.extractDeclaredProperties(sessionParams)
assertEquals(extractedProps["appActivity"], "appActivity")
assertEquals(extractedProps["appWaitActivity"], "appWaitActivity")
assertEquals(extractedProps["appWaitDuration"], 1L)
}

class TestClass {
Expand All @@ -86,10 +83,10 @@ class `KReflectionUtils Test` {
}

fun plus (dumbEnum: DumbEnum, num: Int): String {
when (dumbEnum) {
DumbEnum.A -> return "A" + num
DumbEnum.B -> return "B" + num
DumbEnum.C -> return "C" + num
return when (dumbEnum) {
DumbEnum.A -> "A$num"
DumbEnum.B -> "B$num"
DumbEnum.C -> "C$num"
}
}
}
Expand Down
2 changes: 1 addition & 1 deletion espresso-server/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ buildscript {
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath 'com.android.tools.build:gradle:3.3.1'
classpath 'com.android.tools.build:gradle:3.3.2'

// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
Expand Down
11 changes: 11 additions & 0 deletions lib/commands/general.js
Original file line number Diff line number Diff line change
Expand Up @@ -263,6 +263,17 @@ helpers.suspendChromedriverProxy = function suspendChromedriverProxy () {
this.jwpProxyActive = true;
};

commands.startActivity = async function startActivity (appPackage, appActivity,
appWaitPackage, appWaitActivity) {
// intentAction, intentCategory, intentFlags, optionalIntentArguments, dontStopAppOnReset
// parameters are not supported by Espresso

logger.debug(`Starting activity '${appActivity}' for package '${appPackage}'`);
await this.espresso.jwproxy.command(`/appium/device/start_activity`, 'POST', {appActivity});
await this.adb.waitForActivity(appWaitPackage || appPackage || this.caps.appPackage,
appWaitActivity || appActivity);
};


Object.assign(extensions, commands, helpers);
export { commands, helpers };
Expand Down
38 changes: 29 additions & 9 deletions lib/driver.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ const NO_PROXY = [
['POST', new RegExp('^/session/[^/]+/appium/device/pull_folder')],
['POST', new RegExp('^/session/[^/]+/appium/device/push_file')],
['POST', new RegExp('^/session/[^/]+/appium/device/remove_app')],
['POST', new RegExp('^/session/[^/]+/appium/device/start_activity')],
['POST', new RegExp('^/session/[^/]+/appium/device/terminate_app')],
['POST', new RegExp('^/session/[^/]+/appium/device/unlock')],
['POST', new RegExp('^/session/[^/]+/appium/getPerformanceData')],
Expand Down Expand Up @@ -225,8 +226,12 @@ class EspressoDriver extends BaseDriver {

// get appPackage et al from manifest if necessary
let appInfo = await helpers.getLaunchInfo(this.adb, this.opts);
// and get it onto our 'opts' object so we use it from now on
Object.assign(this.opts, appInfo);
if (appInfo) {
// and get it onto our 'opts' object so we use it from now on
Object.assign(this.opts, appInfo);
} else {
appInfo = this.opts;
}

// start an avd, set the language/locale, pick an emulator, etc...
// TODO with multiple devices we'll need to parameterize this
Expand Down Expand Up @@ -267,19 +272,34 @@ class EspressoDriver extends BaseDriver {
if (!this.caps.appPackage) {
this.caps.appPackage = appInfo.appPackage;
}
if (!this.caps.appActivity) {
// fully qualify the appActivity
let appActivity = appInfo.appActivity;
if (!appInfo.appActivity.startsWith(this.caps.appPackage)) {
appActivity = `${this.caps.appPackage}${appActivity.startsWith('.') ? '' : '.'}${appActivity}`;
logger.warn(`Adjusted appActivity to fully qualified version: '${appActivity}'`);
if (!this.caps.appWaitPackage) {
this.caps.appWaitPackage = appInfo.appWaitPackage || appInfo.appPackage || this.caps.appPackage;
}
const qualifyActivityName = (activityName, packageName) => {
let result = activityName;
if (!activityName.startsWith(packageName)) {
result = `${packageName}${activityName.startsWith('.') ? '' : '.'}${activityName}`;
logger.warn(`Adjusted activity '${activityName}' to fully qualified version: '${result}'`);
}
this.caps.appActivity = appActivity;
return result;
};
if (this.caps.appActivity) {
this.caps.appActivity = qualifyActivityName(this.caps.appActivity, this.caps.appPackage);
} else {
this.caps.appActivity = qualifyActivityName(appInfo.appActivity, this.caps.appPackage);
}
if (this.caps.appWaitActivity) {
this.caps.appWaitActivity = qualifyActivityName(this.caps.appWaitActivity, this.caps.appWaitPackage);
} else {
this.caps.appWaitActivity = qualifyActivityName(appInfo.appWaitActivity || appInfo.appActivity || this.caps.appActivity,
this.caps.appWaitPackage);
}

// launch espresso and wait till its online and we have a session
await this.espresso.startSession(this.caps);

await this.adb.waitForActivity(this.caps.appWaitPackage, this.caps.appWaitActivity, this.opts.appWaitDuration);

// if we want to immediately get into a webview, set our context
// appropriately
if (this.opts.autoWebview) {
Expand Down
10 changes: 8 additions & 2 deletions test/functional/commands/mobile-e2e-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,10 @@ describe('mobile', function () {

describe('mobile: setDate, mobile: setTime', function () {
it('should set the date on a DatePicker', async function () {
await driver.startActivity({appPackage: 'io.appium.android.apis', appActivity: 'io.appium.android.apis.view.DateWidgets1'});
await driver.startActivity({
appPackage: 'io.appium.android.apis',
appActivity: 'io.appium.android.apis.view.DateWidgets1',
});
let dateEl = await driver.elementByAccessibilityId('change the date');
await dateEl.click();
let datePicker = await driver.elementById('android:id/datePicker');
Expand All @@ -116,7 +119,10 @@ describe('mobile', function () {
await driver.back();
});
it('should set the time on a timepicker', async function () {
await driver.startActivity({appPackage: 'io.appium.android.apis', appActivity: 'io.appium.android.apis.view.DateWidgets2'});
await driver.startActivity({
appPackage: 'io.appium.android.apis',
appActivity: 'io.appium.android.apis.view.DateWidgets2',
});
let timeEl = await driver.elementByXPath('//android.widget.TimePicker');
await driver.execute('mobile: setTime', {hours: 10, minutes: 58, element: timeEl});
let source = await driver.source();
Expand Down
15 changes: 8 additions & 7 deletions test/functional/commands/touch-e2e-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ import wd from 'wd';
import request from 'request-promise';
import B from 'bluebird';
import _ from 'lodash';
import { initSession, deleteSession, HOST, PORT,
MOCHA_TIMEOUT } from '../helpers/session';
import {
initSession, deleteSession, HOST, PORT,
MOCHA_TIMEOUT } from '../helpers/session';
import { APIDEMO_CAPS } from '../desired';


Expand All @@ -27,36 +28,36 @@ describe('touch actions -', function () {

async function startListActivity () {
await driver.startActivity({
appActivity: '.view.List5',
appPackage: 'io.appium.android.apis',
appActivity: '.view.List5',
});
}

async function startFingerPaintActivity () {
await driver.startActivity({
appActivity: '.graphics.FingerPaint',
appPackage: 'io.appium.android.apis',
appActivity: '.graphics.FingerPaint',
});
}

async function startSplitTouchActivity () {
await driver.startActivity({
appActivity: '.view.SplitTouchView',
appPackage: 'io.appium.android.apis',
appActivity: '.view.SplitTouchView',
});
}

async function startDragAndDropActivity () {
await driver.startActivity({
appActivity: '.view.DragAndDropDemo',
appPackage: 'io.appium.android.apis',
appActivity: '.view.DragAndDropDemo',
});
}

async function startTextSwitcherActivity () {
await driver.startActivity({
appActivity: '.view.TextSwitcher1',
appPackage: 'io.appium.android.apis',
appActivity: '.view.TextSwitcher1',
});
}

Expand Down
12 changes: 9 additions & 3 deletions test/functional/driver-e2e-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ describe('EspressoDriver', function () {
// for now the activity needs to be fully qualified
await driver.init(Object.assign({
appActivity: 'io.appium.android.apis.some.fake.Activity'
}, APIDEMO_CAPS)).should.eventually.be.rejectedWith(/unable to resolve/i);
}, APIDEMO_CAPS)).should.eventually.be.rejected;
});
it('should reject opening of appPackage with incorrect signature', async function () {
await driver.init(Object.assign({
Expand All @@ -97,12 +97,18 @@ describe('EspressoDriver', function () {
});
it('should start activity by name', async function () {
await driver.init(APIDEMO_CAPS);
await driver.startActivity({appActivity: '.accessibility.AccessibilityNodeProviderActivity'});
await driver.startActivity({
appPackage: 'io.appium.android.apis',
appActivity: '.accessibility.AccessibilityNodeProviderActivity',
});
await driver.getCurrentDeviceActivity().should.eventually.eql('.accessibility.AccessibilityNodeProviderActivity');
});
it('should start activity by fully-qualified name', async function () {
await driver.init(APIDEMO_CAPS);
await driver.startActivity({appActivity: 'io.appium.android.apis.accessibility.AccessibilityNodeProviderActivity'});
await driver.startActivity({
appPackage: 'io.appium.android.apis',
appActivity: 'io.appium.android.apis.accessibility.AccessibilityNodeProviderActivity',
});
await driver.getCurrentDeviceActivity().should.eventually.eql('.accessibility.AccessibilityNodeProviderActivity');
});
});
Expand Down
5 changes: 4 additions & 1 deletion test/functional/webview/web-e2e-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,10 @@ describe('web', function () {

// Switch to native and go to different activity
await driver.context(contexts[0]);
await driver.startActivity({appActivity: 'io.appium.android.apis.view.WebView3', appPackage: 'io.appium.android.apis'});
await driver.startActivity({
appPackage: 'io.appium.android.apis',
appActivity: 'io.appium.android.apis.view.WebView3',
});
contexts = await driver.contexts();
await driver.elementById('android:id/content').should.eventually.exist;

Expand Down
9 changes: 8 additions & 1 deletion test/unit/driver-specs.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,12 @@ describe('driver', function () {
let driver;
beforeEach(function () {
driver = new EspressoDriver({}, false);
driver.caps = { appPackage: 'io.appium.package', appActivity: '.MainActivity'};
driver.caps = {
appPackage: 'io.appium.package',
appActivity: '.MainActivity',
appWaitPackage: 'io.appium.package',
appWaitActivity: '.MainActivity',
};
driver.opts = { autoLaunch: false, skipUnlock: true };
sandbox.stub(driver, 'initEspressoServer');
sandbox.stub(driver, 'addDeviceInfoToCaps');
Expand Down Expand Up @@ -50,6 +55,7 @@ describe('driver', function () {
forwardPort: () => {},
isAnimationOn: () => false,
installOrUpgrade: () => {},
waitForActivity: () => {},
};
});
await driver.startEspressoSession();
Expand All @@ -74,6 +80,7 @@ describe('driver', function () {
startLogcat: () => {},
forwardPort: () => {},
isAnimationOn: () => false,
waitForActivity: () => {},
};
});
await driver.startEspressoSession();
Expand Down