From f7c0c58a6c92da9193bd92f545c6f532387b0417 Mon Sep 17 00:00:00 2001 From: Puneet Bajaj Date: Mon, 13 Feb 2023 19:44:09 +0530 Subject: [PATCH 1/9] add runtime driver for android devices in cloud --- .../android/cloud/cloudAndroidDriver.js | 430 ++++++++++++++++++ .../src/devices/runtime/factories/android.js | 6 +- 2 files changed, 433 insertions(+), 3 deletions(-) create mode 100644 detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js diff --git a/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js b/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js new file mode 100644 index 0000000000..7c0d3b7e80 --- /dev/null +++ b/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js @@ -0,0 +1,430 @@ +/* eslint @typescript-eslint/no-unused-vars: ["error", { "args": "none" }] */ +// @ts-nocheck +// const path = require('path'); +// const URL = require('url').URL; + +// const fs = require('fs-extra'); +// const _ = require('lodash'); + +const DetoxApi = require('../../../../../android/espressoapi/Detox'); +const EspressoDetoxApi = require('../../../../../android/espressoapi/EspressoDetox'); +const UiDeviceProxy = require('../../../../../android/espressoapi/UiDeviceProxy'); +// const temporaryPath = require('../../../../../artifacts/utils/temporaryPath'); +const DetoxRuntimeError = require('../../../../../errors/DetoxRuntimeError'); +// const getAbsoluteBinaryPath = require('../../../../../utils/getAbsoluteBinaryPath'); +const logger = require('../../../../../utils/logger'); +// const pressAnyKey = require('../../../../../utils/pressAnyKey'); +const retry = require('../../../../../utils/retry'); +const sleep = require('../../../../../utils/sleep'); +// const apkUtils = require('../../../../common/drivers/android/tools/apk'); +const DeviceDriverBase = require('../../DeviceDriverBase'); + +const log = logger.child({ cat: 'device' }); + +/** + * @typedef CloudAndroidDriverProps + * @property adbName { String } The unique identifier associated with ADB + */ + +/** + * @typedef { DeviceDriverDeps } CloudAndroidDriverDeps + * @property invocationManager { InvocationManager } + * @property adb { ADB } + * @property aapt { AAPT } + * @property apkValidator { ApkValidator } + * @property fileTransfer { FileTransfer } + * @property appInstallHelper { AppInstallHelper } + * @property appUninstallHelper { AppUninstallHelper } + * @property devicePathBuilder { AndroidDevicePathBuilder } + * @property instrumentation { MonitoredInstrumentation } + */ + +class CloudAndroidDriver extends DeviceDriverBase { + /** + * @param deps { CloudAndroidDriverDeps } + * @param props { CloudAndroidDriverProps } + */ + constructor(deps, { adbName }) { + super(deps); + + this.adbName = adbName; + this.adb = deps.adb; + this.aapt = deps.aapt; + this.apkValidator = deps.apkValidator; + this.invocationManager = deps.invocationManager; + this.fileTransfer = deps.fileTransfer; + // this.appInstallHelper = deps.appInstallHelper; + // this.appUninstallHelper = deps.appUninstallHelper; + this.devicePathBuilder = deps.devicePathBuilder; + this.instrumentation = deps.instrumentation; + + this.uiDevice = new UiDeviceProxy(this.invocationManager).getUIDevice(); + } + + // getExternalId() { + // return this.adbName; + // } + + // async getBundleIdFromBinary(apkPath) { + // const binaryPath = getAbsoluteBinaryPath(apkPath); + // return await this.aapt.getPackageName(binaryPath); + // } + + // async installApp(_appBinaryPath, _testBinaryPath) { + // const { + // appBinaryPath, + // testBinaryPath, + // } = this._getAppInstallPaths(_appBinaryPath, _testBinaryPath); + // await this._validateAppBinaries(appBinaryPath, testBinaryPath); + // await this._installAppBinaries(appBinaryPath, testBinaryPath); + // } + + // async uninstallApp(bundleId) { + // await this.emitter.emit('beforeUninstallApp', { deviceId: this.adbName, bundleId }); + // await this.appUninstallHelper.uninstall(this.adbName, bundleId); + // } + + // async installUtilBinaries(paths) { + // for (const path of paths) { + // const packageId = await this.getBundleIdFromBinary(path); + // if (!await this.adb.isPackageInstalled(this.adbName, packageId)) { + // await this.appInstallHelper.install(this.adbName, path); + // } + // } + // } + + async launchApp(bundleId, launchArgs, languageAndLocale) { + return await this._handleLaunchApp({ + manually: false, + bundleId, + launchArgs, + languageAndLocale, + }); + } + + // async waitForAppLaunch(bundleId, launchArgs, languageAndLocale) { + // return await this._handleLaunchApp({ + // manually: true, + // bundleId, + // launchArgs, + // languageAndLocale, + // }); + // } + + async _handleLaunchApp({ manually, bundleId, launchArgs }) { + const { adbName } = this; + + await this.emitter.emit('beforeLaunchApp', { bundleId, launchArgs }); + + // launchArgs = await this._modifyArgsForNotificationHandling(adbName, bundleId, launchArgs); + + // if (manually) { + // await this._waitForAppLaunch(adbName, bundleId, launchArgs); + // } else { + await this._launchApp(adbName, bundleId, launchArgs); + // } + + const pid = await this._waitForProcess(adbName, bundleId); + if (manually) { + log.info({}, `Found the app (${bundleId}) with process ID = ${pid}. Proceeding...`); + } + + await this.emitter.emit('launchApp', { deviceId: adbName, bundleId, launchArgs, pid }); + return pid; + } + + // On product + async deliverPayload(params) { + if (params.delayPayload) { + return; + } + + const { url, detoxUserNotificationDataURL } = params; + if (url) { + await this._startActivityWithUrl(url); + } else if (detoxUserNotificationDataURL) { + const payloadPathOnDevice = await this._sendNotificationDataToDevice(detoxUserNotificationDataURL, this.adbName); + await this._startActivityFromNotification(payloadPathOnDevice); + } + } + + // Check this + async waitUntilReady() { + try { + await Promise.race([ + super.waitUntilReady(), + this.instrumentation.waitForCrash() + ]); + } catch (e) { + log.warn({ error: e }, 'An error occurred while waiting for the app to become ready. Waiting for disconnection...'); + await this.client.waitUntilDisconnected(); + log.warn('The app disconnected.'); + throw e; + } finally { + this.instrumentation.abortWaitForCrash(); + } + } + + async pressBack() { + await this.uiDevice.pressBack(); + } + + async sendToHome(params) { + await this.uiDevice.pressHome(); + } + + // Cannot find initialization https://github.com/pb2323/cloud_detox_support/blob/2382f0b3b0d86c0572dcfa915b412c12c8ace4f0/detox/src/android/core/WebElement.js#L39 + async typeText(text) { + await this.adb.typeText(this.adbName, text); + } + + // Check this + async terminate(bundleId) { + // const { adbName } = this; + await this.emitter.emit('beforeTerminateApp', { bundleId }); + await this._terminateInstrumentation(); + await this.adb.terminate(adbName, bundleId); + await this.emitter.emit('terminateApp', { deviceId: adbName, bundleId }); + } + + // Check this + async cleanup(bundleId) { + await this._terminateInstrumentation(); + await super.cleanup(bundleId); + } + + getPlatform() { + return 'android'; + } + + getUiDevice() { + return this.uiDevice; + } + + // async reverseTcpPort(port) { + // await this.adb.reverse(this.adbName, port); + // } + + // async unreverseTcpPort(port) { + // await this.adb.reverseRemove(this.adbName, port); + // } + + async setURLBlacklist(urlList) { + await this.invocationManager.execute(EspressoDetoxApi.setURLBlacklist(urlList)); + } + + async enableSynchronization() { + await this.invocationManager.execute(EspressoDetoxApi.setSynchronization(true)); + } + + async disableSynchronization() { + await this.invocationManager.execute(EspressoDetoxApi.setSynchronization(false)); + } + + // Check this + async takeScreenshot(screenshotName) { + // const { adbName } = this; + + // const pathOnDevice = this.devicePathBuilder.buildTemporaryArtifactPath('.png'); + // await this.adb.screencap(adbName, pathOnDevice); + + // const tempPath = temporaryPath.for.png(); + // await this.adb.pull(adbName, pathOnDevice, tempPath); + // await this.adb.rm(adbName, pathOnDevice); + + await this.emitter.emit('createExternalArtifact', { + pluginId: 'screenshot', + artifactName: screenshotName || '' + // artifactPath: tempPath, + }); + + return ''; + } + + async setOrientation(orientation) { + const orientationMapping = { + landscape: 1, // top at left side landscape + portrait: 0 // non-reversed portrait. + }; + + const call = EspressoDetoxApi.changeOrientation(orientationMapping[orientation]); + await this.invocationManager.execute(call); + } + + // _getAppInstallPaths(_appBinaryPath, _testBinaryPath) { + // const appBinaryPath = getAbsoluteBinaryPath(_appBinaryPath); + // const testBinaryPath = _testBinaryPath ? getAbsoluteBinaryPath(_testBinaryPath) : this._getTestApkPath(appBinaryPath); + // return { + // appBinaryPath, + // testBinaryPath, + // }; + // } + + // async _validateAppBinaries(appBinaryPath, testBinaryPath) { + // try { + // await this.apkValidator.validateAppApk(appBinaryPath); + // } catch (e) { + // logger.warn(e.toString()); + // } + + // try { + // await this.apkValidator.validateTestApk(testBinaryPath); + // } catch (e) { + // logger.warn(e.toString()); + // } + // } + + // async _installAppBinaries(appBinaryPath, testBinaryPath) { + // await this.adb.install(this.adbName, appBinaryPath); + // await this.adb.install(this.adbName, testBinaryPath); + // } + + // _getTestApkPath(originalApkPath) { + // const testApkPath = apkUtils.getTestApkPath(originalApkPath); + + // if (!fs.existsSync(testApkPath)) { + // throw new DetoxRuntimeError({ + // message: `The test APK could not be found at path: '${testApkPath}'`, + // hint: 'Try running the detox build command, and make sure it was configured to execute a build command (e.g. \'./gradlew assembleAndroidTest\')' + + // '\nFor further assistance, visit the Android setup guide: https://github.com/wix/Detox/blob/master/docs/Introduction.Android.md', + // }); + // } + // return testApkPath; + // } + + // async _modifyArgsForNotificationHandling(adbName, bundleId, launchArgs) { + // let _launchArgs = launchArgs; + // if (launchArgs.detoxUserNotificationDataURL) { + // const notificationPayloadTargetPath = await this._sendNotificationDataToDevice(launchArgs.detoxUserNotificationDataURL, adbName); + // _launchArgs = { + // ...launchArgs, + // detoxUserNotificationDataURL: notificationPayloadTargetPath, + // }; + // } + // return _launchArgs; + // } + + async _launchApp(adbName, bundleId, launchArgs) { + if (!this.instrumentation.isRunning()) { + // await this._launchInstrumentationProcess(adbName, bundleId, launchArgs); + // launchArgs can be sent as per product + await this.invocationManager.execute({ + 'type': 'launchApp', + 'params': { + 'bundleId': bundleId, + 'launchArgs': launchArgs + } + }); + await sleep(500); + } else if (launchArgs.detoxURLOverride) { + await this._startActivityWithUrl(launchArgs.detoxURLOverride); + } else if (launchArgs.detoxUserNotificationDataURL) { + await this._startActivityFromNotification(launchArgs.detoxUserNotificationDataURL); + } else { + await this._resumeMainActivity(); + } + } + + // async _launchInstrumentationProcess(adbName, bundleId, userLaunchArgs) { + // const serverPort = await this._reverseServerPort(adbName); + // this.instrumentation.setTerminationFn(async () => { + // await this._terminateInstrumentation(); + // await this.adb.reverseRemove(adbName, serverPort); + // }); + // await this.instrumentation.launch(adbName, bundleId, userLaunchArgs); + // } + + // async _reverseServerPort(adbName) { + // const serverPort = new URL(this.client.serverUrl).port; + // await this.adb.reverse(adbName, serverPort); + // return serverPort; + // } + + async _terminateInstrumentation() { + await this.instrumentation.terminate(); + await this.instrumentation.setTerminationFn(null); + } + + async _sendNotificationDataToDevice(dataFileLocalPath, adbName) { + await this.fileTransfer.prepareDestinationDir(adbName); + return await this.fileTransfer.send(adbName, dataFileLocalPath, 'notification.json'); + } + + _startActivityWithUrl(url) { + return this.invocationManager.execute(DetoxApi.startActivityFromUrl(url)); + } + + _startActivityFromNotification(dataFilePath) { + return this.invocationManager.execute(DetoxApi.startActivityFromNotification(dataFilePath)); + } + + _resumeMainActivity() { + return this.invocationManager.execute(DetoxApi.launchMainActivity()); + } + + async _waitForProcess(adbName, bundleId) { + let pid = NaN; + try { + const queryPid = () => this._queryPID(adbName, bundleId); + const retryQueryPid = () => retry({ backoff: 'none', retries: 4 }, queryPid); + const retryQueryPidMultiple = () => retry({ backoff: 'linear' }, retryQueryPid); + pid = await retryQueryPidMultiple(); + } catch (e) { + // Show generic message sent by hub + log.warn(await this.adb.shell(adbName, 'ps')); + throw e; + } + return pid; + } + + async _queryPID(adbName, bundleId) { + // const pid = await this.adb.pidof(adbName, bundleId); + // This should be a http request + const pid = await this.invocationManager.execute({ + 'type': 'pidOf', + 'params': { + 'bundleID': bundleId + } + }); + if (!pid) { + throw new DetoxRuntimeError('PID still not available'); + } + return pid; + } + + // async _waitForAppLaunch(adbName, bundleId, launchArgs) { + // const instrumentationClass = await this.adb.getInstrumentationRunner(adbName, bundleId); + // this._printInstrumentationHint({ instrumentationClass, launchArgs }); + // await pressAnyKey(); + // await this._reverseServerPort(adbName); + // } + + // _printInstrumentationHint({ instrumentationClass, launchArgs }) { + // const keyMaxLength = Math.max(3, _(launchArgs).keys().maxBy('length').length); + // const valueMaxLength = Math.max(5, _(launchArgs).values().map(String).maxBy('length').length); + // const rows = _.map(launchArgs, (v, k) => { + // const paddedKey = k.padEnd(keyMaxLength, ' '); + // const paddedValue = `${v}`.padEnd(valueMaxLength, ' '); + // return `${paddedKey} | ${paddedValue}`; + // }); + + // const keyHeader = 'Key'.padEnd(keyMaxLength, ' '); + // const valueHeader = 'Value'.padEnd(valueMaxLength, ' '); + // const header = `${keyHeader} | ${valueHeader}`; + // const separator = '-'.repeat(header.length); + + // log.info({}, + // 'Waiting for you to manually launch your app in Android Studio.\n\n' + + // `Instrumentation class: ${instrumentationClass}\n` + + // 'Instrumentation arguments:\n' + + // `${separator}\n` + + // `${header}\n` + + // `${separator}\n` + + // `${rows.join('\n')}\n` + + // `${separator}\n\n` + + // 'Press any key to continue...' + // ); + // } +} + +module.exports = CloudAndroidDriver; diff --git a/detox/src/devices/runtime/factories/android.js b/detox/src/devices/runtime/factories/android.js index b666f83ca1..4d83b8cb2c 100644 --- a/detox/src/devices/runtime/factories/android.js +++ b/detox/src/devices/runtime/factories/android.js @@ -66,10 +66,10 @@ class Genycloud extends RuntimeDriverFactoryAndroid { class Noop extends RuntimeDriverFactoryAndroid { _createDriver(deviceCookie, deps, configs) { const props = { - adbName: deviceCookie.adbName, + adbName: undefined, }; - const AndroidDriver = require('../drivers/android/AndroidDriver'); - return new AndroidDriver(deps, props); + const CloudAndroidDriver = require('../drivers/android/cloud/cloudAndroidDriver'); + return new CloudAndroidDriver(deps, props); } } From a6c4fe81327549219f5f735415bbcb3e20917ffb Mon Sep 17 00:00:00 2001 From: Puneet Bajaj Date: Mon, 13 Feb 2023 19:44:09 +0530 Subject: [PATCH 2/9] add runtime driver for android devices in cloud --- detox/src/client/Client.js | 18 + detox/src/client/actions/actions.js | 38 ++ .../android/cloud/cloudAndroidDriver.js | 434 ++++++++++++++++++ .../src/devices/runtime/factories/android.js | 6 +- detox/src/invoke.js | 8 + 5 files changed, 501 insertions(+), 3 deletions(-) create mode 100644 detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js diff --git a/detox/src/client/Client.js b/detox/src/client/Client.js index c472544927..1bee0e844a 100644 --- a/detox/src/client/Client.js +++ b/detox/src/client/Client.js @@ -251,6 +251,24 @@ class Client { await this.sendAction(new actions.DeliverPayload(params)); } + async waitForCloudPlatform(params) { + try { + return await this.sendAction(new actions.CloudPlatform(params)); + } catch (err) { + this._successfulTestRun = false; + throw err; + } + } + + // async waitForCloudAdb(params) { + // try { + // return await this.sendAction(new actions.CloudAdb(params)); + // } catch (err) { + // this._successfulTestRun = false; + // throw err; + // } + // } + async terminateApp() { /* see the property injection from Detox.js */ } diff --git a/detox/src/client/actions/actions.js b/detox/src/client/actions/actions.js index 25db10beb5..bccd194470 100644 --- a/detox/src/client/actions/actions.js +++ b/detox/src/client/actions/actions.js @@ -179,6 +179,42 @@ class Cleanup extends Action { } } +class CloudPlatform extends Action { + constructor(params) { + super('CloudPlatform', params); + } + + get isAtomic() { + return true; + } + + get timeout() { + return 90000; + } + + async handle(response) { + this.expectResponseOfType(response, 'CloudPlatform'); + } +} + +// class CloudAdb extends Action { +// constructor(params) { +// super('CloudAdb', params); +// } + +// get isAtomic() { +// return true; +// } + +// get timeout() { +// return 90000; +// } + +// async handle(response) { +// this.expectResponseOfType(response, 'CloudAdb'); +// } +// } + class Invoke extends Action { constructor(params) { super('invoke', params); @@ -336,4 +372,6 @@ module.exports = { SetOrientation, SetInstrumentsRecordingState, CaptureViewHierarchy, + // CloudAdb, + CloudPlatform }; diff --git a/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js b/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js new file mode 100644 index 0000000000..7370a8a7ba --- /dev/null +++ b/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js @@ -0,0 +1,434 @@ +/* eslint @typescript-eslint/no-unused-vars: ["error", { "args": "none" }] */ +// @ts-nocheck +// const path = require('path'); +// const URL = require('url').URL; + +// const fs = require('fs-extra'); +const _ = require('lodash'); + +const DetoxApi = require('../../../../../android/espressoapi/Detox'); +const EspressoDetoxApi = require('../../../../../android/espressoapi/EspressoDetox'); +const UiDeviceProxy = require('../../../../../android/espressoapi/UiDeviceProxy'); +// const temporaryPath = require('../../../../../artifacts/utils/temporaryPath'); +// const DetoxRuntimeError = require('../../../../../errors/DetoxRuntimeError'); +// const getAbsoluteBinaryPath = require('../../../../../utils/getAbsoluteBinaryPath'); +const logger = require('../../../../../utils/logger'); +// const pressAnyKey = require('../../../../../utils/pressAnyKey'); +// const retry = require('../../../../../utils/retry'); +const sleep = require('../../../../../utils/sleep'); +// const apkUtils = require('../../../../common/drivers/android/tools/apk'); +const DeviceDriverBase = require('../../DeviceDriverBase'); + +const log = logger.child({ cat: 'device' }); + +/** + * @typedef CloudAndroidDriverProps + * @property adbName { String } The unique identifier associated with ADB + */ + +/** + * @typedef { DeviceDriverDeps } CloudAndroidDriverDeps + * @property invocationManager { InvocationManager } + * @property adb { ADB } + * @property aapt { AAPT } + * @property apkValidator { ApkValidator } + * @property fileTransfer { FileTransfer } + * @property appInstallHelper { AppInstallHelper } + * @property appUninstallHelper { AppUninstallHelper } + * @property devicePathBuilder { AndroidDevicePathBuilder } + * @property instrumentation { MonitoredInstrumentation } + */ + +class CloudAndroidDriver extends DeviceDriverBase { + /** + * @param deps { CloudAndroidDriverDeps } + * @param props { CloudAndroidDriverProps } + */ + constructor(deps, { adbName }) { + super(deps); + + this.adbName = adbName; + this.adb = deps.adb; + this.aapt = deps.aapt; + this.apkValidator = deps.apkValidator; + this.invocationManager = deps.invocationManager; + this.fileTransfer = deps.fileTransfer; + // this.appInstallHelper = deps.appInstallHelper; + // this.appUninstallHelper = deps.appUninstallHelper; + this.devicePathBuilder = deps.devicePathBuilder; + this.instrumentation = deps.instrumentation; + + this.uiDevice = new UiDeviceProxy(this.invocationManager).getUIDevice(); + } + + // getExternalId() { + // return this.adbName; + // } + + // async getBundleIdFromBinary(apkPath) { + // const binaryPath = getAbsoluteBinaryPath(apkPath); + // return await this.aapt.getPackageName(binaryPath); + // } + + // async installApp(_appBinaryPath, _testBinaryPath) { + // const { + // appBinaryPath, + // testBinaryPath, + // } = this._getAppInstallPaths(_appBinaryPath, _testBinaryPath); + // await this._validateAppBinaries(appBinaryPath, testBinaryPath); + // await this._installAppBinaries(appBinaryPath, testBinaryPath); + // } + + // async uninstallApp(bundleId) { + // await this.emitter.emit('beforeUninstallApp', { deviceId: this.adbName, bundleId }); + // await this.appUninstallHelper.uninstall(this.adbName, bundleId); + // } + + // async installUtilBinaries(paths) { + // for (const path of paths) { + // const packageId = await this.getBundleIdFromBinary(path); + // if (!await this.adb.isPackageInstalled(this.adbName, packageId)) { + // await this.appInstallHelper.install(this.adbName, path); + // } + // } + // } + + async launchApp(bundleId, launchArgs, languageAndLocale) { + return await this._handleLaunchApp({ + manually: false, + bundleId, + launchArgs, + languageAndLocale, + }); + } + + // async waitForAppLaunch(bundleId, launchArgs, languageAndLocale) { + // return await this._handleLaunchApp({ + // manually: true, + // bundleId, + // launchArgs, + // languageAndLocale, + // }); + // } + + async _handleLaunchApp({ manually, bundleId, launchArgs }) { + const { adbName } = this; + + await this.emitter.emit('beforeLaunchApp', { bundleId, launchArgs }); + + // launchArgs = await this._modifyArgsForNotificationHandling(adbName, bundleId, launchArgs); + + // if (manually) { + // await this._waitForAppLaunch(adbName, bundleId, launchArgs); + // } else { + const response = await this._launchApp(adbName, bundleId, launchArgs); + // } + // _.get(deviceConfig, 'device.name') + const pid = _.get(response, 'response.success'); + + await this.emitter.emit('launchApp', { deviceId: adbName, bundleId, launchArgs, pid }); + return pid; + } + + // On product + async deliverPayload(params) { + if (params.delayPayload) { + return; + } + + const { url, detoxUserNotificationDataURL } = params; + if (url) { + await this._startActivityWithUrl(url); + } else if (detoxUserNotificationDataURL) { + const payloadPathOnDevice = await this._sendNotificationDataToDevice(detoxUserNotificationDataURL, this.adbName); + await this._startActivityFromNotification(payloadPathOnDevice); + } + } + + // Check this + async waitUntilReady() { + try { + await Promise.race([ + super.waitUntilReady(), + this.instrumentation.waitForCrash() + ]); + } catch (e) { + log.warn({ error: e }, 'An error occurred while waiting for the app to become ready. Waiting for disconnection...'); + await this.client.waitUntilDisconnected(); + log.warn('The app disconnected.'); + throw e; + } finally { + this.instrumentation.abortWaitForCrash(); + } + } + + async pressBack() { + await this.uiDevice.pressBack(); + } + + async sendToHome(params) { + await this.uiDevice.pressHome(); + } + + // Cannot find initialization https://github.com/pb2323/cloud_detox_support/blob/2382f0b3b0d86c0572dcfa915b412c12c8ace4f0/detox/src/android/core/WebElement.js#L39 + async typeText(text) { + await this.adb.typeText(this.adbName, text); + } + + // Check this + // async terminate(bundleId) { + // // const { adbName } = this; + // await this.emitter.emit('beforeTerminateApp', { bundleId }); + // await this._terminateInstrumentation(); + // await this.adb.terminate(adbName, bundleId); + // await this.emitter.emit('terminateApp', { deviceId: adbName, bundleId }); + // } + + // Check this + async cleanup(bundleId) { + await this._terminateInstrumentation(); + await super.cleanup(bundleId); + } + + getPlatform() { + return 'android'; + } + + getUiDevice() { + return this.uiDevice; + } + + // async reverseTcpPort(port) { + // await this.adb.reverse(this.adbName, port); + // } + + // async unreverseTcpPort(port) { + // await this.adb.reverseRemove(this.adbName, port); + // } + + async setURLBlacklist(urlList) { + await this.invocationManager.execute(EspressoDetoxApi.setURLBlacklist(urlList)); + } + + async enableSynchronization() { + await this.invocationManager.execute(EspressoDetoxApi.setSynchronization(true)); + } + + async disableSynchronization() { + await this.invocationManager.execute(EspressoDetoxApi.setSynchronization(false)); + } + + // Check this + async takeScreenshot(screenshotName) { + // const { adbName } = this; + + // const pathOnDevice = this.devicePathBuilder.buildTemporaryArtifactPath('.png'); + // await this.adb.screencap(adbName, pathOnDevice); + + // const tempPath = temporaryPath.for.png(); + // await this.adb.pull(adbName, pathOnDevice, tempPath); + // await this.adb.rm(adbName, pathOnDevice); + + await this.emitter.emit('createExternalArtifact', { + pluginId: 'screenshot', + artifactName: screenshotName || '' + // artifactPath: tempPath, + }); + + return ''; + } + + async setOrientation(orientation) { + const orientationMapping = { + landscape: 1, // top at left side landscape + portrait: 0 // non-reversed portrait. + }; + + const call = EspressoDetoxApi.changeOrientation(orientationMapping[orientation]); + await this.invocationManager.execute(call); + } + + // _getAppInstallPaths(_appBinaryPath, _testBinaryPath) { + // const appBinaryPath = getAbsoluteBinaryPath(_appBinaryPath); + // const testBinaryPath = _testBinaryPath ? getAbsoluteBinaryPath(_testBinaryPath) : this._getTestApkPath(appBinaryPath); + // return { + // appBinaryPath, + // testBinaryPath, + // }; + // } + + // async _validateAppBinaries(appBinaryPath, testBinaryPath) { + // try { + // await this.apkValidator.validateAppApk(appBinaryPath); + // } catch (e) { + // logger.warn(e.toString()); + // } + + // try { + // await this.apkValidator.validateTestApk(testBinaryPath); + // } catch (e) { + // logger.warn(e.toString()); + // } + // } + + // async _installAppBinaries(appBinaryPath, testBinaryPath) { + // await this.adb.install(this.adbName, appBinaryPath); + // await this.adb.install(this.adbName, testBinaryPath); + // } + + // _getTestApkPath(originalApkPath) { + // const testApkPath = apkUtils.getTestApkPath(originalApkPath); + + // if (!fs.existsSync(testApkPath)) { + // throw new DetoxRuntimeError({ + // message: `The test APK could not be found at path: '${testApkPath}'`, + // hint: 'Try running the detox build command, and make sure it was configured to execute a build command (e.g. \'./gradlew assembleAndroidTest\')' + + // '\nFor further assistance, visit the Android setup guide: https://github.com/wix/Detox/blob/master/docs/Introduction.Android.md', + // }); + // } + // return testApkPath; + // } + + // async _modifyArgsForNotificationHandling(adbName, bundleId, launchArgs) { + // let _launchArgs = launchArgs; + // if (launchArgs.detoxUserNotificationDataURL) { + // const notificationPayloadTargetPath = await this._sendNotificationDataToDevice(launchArgs.detoxUserNotificationDataURL, adbName); + // _launchArgs = { + // ...launchArgs, + // detoxUserNotificationDataURL: notificationPayloadTargetPath, + // }; + // } + // return _launchArgs; + // } + + async _launchApp(adbName, bundleId, launchArgs) { + if (!this.instrumentation.isRunning()) { + // await this._launchInstrumentationProcess(adbName, bundleId, launchArgs); + // launchArgs can be sent as per product + await this.invocationManager.executeCloudPlatform({ + 'method': 'launchApp', + 'args': { + 'bundleId': bundleId, + 'launchArgs': launchArgs + } + }); + await sleep(500); + } else if (launchArgs.detoxURLOverride) { + await this._startActivityWithUrl(launchArgs.detoxURLOverride); + } else if (launchArgs.detoxUserNotificationDataURL) { + await this._startActivityFromNotification(launchArgs.detoxUserNotificationDataURL); + } else { + await this._resumeMainActivity(); + } + } + + // async _launchInstrumentationProcess(adbName, bundleId, userLaunchArgs) { + // const serverPort = await this._reverseServerPort(adbName); + // this.instrumentation.setTerminationFn(async () => { + // await this._terminateInstrumentation(); + // await this.adb.reverseRemove(adbName, serverPort); + // }); + // await this.instrumentation.launch(adbName, bundleId, userLaunchArgs); + // } + + // async _reverseServerPort(adbName) { + // const serverPort = new URL(this.client.serverUrl).port; + // await this.adb.reverse(adbName, serverPort); + // return serverPort; + // } + + async _terminateInstrumentation() { + await this.instrumentation.terminate(); + await this.instrumentation.setTerminationFn(null); + } + + async _sendNotificationDataToDevice(dataFileLocalPath, adbName) { + await this.fileTransfer.prepareDestinationDir(adbName); + return await this.fileTransfer.send(adbName, dataFileLocalPath, 'notification.json'); + } + + _startActivityWithUrl(url) { + return this.invocationManager.execute(DetoxApi.startActivityFromUrl(url)); + } + + _startActivityFromNotification(dataFilePath) { + return this.invocationManager.execute(DetoxApi.startActivityFromNotification(dataFilePath)); + } + + _resumeMainActivity() { + return this.invocationManager.execute(DetoxApi.launchMainActivity()); + } + + // async _waitForProcess(adbName, bundleId) { + // let pid = NaN; + // try { + // const queryPid = () => this._queryPID(adbName, bundleId); + // const retryQueryPid = () => retry({ backoff: 'none', retries: 4 }, queryPid); + // const retryQueryPidMultiple = () => retry({ backoff: 'linear' }, retryQueryPid); + // pid = await retryQueryPidMultiple(); + // } catch (e) { + // // Show generic message sent by hub + // log.warn(await this.adb.shell(adbName, 'ps')); + // throw e; + // } + // return pid; + // } + + // async _queryPID(adbName, bundleId) { + // // const pid = await this.adb.pidof(adbName, bundleId); + // // This should be a http request + // // "/Users/puneetbajaj/Library/Android/sdk/platform-tools/adb" -s RFCR90M7DPD shell "ps | grep \"com\.detox\.rn\.example$\"" + // const pid = await this.invocationManager.executeCloudAdb({ + // 'method': 'pidOf', + // 'target': { + // 'type': 'ps', + // 'params': { + // 'method': 'instrument', + // 'args': [ + // '' + // ] + // } + // } + // }); + // if (!pid) { + // throw new DetoxRuntimeError('PID still not available'); + // } + // return pid; + // } + + // async _waitForAppLaunch(adbName, bundleId, launchArgs) { + // const instrumentationClass = await this.adb.getInstrumentationRunner(adbName, bundleId); + // this._printInstrumentationHint({ instrumentationClass, launchArgs }); + // await pressAnyKey(); + // await this._reverseServerPort(adbName); + // } + + // _printInstrumentationHint({ instrumentationClass, launchArgs }) { + // const keyMaxLength = Math.max(3, _(launchArgs).keys().maxBy('length').length); + // const valueMaxLength = Math.max(5, _(launchArgs).values().map(String).maxBy('length').length); + // const rows = _.map(launchArgs, (v, k) => { + // const paddedKey = k.padEnd(keyMaxLength, ' '); + // const paddedValue = `${v}`.padEnd(valueMaxLength, ' '); + // return `${paddedKey} | ${paddedValue}`; + // }); + + // const keyHeader = 'Key'.padEnd(keyMaxLength, ' '); + // const valueHeader = 'Value'.padEnd(valueMaxLength, ' '); + // const header = `${keyHeader} | ${valueHeader}`; + // const separator = '-'.repeat(header.length); + + // log.info({}, + // 'Waiting for you to manually launch your app in Android Studio.\n\n' + + // `Instrumentation class: ${instrumentationClass}\n` + + // 'Instrumentation arguments:\n' + + // `${separator}\n` + + // `${header}\n` + + // `${separator}\n` + + // `${rows.join('\n')}\n` + + // `${separator}\n\n` + + // 'Press any key to continue...' + // ); + // } +} + +module.exports = CloudAndroidDriver; diff --git a/detox/src/devices/runtime/factories/android.js b/detox/src/devices/runtime/factories/android.js index b666f83ca1..4d83b8cb2c 100644 --- a/detox/src/devices/runtime/factories/android.js +++ b/detox/src/devices/runtime/factories/android.js @@ -66,10 +66,10 @@ class Genycloud extends RuntimeDriverFactoryAndroid { class Noop extends RuntimeDriverFactoryAndroid { _createDriver(deviceCookie, deps, configs) { const props = { - adbName: deviceCookie.adbName, + adbName: undefined, }; - const AndroidDriver = require('../drivers/android/AndroidDriver'); - return new AndroidDriver(deps, props); + const CloudAndroidDriver = require('../drivers/android/cloud/cloudAndroidDriver'); + return new CloudAndroidDriver(deps, props); } } diff --git a/detox/src/invoke.js b/detox/src/invoke.js index 5d0b9eab51..10f9fd3d23 100644 --- a/detox/src/invoke.js +++ b/detox/src/invoke.js @@ -11,6 +11,14 @@ class InvocationManager { async execute(invocation) { return await this.executionHandler.execute(invocation); } + + // async executeCloudAdb(invocation) { + // return await this.executionHandler.waitForCloudAdb(invocation); + // } + + async executeCloudPlatform(invocation) { + return await this.executionHandler.waitForCloudPlatform(invocation); + } } module.exports = { From dad3cac8e8df6075aa4282bc41742bc00d15921c Mon Sep 17 00:00:00 2001 From: Puneet Bajaj Date: Fri, 17 Feb 2023 18:27:04 +0530 Subject: [PATCH 3/9] add cloudArtifactsManager and fix response parsing for cloudPlatform api --- detox/src/artifacts/CloudArtifactsManager.js | 235 ++---------------- detox/src/artifacts/factories/index.js | 2 +- detox/src/client/actions/actions.js | 9 +- .../android/cloud/cloudAndroidDriver.js | 12 +- detox/src/invoke.js | 13 +- 5 files changed, 34 insertions(+), 237 deletions(-) diff --git a/detox/src/artifacts/CloudArtifactsManager.js b/detox/src/artifacts/CloudArtifactsManager.js index b9952eaea7..ba9fab3278 100644 --- a/detox/src/artifacts/CloudArtifactsManager.js +++ b/detox/src/artifacts/CloudArtifactsManager.js @@ -1,275 +1,76 @@ -// const EventEmitter = require('events'); -// const path = require('path'); - -// const fs = require('fs-extra'); -const _ = require('lodash'); - -const DetoxRuntimeError = require('../errors/DetoxRuntimeError'); -const log = require('../utils/logger').child({ cat: 'artifacts-manager,artifact' }); -// const resolveModuleFromPath = require('../utils/resolveModuleFromPath'); -// const traceMethods = require('../utils/traceMethods'); - -const ArtifactsManager = require('./ArtifactsManager'); -const FileArtifact = require('./templates/artifact/FileArtifact'); -// const ArtifactPathBuilder = require('./utils/ArtifactPathBuilder'); - -class CloudArtifactsManager extends ArtifactsManager { +class CloudArtifactsManager { constructor() { - super(); - - // this._pluginConfigs = plugins; this._idlePromise = Promise.resolve(); - // this._idleCallbackRequests = []; this._artifactPlugins = {}; - // this._pathBuilder = this._resolveArtifactsPathBuilder(pathBuilder, rootDir); - - // traceMethods(log, this, [ - // 'onAppReady', - // 'onBeforeCleanup', - // 'onBeforeLaunchApp', - // 'onBeforeShutdownDevice', - // 'onBeforeTerminateApp', - // 'onBeforeUninstallApp', - // 'onBootDevice', - // 'onCreateExternalArtifact', - // 'onHookFailure', - // 'onLaunchApp', - // 'onRunDescribeFinish', - // 'onRunDescribeStart', - // 'onShutdownDevice', - // 'onTerminateApp', - // 'onTestDone', - // 'onTestFnFailure', - // 'onTestStart', - // ]); } -// _resolveArtifactsPathBuilder(pathBuilder, rootDir) { -// if (typeof pathBuilder === 'string') { -// pathBuilder = resolveModuleFromPath(pathBuilder); -// } - -// if (typeof pathBuilder === 'function') { -// try { -// pathBuilder = pathBuilder({ rootDir }); -// } catch (e) { -// pathBuilder = new pathBuilder({ rootDir }); -// } -// } - -// if (!pathBuilder) { -// pathBuilder = new ArtifactPathBuilder({ rootDir }); -// } - -// return pathBuilder; -// } - -// _instantiateArtifactPlugin(pluginFactory, pluginUserConfig) { -// const artifactsApi = { -// plugin: null, - -// userConfig: { ...pluginUserConfig }, - -// preparePathForArtifact: async (artifactName, testSummary) => { -// const artifactPath = this._pathBuilder.buildPathForTestArtifact(artifactName, testSummary); -// const artifactDir = path.dirname(artifactPath); -// await fs.ensureDir(artifactDir); - -// return artifactPath; -// }, - -// trackArtifact: (artifact) => { -// this.emit('trackArtifact', artifact); -// }, - -// untrackArtifact: (artifact) => { -// this.emit('untrackArtifact', artifact); -// }, - -// requestIdleCallback: (callback) => { -// this._idleCallbackRequests.push({ -// caller: artifactsApi.plugin, -// callback, -// }); - -// this._idlePromise = this._idlePromise.then(() => { -// const nextCallbackRequest = this._idleCallbackRequests.shift(); - -// /* istanbul ignore else */ -// if (nextCallbackRequest) { -// return this._executeIdleCallbackRequest(nextCallbackRequest); -// } -// }); - -// return this._idlePromise; -// }, -// }; - -// const plugin = pluginFactory(artifactsApi); -// artifactsApi.plugin = plugin; - -// return plugin; -// } - -// _executeIdleCallbackRequest({ callback, caller }) { -// return Promise.resolve() -// .then(callback) -// .catch(e => this._idleCallbackErrorHandle(e, caller)); -// } - -// registerArtifactPlugins(artifactPluginFactoriesMap) { -// for (const [key, factory] of Object.entries(artifactPluginFactoriesMap)) { -// const config = this._pluginConfigs[key]; -// this._artifactPlugins[key] = this._instantiateArtifactPlugin(factory, config); -// } -// } - -// subscribeToDeviceEvents(deviceEmitter) { -// deviceEmitter.on('bootDevice', this.onBootDevice.bind(this)); -// deviceEmitter.on('beforeShutdownDevice', this.onBeforeShutdownDevice.bind(this)); -// deviceEmitter.on('shutdownDevice', this.onShutdownDevice.bind(this)); -// deviceEmitter.on('beforeLaunchApp', this.onBeforeLaunchApp.bind(this)); -// deviceEmitter.on('launchApp', this.onLaunchApp.bind(this)); -// deviceEmitter.on('appReady', this.onAppReady.bind(this)); -// deviceEmitter.on('beforeUninstallApp', this.onBeforeUninstallApp.bind(this)); -// deviceEmitter.on('beforeTerminateApp', this.onBeforeTerminateApp.bind(this)); -// deviceEmitter.on('terminateApp', this.onTerminateApp.bind(this)); -// deviceEmitter.on('createExternalArtifact', this.onCreateExternalArtifact.bind(this)); -// } - async onBootDevice(deviceInfo) { - await this._callPlugins('plain', 'onBootDevice', deviceInfo); + return this._idlePromise; } async onBeforeLaunchApp(appLaunchInfo) { - await this._callPlugins('plain', 'onBeforeLaunchApp', appLaunchInfo); + return this._idlePromise; } async onLaunchApp(appLaunchInfo) { - await this._callPlugins('plain', 'onLaunchApp', appLaunchInfo); + return this._idlePromise; } async onAppReady(appInfo) { - await this._callPlugins('plain', 'onAppReady', appInfo); + return this._idlePromise; } async onBeforeTerminateApp(appInfo) { - await this._callPlugins('plain', 'onBeforeTerminateApp', appInfo); + return this._idlePromise; } async onTerminateApp(appInfo) { - await this._callPlugins('plain', 'onTerminateApp', appInfo); + return this._idlePromise; } async onBeforeUninstallApp(appInfo) { - await this._callPlugins('plain', 'onBeforeUninstallApp', appInfo); + return this._idlePromise; } async onBeforeShutdownDevice(deviceInfo) { - await this._callPlugins('plain', 'onBeforeShutdownDevice', deviceInfo); + return this._idlePromise; } async onShutdownDevice(deviceInfo) { - await this._callPlugins('plain', 'onShutdownDevice', deviceInfo); + return this._idlePromise; } async onCreateExternalArtifact({ pluginId, artifactName, artifactPath }) { - await this._callSinglePlugin(pluginId, 'onCreateExternalArtifact', { - artifact: new FileArtifact({ temporaryPath: artifactPath }), - name: artifactName, - }); + return this._idlePromise; } async onRunDescribeStart(suite) { - await this._callPlugins('ascending', 'onRunDescribeStart', suite); + return this._idlePromise; } async onTestStart(testSummary) { - await this._callPlugins('ascending', 'onTestStart', testSummary); + return this._idlePromise; } async onHookFailure(testSummary) { - await this._callPlugins('plain', 'onHookFailure', testSummary); + return this._idlePromise; } async onTestFnFailure(testSummary) { - await this._callPlugins('plain', 'onTestFnFailure', testSummary); + return this._idlePromise; } async onTestDone(testSummary) { - await this._callPlugins('descending', 'onTestDone', testSummary); + return this._idlePromise; } async onRunDescribeFinish(suite) { - await this._callPlugins('descending', 'onRunDescribeFinish', suite); + return this._idlePromise; } async onBeforeCleanup() { - await this._callPlugins('descending', 'onBeforeCleanup'); - await this._idlePromise; - } - - async _callSinglePlugin(pluginId, methodName, ...args) { - const plugin = this._artifactPlugins[pluginId]; - try { - await plugin[methodName](...args); - } catch (e) { - this._unhandledPluginExceptionHandler(e, { plugin, methodName }); - } - } - - async _callPlugins(strategy, methodName, ...args) { - for (const pluginGroup of this._groupPlugins(strategy)) { - await Promise.all(pluginGroup.map(async (plugin) => { - try { - await plugin[methodName](...args); - } catch (e) { - this._unhandledPluginExceptionHandler(e, { plugin, methodName }); - } - })); - } - } - - _groupPlugins(strategy) { - if (strategy === 'plain') { - return [_.values(this._artifactPlugins)]; - } - - const pluginsByPriority = _.chain(this._artifactPlugins) - .values() - .groupBy('priority') - .entries() - .sortBy(([priority]) => Number(priority)) - .map(1) - .value(); - - switch (strategy) { - case 'descending': - return pluginsByPriority.reverse(); - case 'ascending': - return pluginsByPriority; - /* istanbul ignore next */ - default: // is - throw new DetoxRuntimeError(`Unknown plugins grouping strategy: ${strategy}`); - } + return this._idlePromise; } - - _unhandledPluginExceptionHandler(err, { plugin, methodName }) { - const logObject = { - plugin: plugin.name, - methodName, - err, - }; - - log.warn(logObject, `Suppressed error inside function call.`); - } - -// _idleCallbackErrorHandle(err, caller) { -// this._unhandledPluginExceptionHandler(err, { -// plugin: caller, -// methodName: 'onIdleCallback', -// }); -// } } module.exports = CloudArtifactsManager; diff --git a/detox/src/artifacts/factories/index.js b/detox/src/artifacts/factories/index.js index 13dfbbb667..b7d90f1c22 100644 --- a/detox/src/artifacts/factories/index.js +++ b/detox/src/artifacts/factories/index.js @@ -53,7 +53,7 @@ class Noop extends ArtifactsManagerFactory { super(new EmptyProvider()); } createArtifactsManager(artifactsConfig) { - const artifactsManager = new CloudArtifactsManager(artifactsConfig); + const artifactsManager = new CloudArtifactsManager(); return artifactsManager; } } diff --git a/detox/src/client/actions/actions.js b/detox/src/client/actions/actions.js index bccd194470..f9cfaa7a1d 100644 --- a/detox/src/client/actions/actions.js +++ b/detox/src/client/actions/actions.js @@ -41,7 +41,7 @@ class Login extends Action { } get timeout() { - return 1000; + return 240000; } async handle(response) { @@ -194,6 +194,13 @@ class CloudPlatform extends Action { async handle(response) { this.expectResponseOfType(response, 'CloudPlatform'); + const json = JSON.parse(response); + const status = json && json.response && json.response.success && json.response.success.toString() === 'true'; + const log = logger.child({ cat: 'device' }); + if (!status) { + log.warn(json.response.message); + } + return response; } } diff --git a/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js b/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js index eb1c08c916..8c0a4a2c01 100644 --- a/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js +++ b/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js @@ -84,9 +84,7 @@ class CloudAndroidDriver extends DeviceDriverBase { await this._terminateInstrumentation(); } - // Check this async cleanup(bundleId) { - await this._terminateInstrumentation(bundleId); await super.cleanup(bundleId); } @@ -106,7 +104,7 @@ class CloudAndroidDriver extends DeviceDriverBase { await this.invocationManager.execute(EspressoDetoxApi.setSynchronization(false)); } - // Check this + // Throw error if api fails async takeScreenshot(screenshotName) { await this.invocationManager.executeCloudPlatform({ 'method': 'screenshot', @@ -136,10 +134,11 @@ class CloudAndroidDriver extends DeviceDriverBase { 'launchArgs': launchArgs } }); - const status = _.get(response, 'response.success'); + const json = JSON.parse(response); + const status = _.get(json, 'response.success'); this.instrumentation = status; - if(status.toString() === 'false') - throw new DetoxRuntimeError({ error: _.get(response, 'response.message') }); + if(!status || status.toString() === 'false') + throw new DetoxRuntimeError(_.get(response, 'response.message')); } else if (launchArgs.detoxURLOverride) { await this._startActivityWithUrl(launchArgs.detoxURLOverride); } else { @@ -147,6 +146,7 @@ class CloudAndroidDriver extends DeviceDriverBase { } } + // Do we want to throw error if terminate app fails async _terminateInstrumentation(bundleId) { await this.invocationManager.executeCloudPlatform({ 'method': 'terminateApp', diff --git a/detox/src/invoke.js b/detox/src/invoke.js index cb0c5db63e..10f9fd3d23 100644 --- a/detox/src/invoke.js +++ b/detox/src/invoke.js @@ -1,13 +1,7 @@ -const _ = require('lodash'); - -// const DetoxRuntimeError = require('./errors/DetoxRuntimeError'); const EarlGrey = require('./invoke/EarlGrey'); const Espresso = require('./invoke/Espresso'); const EspressoWeb = require('./invoke/EspressoWeb'); const Invoke = require('./invoke/Invoke'); -const logger = require('./utils/logger'); - -const log = logger.child({ cat: 'device' }); class InvocationManager { constructor(excutionHandler) { @@ -23,12 +17,7 @@ class InvocationManager { // } async executeCloudPlatform(invocation) { - const response = await this.executionHandler.waitForCloudPlatform(invocation); - const status = _.get(response, 'response.success'); - if (status.toString() === 'false') { - log.warn('An error occurred while waiting for response from cloud'); - } - return response; + return await this.executionHandler.waitForCloudPlatform(invocation); } } From 02d3cb614ed8f79b29bacb5367a01752fef99296 Mon Sep 17 00:00:00 2001 From: Puneet Bajaj Date: Mon, 20 Feb 2023 18:09:55 +0530 Subject: [PATCH 4/9] fix terminate app api json parsing bug and increase currentStatus timeout --- detox/src/client/actions/actions.js | 8 +------- .../runtime/drivers/android/cloud/cloudAndroidDriver.js | 6 +++--- 2 files changed, 4 insertions(+), 10 deletions(-) diff --git a/detox/src/client/actions/actions.js b/detox/src/client/actions/actions.js index f9cfaa7a1d..a89f6a7359 100644 --- a/detox/src/client/actions/actions.js +++ b/detox/src/client/actions/actions.js @@ -194,12 +194,6 @@ class CloudPlatform extends Action { async handle(response) { this.expectResponseOfType(response, 'CloudPlatform'); - const json = JSON.parse(response); - const status = json && json.response && json.response.success && json.response.success.toString() === 'true'; - const log = logger.child({ cat: 'device' }); - if (!status) { - log.warn(json.response.message); - } return response; } } @@ -308,7 +302,7 @@ class CurrentStatus extends Action { } get timeout() { - return 5000; + return 10000; } async handle(response) { diff --git a/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js b/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js index 8c0a4a2c01..cc6af80b24 100644 --- a/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js +++ b/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js @@ -81,7 +81,7 @@ class CloudAndroidDriver extends DeviceDriverBase { // } async terminate(bundleId) { - await this._terminateInstrumentation(); + return await this._terminateInstrumentation(); } async cleanup(bundleId) { @@ -134,7 +134,7 @@ class CloudAndroidDriver extends DeviceDriverBase { 'launchArgs': launchArgs } }); - const json = JSON.parse(response); + const json = response; const status = _.get(json, 'response.success'); this.instrumentation = status; if(!status || status.toString() === 'false') @@ -148,7 +148,7 @@ class CloudAndroidDriver extends DeviceDriverBase { // Do we want to throw error if terminate app fails async _terminateInstrumentation(bundleId) { - await this.invocationManager.executeCloudPlatform({ + return await this.invocationManager.executeCloudPlatform({ 'method': 'terminateApp', 'args': {} }); From 931ccc4969dbc392cadbbeca5427d84fb988950a Mon Sep 17 00:00:00 2001 From: Puneet Bajaj Date: Thu, 23 Feb 2023 17:09:25 +0530 Subject: [PATCH 5/9] create new session id for each session and remove platform api response parsing for status extraction --- detox/src/DetoxWorker.js | 2 +- .../drivers/android/cloud/cloudAndroidDriver.js | 15 +-------------- 2 files changed, 2 insertions(+), 15 deletions(-) diff --git a/detox/src/DetoxWorker.js b/detox/src/DetoxWorker.js index ce8ead0759..4689b0f28d 100644 --- a/detox/src/DetoxWorker.js +++ b/detox/src/DetoxWorker.js @@ -97,7 +97,7 @@ class DetoxWorker { this._deviceConfig = deviceConfig; this._sessionConfig = sessionConfig; // @ts-ignore - this._sessionConfig.sessionId = sessionConfig.sessionId || uuid.UUID(); + this._sessionConfig.sessionId = uuid.UUID(); this._runtimeErrorComposer.appsConfig = this._appsConfig; this._client = new Client(sessionConfig); diff --git a/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js b/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js index cc6af80b24..f2a0d4c933 100644 --- a/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js +++ b/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js @@ -5,7 +5,6 @@ const _ = require('lodash'); const DetoxApi = require('../../../../../android/espressoapi/Detox'); const EspressoDetoxApi = require('../../../../../android/espressoapi/EspressoDetox'); const UiDeviceProxy = require('../../../../../android/espressoapi/UiDeviceProxy'); -const DetoxRuntimeError = require('../../../../../errors/DetoxRuntimeError'); const logger = require('../../../../../utils/logger'); const DeviceDriverBase = require('../../DeviceDriverBase'); @@ -104,14 +103,7 @@ class CloudAndroidDriver extends DeviceDriverBase { await this.invocationManager.execute(EspressoDetoxApi.setSynchronization(false)); } - // Throw error if api fails async takeScreenshot(screenshotName) { - await this.invocationManager.executeCloudPlatform({ - 'method': 'screenshot', - 'args': { - 'name': screenshotName - } - }); return ''; } @@ -128,17 +120,12 @@ class CloudAndroidDriver extends DeviceDriverBase { async _launchApp( bundleId, launchArgs) { if (!this.instrumentation) { - const response = await this.invocationManager.executeCloudPlatform({ + await this.invocationManager.executeCloudPlatform({ 'method': 'launchApp', 'args': { 'launchArgs': launchArgs } }); - const json = response; - const status = _.get(json, 'response.success'); - this.instrumentation = status; - if(!status || status.toString() === 'false') - throw new DetoxRuntimeError(_.get(response, 'response.message')); } else if (launchArgs.detoxURLOverride) { await this._startActivityWithUrl(launchArgs.detoxURLOverride); } else { From 36ee9cda95b7934436389428a7ba2338a575aa21 Mon Sep 17 00:00:00 2001 From: Puneet Bajaj Date: Mon, 13 Mar 2023 19:31:03 +0530 Subject: [PATCH 6/9] reset currentStatus message timeout to default 5000s --- detox/src/client/actions/actions.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/detox/src/client/actions/actions.js b/detox/src/client/actions/actions.js index a89f6a7359..7c2b87090a 100644 --- a/detox/src/client/actions/actions.js +++ b/detox/src/client/actions/actions.js @@ -302,7 +302,7 @@ class CurrentStatus extends Action { } get timeout() { - return 10000; + return 5000; } async handle(response) { From 4e635dc3e75646aed94d60b170b4bdbf9473c78e Mon Sep 17 00:00:00 2001 From: Puneet Bajaj Date: Wed, 15 Mar 2023 15:11:30 +0530 Subject: [PATCH 7/9] track instrumentation process based on cloudPlatform api response --- .../runtime/drivers/android/cloud/cloudAndroidDriver.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js b/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js index f2a0d4c933..ea18535e7d 100644 --- a/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js +++ b/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js @@ -120,12 +120,14 @@ class CloudAndroidDriver extends DeviceDriverBase { async _launchApp( bundleId, launchArgs) { if (!this.instrumentation) { - await this.invocationManager.executeCloudPlatform({ + const response = await this.invocationManager.executeCloudPlatform({ 'method': 'launchApp', 'args': { 'launchArgs': launchArgs } }); + const status = _.get(response, 'response.success'); + this.instrumentation = status && status.toString() == 'true'; } else if (launchArgs.detoxURLOverride) { await this._startActivityWithUrl(launchArgs.detoxURLOverride); } else { @@ -135,10 +137,12 @@ class CloudAndroidDriver extends DeviceDriverBase { // Do we want to throw error if terminate app fails async _terminateInstrumentation(bundleId) { - return await this.invocationManager.executeCloudPlatform({ + const response = await this.invocationManager.executeCloudPlatform({ 'method': 'terminateApp', 'args': {} }); + const status = _.get(response, 'response.success'); + this.instrumentation = !(status && status.toString() == 'true'); } _startActivityWithUrl(url) { From 08388330c5d85556930359d283fc870a9d39e709 Mon Sep 17 00:00:00 2001 From: Puneet Bajaj Date: Wed, 22 Mar 2023 16:49:50 +0530 Subject: [PATCH 8/9] remove unwanted comments --- detox/src/client/Client.js | 9 --------- detox/src/client/actions/actions.js | 18 ------------------ .../android/cloud/cloudAndroidDriver.js | 6 ------ detox/src/invoke.js | 4 ---- 4 files changed, 37 deletions(-) diff --git a/detox/src/client/Client.js b/detox/src/client/Client.js index 1bee0e844a..28936b67fa 100644 --- a/detox/src/client/Client.js +++ b/detox/src/client/Client.js @@ -260,15 +260,6 @@ class Client { } } - // async waitForCloudAdb(params) { - // try { - // return await this.sendAction(new actions.CloudAdb(params)); - // } catch (err) { - // this._successfulTestRun = false; - // throw err; - // } - // } - async terminateApp() { /* see the property injection from Detox.js */ } diff --git a/detox/src/client/actions/actions.js b/detox/src/client/actions/actions.js index 7c2b87090a..c9b7aaaf7e 100644 --- a/detox/src/client/actions/actions.js +++ b/detox/src/client/actions/actions.js @@ -198,23 +198,6 @@ class CloudPlatform extends Action { } } -// class CloudAdb extends Action { -// constructor(params) { -// super('CloudAdb', params); -// } - -// get isAtomic() { -// return true; -// } - -// get timeout() { -// return 90000; -// } - -// async handle(response) { -// this.expectResponseOfType(response, 'CloudAdb'); -// } -// } class Invoke extends Action { constructor(params) { @@ -373,6 +356,5 @@ module.exports = { SetOrientation, SetInstrumentsRecordingState, CaptureViewHierarchy, - // CloudAdb, CloudPlatform }; diff --git a/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js b/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js index ea18535e7d..6083fd479a 100644 --- a/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js +++ b/detox/src/devices/runtime/drivers/android/cloud/cloudAndroidDriver.js @@ -74,11 +74,6 @@ class CloudAndroidDriver extends DeviceDriverBase { await this.uiDevice.pressHome(); } - // Deep to come back - // async typeText(text) { - // await this.adb.typeText(this.adbName, text); - // } - async terminate(bundleId) { return await this._terminateInstrumentation(); } @@ -135,7 +130,6 @@ class CloudAndroidDriver extends DeviceDriverBase { } } - // Do we want to throw error if terminate app fails async _terminateInstrumentation(bundleId) { const response = await this.invocationManager.executeCloudPlatform({ 'method': 'terminateApp', diff --git a/detox/src/invoke.js b/detox/src/invoke.js index 10f9fd3d23..61b302d1ae 100644 --- a/detox/src/invoke.js +++ b/detox/src/invoke.js @@ -12,10 +12,6 @@ class InvocationManager { return await this.executionHandler.execute(invocation); } - // async executeCloudAdb(invocation) { - // return await this.executionHandler.waitForCloudAdb(invocation); - // } - async executeCloudPlatform(invocation) { return await this.executionHandler.waitForCloudPlatform(invocation); } From 7c319b9228c36aea9d0edc70ea840730211d04dc Mon Sep 17 00:00:00 2001 From: Puneet Bajaj Date: Thu, 23 Mar 2023 19:05:26 +0530 Subject: [PATCH 9/9] wait until app is disconnected during terminate app --- detox/src/client/Client.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/detox/src/client/Client.js b/detox/src/client/Client.js index 28936b67fa..88ba4141bf 100644 --- a/detox/src/client/Client.js +++ b/detox/src/client/Client.js @@ -253,7 +253,14 @@ class Client { async waitForCloudPlatform(params) { try { - return await this.sendAction(new actions.CloudPlatform(params)); + const response = await this.sendAction(new actions.CloudPlatform(params)); + if (params['method'] == 'terminateApp') { + await this.waitUntilDisconnected(); + } + // else if (params['method'] == 'launchApp') { + // this._onAppConnected(); + // } + return response; } catch (err) { this._successfulTestRun = false; throw err;