diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/CreateSession.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/CreateSession.kt index 029224ca4..f3289ef72 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/CreateSession.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/CreateSession.kt @@ -29,11 +29,9 @@ class CreateSession : RequestHandler { 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) diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/StartActivity.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/StartActivity.kt index 6ed9432ad..72a4d97fd 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/StartActivity.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/handlers/StartActivity.kt @@ -25,7 +25,7 @@ class StartActivity : RequestHandler { @Throws(AppiumException::class) override fun handle(params: StartActivityParams): Void? { - startActivity(params.appActivity, params.appWaitActivity) + startActivity(params.appActivity) return null } } diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/helpers/ActivityHelper.java b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/helpers/ActivityHelper.java index 280cd0bcb..53cf5ddbb 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/helpers/ActivityHelper.java +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/helpers/ActivityHelper.java @@ -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 { @@ -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())); } } diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/model/SessionParams.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/model/SessionParams.kt index 5c857d20b..2b835fd00 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/model/SessionParams.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/model/SessionParams.kt @@ -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?) } diff --git a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/model/StartActivityParams.kt b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/model/StartActivityParams.kt index f0c1525a7..4a118b01b 100644 --- a/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/model/StartActivityParams.kt +++ b/espresso-server/app/src/androidTest/java/io/appium/espressoserver/lib/model/StartActivityParams.kt @@ -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() diff --git a/espresso-server/app/src/test/java/io/appium/espressoserver/test/helpers/KReflectionUtilsTest.kt b/espresso-server/app/src/test/java/io/appium/espressoserver/test/helpers/KReflectionUtilsTest.kt index 75b2cf750..fa560fad0 100644 --- a/espresso-server/app/src/test/java/io/appium/espressoserver/test/helpers/KReflectionUtilsTest.kt +++ b/espresso-server/app/src/test/java/io/appium/espressoserver/test/helpers/KReflectionUtilsTest.kt @@ -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 { @@ -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" } } } diff --git a/espresso-server/build.gradle b/espresso-server/build.gradle index 795fc7786..705f511fd 100644 --- a/espresso-server/build.gradle +++ b/espresso-server/build.gradle @@ -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 diff --git a/lib/commands/general.js b/lib/commands/general.js index db90181a9..670c18ee7 100644 --- a/lib/commands/general.js +++ b/lib/commands/general.js @@ -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 }; diff --git a/lib/driver.js b/lib/driver.js index bc314db10..8ecafc2a9 100644 --- a/lib/driver.js +++ b/lib/driver.js @@ -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')], @@ -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 @@ -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) { diff --git a/test/functional/commands/mobile-e2e-specs.js b/test/functional/commands/mobile-e2e-specs.js index ea7d39349..45065bb38 100644 --- a/test/functional/commands/mobile-e2e-specs.js +++ b/test/functional/commands/mobile-e2e-specs.js @@ -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'); @@ -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(); diff --git a/test/functional/commands/touch-e2e-specs.js b/test/functional/commands/touch-e2e-specs.js index f1d2e5364..610301336 100644 --- a/test/functional/commands/touch-e2e-specs.js +++ b/test/functional/commands/touch-e2e-specs.js @@ -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'; @@ -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', }); } diff --git a/test/functional/driver-e2e-specs.js b/test/functional/driver-e2e-specs.js index e6acfefd8..11f3918c0 100644 --- a/test/functional/driver-e2e-specs.js +++ b/test/functional/driver-e2e-specs.js @@ -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({ @@ -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'); }); }); diff --git a/test/functional/webview/web-e2e-specs.js b/test/functional/webview/web-e2e-specs.js index 634eb91a3..4f334174d 100644 --- a/test/functional/webview/web-e2e-specs.js +++ b/test/functional/webview/web-e2e-specs.js @@ -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; diff --git a/test/unit/driver-specs.js b/test/unit/driver-specs.js index ba83356df..9178e2aa5 100644 --- a/test/unit/driver-specs.js +++ b/test/unit/driver-specs.js @@ -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'); @@ -50,6 +55,7 @@ describe('driver', function () { forwardPort: () => {}, isAnimationOn: () => false, installOrUpgrade: () => {}, + waitForActivity: () => {}, }; }); await driver.startEspressoSession(); @@ -74,6 +80,7 @@ describe('driver', function () { startLogcat: () => {}, forwardPort: () => {}, isAnimationOn: () => false, + waitForActivity: () => {}, }; }); await driver.startEspressoSession();