detox 21.0.0-rc.6 → 21.0.0-rc.8
Sign up to get free protection for your applications and to get access to all the features.
- package/.eslintignore +1 -0
- package/Detox-android/com/wix/detox/{21.0.0-rc.6/detox-21.0.0-rc.6-javadoc.jar → 21.0.0-rc.8/detox-21.0.0-rc.8-javadoc.jar} +0 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8-javadoc.jar.md5 +1 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8-javadoc.jar.sha1 +1 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8-javadoc.jar.sha256 +1 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8-javadoc.jar.sha512 +1 -0
- package/Detox-android/com/wix/detox/{21.0.0-rc.6/detox-21.0.0-rc.6-sources.jar → 21.0.0-rc.8/detox-21.0.0-rc.8-sources.jar} +0 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8-sources.jar.md5 +1 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8-sources.jar.sha1 +1 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8-sources.jar.sha256 +1 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8-sources.jar.sha512 +1 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.aar +0 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.aar.md5 +1 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.aar.sha1 +1 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.aar.sha256 +1 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.aar.sha512 +1 -0
- package/Detox-android/com/wix/detox/{21.0.0-rc.6/detox-21.0.0-rc.6.pom → 21.0.0-rc.8/detox-21.0.0-rc.8.pom} +1 -7
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.pom.md5 +1 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.pom.sha1 +1 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.pom.sha256 +1 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.pom.sha512 +1 -0
- package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
- package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
- package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
- package/Detox-ios-framework.tbz +0 -0
- package/Detox-ios-src.tbz +0 -0
- package/Detox-ios-xcuitest.tbz +0 -0
- package/android/detox/build.gradle +13 -8
- package/android/detox/src/full/java/com/wix/detox/espresso/DetoxAssertion.java +44 -25
- package/android/detox/src/full/java/com/wix/detox/espresso/EspressoDetox.java +6 -7
- package/android/detox/src/full/java/com/wix/detox/espresso/action/AdjustSliderToPositionAction.kt +2 -2
- package/android/detox/src/full/java/com/wix/detox/espresso/action/GetAttributesAction.kt +34 -35
- package/android/detox/src/full/java/com/wix/detox/espresso/common/MaterialSliderHelper.kt +21 -0
- package/android/detox/src/full/java/com/wix/detox/espresso/common/{SliderHelper.kt → ReactSliderHelper.kt} +6 -5
- package/android/detox/src/full/java/com/wix/detox/espresso/matcher/ViewMatchers.kt +2 -2
- package/android/detox/src/full/java/com/wix/detox/espresso/performer/MultipleViewsActionPerformer.kt +43 -0
- package/android/detox/src/full/java/com/wix/detox/espresso/performer/SingleViewActionPerformer.kt +19 -0
- package/android/detox/src/full/java/com/wix/detox/espresso/performer/ViewActionPerformer.kt +24 -0
- package/android/detox/src/full/java/com/wix/detox/espresso/web/WebElement.java +4 -4
- package/android/detox/src/full/java/com/wix/invoke/types/Invocation.java +7 -6
- package/android/detox/src/main/java/com/wix/detox/espresso/MultipleViewsAction.kt +4 -0
- package/android/detox/src/main/java/com/wix/detox/espresso/UiControllerSpy.kt +0 -1
- package/android/detox/src/main/java/com/wix/detox/espresso/ViewActionWithResult.kt +2 -1
- package/android/detox/src/main/java/com/wix/detox/espresso/action/common/MotionEvents.kt +60 -4
- package/android/detox/src/testFull/java/com/wix/detox/espresso/action/GetAttributesActionTest.kt +6 -5
- package/android/detox/src/testFull/java/com/wix/detox/espresso/common/MaterialSliderHelperTest.kt +33 -0
- package/android/detox/src/testFull/java/com/wix/detox/espresso/common/{SliderHelperTest.kt → ReactSliderHelperTest.kt} +3 -3
- package/android/detox/src/testFull/java/com/wix/detox/espresso/performer/ViewActionPerformerSpec.kt +37 -0
- package/android/detox/src/testFull/java/com/wix/invoke/JsonParserTest.java +23 -7
- package/android/detox/src/testFull/resources/targetInvocationEspressoWebDetoxScript.json +47 -0
- package/detox.d.ts +1840 -0
- package/globals.d.ts +23 -0
- package/index.d.ts +2 -1823
- package/internals.d.ts +11 -1
- package/jest.config.js +108 -0
- package/local-cli/reset-lock-file.js +5 -9
- package/local-cli/testCommand/TestRunnerCommand.js +26 -3
- package/local-cli/utils/interruptListeners.js +15 -0
- package/package.json +2 -100
- package/runners/jest/reporter.js +21 -1
- package/runners/jest/reporters/DetoxIPCReporter.js +34 -0
- package/runners/jest/reporters/DetoxReporterDispatcher.js +144 -0
- package/runners/jest/reporters/DetoxSummaryReporter.js +16 -0
- package/runners/jest/reporters/DetoxVerboseReporter.js +16 -0
- package/runners/jest/reporters/index.js +6 -0
- package/runners/jest/testEnvironment/index.js +11 -0
- package/src/DetoxWorker.js +5 -11
- package/src/android/core/NativeElement.js +26 -29
- package/src/android/core/WebElement.js +24 -6
- package/src/android/espressoapi/DetoxAssertion.js +16 -14
- package/src/android/espressoapi/EspressoDetox.js +9 -2
- package/src/android/espressoapi/web/WebElement.js +1 -4
- package/src/android/interactions/native.js +2 -3
- package/src/android/matchers/index.js +2 -1
- package/src/android/matchers/web.js +9 -1
- package/src/artifacts/providers/index.js +3 -3
- package/src/artifacts/screenshot/SimulatorScreenshotPlugin.js +0 -17
- package/src/configuration/composeLoggerConfig.js +1 -0
- package/src/configuration/composeRunnerConfig.js +3 -1
- package/src/devices/allocation/DeviceAllocator.js +66 -20
- package/src/devices/allocation/DeviceList.js +44 -0
- package/src/devices/allocation/DeviceRegistry.js +189 -0
- package/src/devices/allocation/drivers/AllocationDriverBase.d.ts +15 -0
- package/src/devices/{common/drivers/android/tools → allocation/drivers/android}/FreeDeviceFinder.js +11 -10
- package/src/devices/allocation/drivers/android/attached/AttachedAndroidAllocDriver.js +22 -17
- package/src/devices/allocation/drivers/android/emulator/EmulatorAllocDriver.js +97 -38
- package/src/devices/allocation/drivers/android/emulator/EmulatorLauncher.js +32 -45
- package/src/devices/allocation/drivers/android/emulator/FreeEmulatorFinder.js +1 -1
- package/src/devices/allocation/drivers/android/emulator/FreePortFinder.js +37 -0
- package/src/devices/allocation/drivers/android/emulator/launchEmulatorProcess.js +3 -3
- package/src/devices/allocation/drivers/android/genycloud/GenyAllocDriver.js +104 -32
- package/src/devices/allocation/drivers/android/genycloud/GenyInstanceLauncher.js +40 -31
- package/src/devices/allocation/drivers/android/genycloud/GenyRegistry.js +121 -0
- package/src/devices/allocation/drivers/android/genycloud/services/GenyInstanceLifecycleService.js +24 -0
- package/src/devices/{common → allocation}/drivers/android/genycloud/services/GenyRecipesService.js +1 -1
- package/src/devices/allocation/drivers/android/genycloud/services/dto/GenyInstance.js +83 -0
- package/src/devices/allocation/drivers/android/genycloud/services/dto/GenyRecipe.js +25 -0
- package/src/devices/allocation/drivers/ios/SimulatorAllocDriver.js +95 -54
- package/src/devices/allocation/drivers/ios/SimulatorQuery.js +24 -0
- package/src/devices/allocation/factories/android.js +29 -35
- package/src/devices/allocation/factories/ios.js +6 -7
- package/src/devices/common/drivers/DeviceCookie.d.ts +12 -0
- package/src/devices/common/drivers/android/cookies.d.ts +11 -0
- package/src/devices/common/drivers/android/emulator/exec/EmulatorExec.js +17 -5
- package/src/devices/common/drivers/android/exec/ADB.js +1 -0
- package/src/devices/common/drivers/android/tools/instrumentationArgs.js +7 -1
- package/src/devices/common/drivers/ios/cookies.d.ts +9 -0
- package/src/devices/common/drivers/ios/tools/AppleSimUtils.js +3 -1
- package/src/devices/cookies/index.js +0 -6
- package/src/devices/runtime/drivers/android/genycloud/GenyCloudDriver.js +7 -6
- package/src/devices/runtime/drivers/ios/SimulatorDriver.js +5 -4
- package/src/devices/runtime/factories/android.js +3 -11
- package/src/devices/runtime/factories/ios.js +3 -4
- package/src/{servicelocator → devices/servicelocator}/android/emulatorServiceLocator.js +1 -1
- package/src/devices/servicelocator/android/genycloudServiceLocator.js +17 -0
- package/src/devices/servicelocator/android/index.js +23 -0
- package/src/{validation → devices/validation}/EnvironmentValidatorBase.js +1 -0
- package/src/{validation → devices/validation}/android/GenycloudEnvValidator.js +2 -2
- package/src/{validation → devices/validation}/factories/index.js +1 -1
- package/src/{validation → devices/validation}/ios/IosSimulatorEnvValidator.js +2 -2
- package/src/environmentFactory.js +1 -11
- package/src/ios/web.js +27 -5
- package/src/ipc/IPCClient.js +22 -1
- package/src/ipc/IPCServer.js +42 -1
- package/src/ipc/SessionState.js +1 -0
- package/src/logger/DetoxLogger.js +2 -2
- package/src/realms/DetoxContext.js +8 -0
- package/src/realms/DetoxInternalsFacade.js +1 -0
- package/src/realms/DetoxPrimaryContext.js +48 -42
- package/src/realms/DetoxSecondaryContext.js +27 -0
- package/src/realms/symbols.js +6 -0
- package/src/utils/PIDService.js +27 -0
- package/src/utils/assertIsFunction.js +35 -0
- package/src/utils/environment.js +8 -15
- package/src/utils/errorUtils.js +3 -3
- package/src/utils/isArrowFunction.js +24 -0
- package/tsconfig.json +8 -3
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6-javadoc.jar.md5 +0 -1
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6-javadoc.jar.sha1 +0 -1
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6-javadoc.jar.sha256 +0 -1
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6-javadoc.jar.sha512 +0 -1
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6-sources.jar.md5 +0 -1
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6-sources.jar.sha1 +0 -1
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6-sources.jar.sha256 +0 -1
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6-sources.jar.sha512 +0 -1
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.aar +0 -0
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.aar.md5 +0 -1
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.aar.sha1 +0 -1
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.aar.sha256 +0 -1
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.aar.sha512 +0 -1
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.pom.md5 +0 -1
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.pom.sha1 +0 -1
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.pom.sha256 +0 -1
- package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.pom.sha512 +0 -1
- package/runners/jest/reporters/DetoxReporter.js +0 -36
- package/src/devices/DeviceRegistry.js +0 -176
- package/src/devices/allocation/drivers/AllocationDriverBase.js +0 -30
- package/src/devices/allocation/drivers/android/attached/AttachedAndroidLauncher.js +0 -13
- package/src/devices/allocation/drivers/android/emulator/EmulatorAllocationHelper.js +0 -72
- package/src/devices/allocation/drivers/android/genycloud/GenyDeviceRegistryFactory.js +0 -16
- package/src/devices/allocation/drivers/android/genycloud/GenyInstanceAllocationHelper.js +0 -65
- package/src/devices/allocation/drivers/ios/SimulatorLauncher.js +0 -21
- package/src/devices/common/drivers/DeviceAllocationHelper.js +0 -20
- package/src/devices/common/drivers/DeviceLauncher.js +0 -19
- package/src/devices/common/drivers/android/genycloud/services/GenyInstanceLifecycleService.js +0 -25
- package/src/devices/common/drivers/android/genycloud/services/GenyInstanceLookupService.js +0 -38
- package/src/devices/common/drivers/android/genycloud/services/GenyInstanceNaming.js +0 -14
- package/src/devices/common/drivers/android/genycloud/services/dto/GenyInstance.js +0 -66
- package/src/devices/common/drivers/android/genycloud/services/dto/GenyRecipe.js +0 -13
- package/src/devices/cookies/AndroidDeviceCookie.js +0 -13
- package/src/devices/cookies/AndroidEmulatorCookie.js +0 -6
- package/src/devices/cookies/AttachedAndroidDeviceCookie.js +0 -12
- package/src/devices/cookies/DeviceCookie.js +0 -4
- package/src/devices/cookies/GenycloudEmulatorCookie.js +0 -20
- package/src/devices/cookies/IosCookie.js +0 -6
- package/src/devices/cookies/IosSimulatorCookie.js +0 -10
- package/src/devices/lifecycle/GenyGlobalLifecycleHandler.js +0 -71
- package/src/devices/lifecycle/factories/GenyGlobalLifecycleHandlerFactory.js +0 -18
- package/src/servicelocator/android/genycloudServiceLocator.js +0 -21
- package/src/servicelocator/android/index.js +0 -25
- package/src/servicelocator/ios.js +0 -7
- /package/src/devices/{common → allocation}/drivers/android/genycloud/exec/GenyCloudExec.js +0 -0
- /package/src/devices/{common → allocation}/drivers/android/genycloud/services/GenyAuthService.js +0 -0
@@ -1,49 +1,53 @@
|
|
1
1
|
// @ts-nocheck
|
2
2
|
const { DetoxRuntimeError } = require('../../../../../errors');
|
3
|
-
const log = require('../../../../../utils/logger').child({ cat: 'device' });
|
4
3
|
const retry = require('../../../../../utils/retry');
|
5
|
-
const traceMethods = require('../../../../../utils/traceMethods');
|
6
|
-
const DeviceLauncher = require('../../../../common/drivers/DeviceLauncher');
|
7
4
|
const { LaunchCommand } = require('../../../../common/drivers/android/emulator/exec/EmulatorExec');
|
8
5
|
|
9
6
|
const { launchEmulatorProcess } = require('./launchEmulatorProcess');
|
10
7
|
|
11
8
|
const isUnknownEmulatorError = (err) => (err.message || '').includes('failed with code null');
|
12
9
|
|
13
|
-
class EmulatorLauncher
|
14
|
-
constructor({ adb, emulatorExec
|
15
|
-
super(eventEmitter);
|
10
|
+
class EmulatorLauncher {
|
11
|
+
constructor({ adb, emulatorExec }) {
|
16
12
|
this._adb = adb;
|
17
13
|
this._emulatorExec = emulatorExec;
|
18
|
-
traceMethods(log, this, ['_awaitEmulatorBoot']);
|
19
14
|
}
|
20
15
|
|
21
16
|
/**
|
22
|
-
* @param
|
23
|
-
* @param
|
24
|
-
* @param
|
25
|
-
* @param
|
26
|
-
* @param
|
27
|
-
* @param
|
28
|
-
* @param options.
|
29
|
-
* @param options.
|
30
|
-
* @param options.readonly { Boolean }
|
17
|
+
* @param {object} options
|
18
|
+
* @param {string} options.avdName
|
19
|
+
* @param {string} options.adbName
|
20
|
+
* @param {number} options.port
|
21
|
+
* @param {string | undefined} options.bootArgs
|
22
|
+
* @param {string | undefined} options.gpuMode
|
23
|
+
* @param {boolean} options.headless
|
24
|
+
* @param {boolean} options.readonly
|
31
25
|
*/
|
32
|
-
async launch(
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
26
|
+
async launch(options) {
|
27
|
+
const launchCommand = new LaunchCommand(options);
|
28
|
+
await retry({
|
29
|
+
retries: 2,
|
30
|
+
interval: 100,
|
31
|
+
conditionFn: isUnknownEmulatorError,
|
32
|
+
}, () => launchEmulatorProcess(this._emulatorExec, this._adb, launchCommand));
|
33
|
+
}
|
34
|
+
|
35
|
+
/**
|
36
|
+
* @param {string} adbName
|
37
|
+
*/
|
38
|
+
async awaitEmulatorBoot(adbName) {
|
39
|
+
await retry({ retries: 240, interval: 2500, shouldUnref: true }, async () => {
|
40
|
+
const isBootComplete = await this._adb.isBootComplete(adbName);
|
41
|
+
|
42
|
+
if (!isBootComplete) {
|
43
|
+
throw new DetoxRuntimeError({
|
44
|
+
message: `Waited for ${adbName} to complete booting for too long!`,
|
45
|
+
});
|
46
|
+
}
|
47
|
+
});
|
43
48
|
}
|
44
49
|
|
45
50
|
async shutdown(adbName) {
|
46
|
-
await this._notifyPreShutdown(adbName);
|
47
51
|
await this._adb.emuKill(adbName);
|
48
52
|
await retry({
|
49
53
|
retries: 5,
|
@@ -57,23 +61,6 @@ class EmulatorLauncher extends DeviceLauncher {
|
|
57
61
|
});
|
58
62
|
}
|
59
63
|
});
|
60
|
-
await this._notifyShutdownCompleted(adbName);
|
61
|
-
}
|
62
|
-
|
63
|
-
_launchEmulator(emulatorName, launchCommand, adbName) {
|
64
|
-
return launchEmulatorProcess(emulatorName, this._emulatorExec, launchCommand, this._adb, adbName);
|
65
|
-
}
|
66
|
-
|
67
|
-
async _awaitEmulatorBoot(adbName) {
|
68
|
-
await retry({ retries: 240, interval: 2500, shouldUnref: true }, async () => {
|
69
|
-
const isBootComplete = await this._adb.isBootComplete(adbName);
|
70
|
-
|
71
|
-
if (!isBootComplete) {
|
72
|
-
throw new DetoxRuntimeError({
|
73
|
-
message: `Waited for ${adbName} to complete booting for too long!`,
|
74
|
-
});
|
75
|
-
}
|
76
|
-
});
|
77
64
|
}
|
78
65
|
}
|
79
66
|
|
@@ -0,0 +1,37 @@
|
|
1
|
+
const net = require('net');
|
2
|
+
|
3
|
+
class FreePortFinder {
|
4
|
+
constructor({ min = 10000, max = 20000 } = {}) {
|
5
|
+
this._min = min;
|
6
|
+
this._max = max;
|
7
|
+
}
|
8
|
+
|
9
|
+
async findFreePort() {
|
10
|
+
let port;
|
11
|
+
|
12
|
+
do {
|
13
|
+
const min = this._min;
|
14
|
+
const max = this._max;
|
15
|
+
port = Math.random() * (max - min) + min;
|
16
|
+
port = port & 0xFFFFFFFE; // Should always be even
|
17
|
+
} while (await this.isPortTaken(port));
|
18
|
+
|
19
|
+
return port;
|
20
|
+
}
|
21
|
+
|
22
|
+
async isPortTaken(port) {
|
23
|
+
return new Promise((resolve, reject) => {
|
24
|
+
const tester = net.createServer()
|
25
|
+
.once('error', /** @param {*} err */ (err) => {
|
26
|
+
/* istanbul ignore next */
|
27
|
+
return err && err.code === 'EADDRINUSE' ? resolve(true) : reject(err);
|
28
|
+
})
|
29
|
+
.once('listening', () => {
|
30
|
+
tester.once('close', () => resolve(false)).close();
|
31
|
+
})
|
32
|
+
.listen(port);
|
33
|
+
});
|
34
|
+
}
|
35
|
+
}
|
36
|
+
|
37
|
+
module.exports = FreePortFinder;
|
@@ -4,10 +4,10 @@ const _ = require('lodash');
|
|
4
4
|
|
5
5
|
const unitLogger = require('../../../../../utils/logger').child({ cat: 'device' });
|
6
6
|
|
7
|
-
function launchEmulatorProcess(
|
7
|
+
function launchEmulatorProcess(emulatorExec, adb, emulatorLaunchCommand) {
|
8
8
|
let childProcessOutput;
|
9
9
|
const portName = emulatorLaunchCommand.port ? `-${emulatorLaunchCommand.port}` : '';
|
10
|
-
const tempLog = `./${
|
10
|
+
const tempLog = `./${emulatorLaunchCommand.avdName}${portName}.log`;
|
11
11
|
const stdout = fs.openSync(tempLog, 'a');
|
12
12
|
const stderr = fs.openSync(tempLog, 'a');
|
13
13
|
|
@@ -31,7 +31,7 @@ function launchEmulatorProcess(emulatorName, emulatorExec, emulatorLaunchCommand
|
|
31
31
|
|
32
32
|
log = log.child({ child_pid: childProcessPromise.childProcess.pid });
|
33
33
|
|
34
|
-
adb.waitForDevice(adbName).then(() => childProcessPromise._cpResolve());
|
34
|
+
adb.waitForDevice(emulatorLaunchCommand.adbName).then(() => childProcessPromise._cpResolve());
|
35
35
|
|
36
36
|
return childProcessPromise.then(() => true).catch((err) => {
|
37
37
|
detach();
|
@@ -1,25 +1,43 @@
|
|
1
|
+
/**
|
2
|
+
* @typedef {import('../../AllocationDriverBase').AllocationDriverBase} AllocationDriverBase
|
3
|
+
* @typedef {import('../../../../common/drivers/android/cookies').GenycloudEmulatorCookie} GenycloudEmulatorCookie
|
4
|
+
*/
|
5
|
+
|
1
6
|
const { DetoxRuntimeError } = require('../../../../../errors');
|
2
7
|
const Timer = require('../../../../../utils/Timer');
|
3
|
-
const
|
4
|
-
|
8
|
+
const log = require('../../../../../utils/logger').child({ cat: 'device' });
|
9
|
+
|
10
|
+
const GenyRegistry = require('./GenyRegistry');
|
5
11
|
|
6
|
-
|
12
|
+
const events = {
|
13
|
+
GENYCLOUD_TEARDOWN: { event: 'GENYCLOUD_TEARDOWN' },
|
14
|
+
};
|
7
15
|
|
16
|
+
/**
|
17
|
+
* @implements {AllocationDriverBase}
|
18
|
+
*/
|
19
|
+
class GenyAllocDriver {
|
8
20
|
/**
|
9
21
|
* @param {object} options
|
10
22
|
* @param {import('../../../../common/drivers/android/exec/ADB')} options.adb
|
11
|
-
* @param {
|
12
|
-
* @param {import('./
|
23
|
+
* @param {DetoxInternals.SessionState} options.detoxSession
|
24
|
+
* @param {import('./GenyRegistry')} options.genyRegistry
|
13
25
|
* @param {import('./GenyInstanceLauncher')} options.instanceLauncher
|
26
|
+
* @param {import('./GenyRecipeQuerying')} options.recipeQuerying
|
14
27
|
*/
|
15
|
-
constructor({
|
16
|
-
|
17
|
-
|
28
|
+
constructor({
|
29
|
+
adb,
|
30
|
+
detoxSession,
|
31
|
+
genyRegistry = new GenyRegistry(),
|
32
|
+
instanceLauncher,
|
33
|
+
recipeQuerying,
|
34
|
+
}) {
|
18
35
|
this._adb = adb;
|
19
|
-
this.
|
36
|
+
this._detoxSessionId = detoxSession.id;
|
37
|
+
this._genyRegistry = genyRegistry;
|
20
38
|
this._instanceLauncher = instanceLauncher;
|
21
|
-
this.
|
22
|
-
this.
|
39
|
+
this._recipeQuerying = recipeQuerying;
|
40
|
+
this._instanceCounter = 0;
|
23
41
|
}
|
24
42
|
|
25
43
|
/**
|
@@ -27,47 +45,83 @@ class GenyAllocDriver extends AllocationDriverBase {
|
|
27
45
|
* @return {Promise<GenycloudEmulatorCookie>}
|
28
46
|
*/
|
29
47
|
async allocate(deviceConfig) {
|
48
|
+
await new Promise((resolve) => setTimeout(resolve, 10000));
|
30
49
|
const deviceQuery = deviceConfig.device;
|
31
50
|
const recipe = await this._recipeQuerying.getRecipeFromQuery(deviceQuery);
|
32
51
|
this._assertRecipe(deviceQuery, recipe);
|
33
52
|
|
34
|
-
|
35
|
-
|
36
|
-
|
53
|
+
let instance = this._genyRegistry.findFreeInstance(recipe);
|
54
|
+
if (!instance) {
|
55
|
+
const instanceName = `Detox.${this._detoxSessionId}.${this._instanceCounter++}`;
|
56
|
+
instance = await this._instanceLauncher.launch(recipe, instanceName);
|
57
|
+
this._genyRegistry.addInstance(instance, recipe);
|
58
|
+
}
|
59
|
+
|
60
|
+
return {
|
61
|
+
id: instance.uuid,
|
62
|
+
adbName: instance.adbName,
|
63
|
+
name: instance.name,
|
64
|
+
instance,
|
65
|
+
};
|
37
66
|
}
|
38
67
|
|
39
68
|
/**
|
40
69
|
* @param {GenycloudEmulatorCookie} cookie
|
41
|
-
* @returns {Promise<void>}
|
42
70
|
*/
|
43
71
|
async postAllocate(cookie) {
|
44
|
-
const
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
await
|
51
|
-
|
52
|
-
|
53
|
-
|
72
|
+
const instance = await this._instanceLauncher.connect(cookie.instance);
|
73
|
+
this._genyRegistry.updateInstance(instance);
|
74
|
+
|
75
|
+
if (this._genyRegistry.pollNewInstance(instance.uuid)) {
|
76
|
+
const { adbName } = instance;
|
77
|
+
|
78
|
+
await Timer.run(20000, 'waiting for device to respond', async () => {
|
79
|
+
await this._adb.disableAndroidAnimations(adbName);
|
80
|
+
await this._adb.setWiFiToggle(adbName, true);
|
81
|
+
await this._adb.apiLevel(adbName);
|
82
|
+
});
|
83
|
+
}
|
84
|
+
|
85
|
+
return {
|
86
|
+
...cookie,
|
87
|
+
adbName: instance.adbName,
|
88
|
+
};
|
54
89
|
}
|
55
90
|
|
56
91
|
/**
|
57
|
-
* @param cookie {
|
58
|
-
* @param options {
|
92
|
+
* @param cookie {Omit<GenycloudEmulatorCookie, 'instance'>}
|
93
|
+
* @param options {Partial<import('../../AllocationDriverBase').DeallocOptions>}
|
59
94
|
* @return {Promise<void>}
|
60
95
|
*/
|
61
96
|
async free(cookie, options = {}) {
|
62
|
-
|
63
|
-
|
64
|
-
await this._instanceAllocationHelper.deallocateDevice(instance.uuid);
|
65
|
-
|
97
|
+
// Known issue: cookie won't have a proper 'instance' field due to (de)serialization
|
66
98
|
if (options.shutdown) {
|
67
|
-
|
99
|
+
this._genyRegistry.removeInstance(cookie.id);
|
100
|
+
await this._instanceLauncher.shutdown(cookie.id);
|
101
|
+
} else {
|
102
|
+
this._genyRegistry.markAsFree(cookie.id);
|
68
103
|
}
|
69
104
|
}
|
70
105
|
|
106
|
+
async cleanup() {
|
107
|
+
log.info(events.GENYCLOUD_TEARDOWN, 'Initiating Genymotion SaaS instances teardown...');
|
108
|
+
|
109
|
+
const killPromises = this._genyRegistry.getInstances().map((instance) => {
|
110
|
+
this._genyRegistry.markAsBusy(instance.uuid);
|
111
|
+
const onSuccess = () => this._genyRegistry.removeInstance(instance.uuid);
|
112
|
+
const onError = (error) => ({ ...instance, error });
|
113
|
+
return this._instanceLauncher.shutdown(instance.uuid).then(onSuccess, onError);
|
114
|
+
});
|
115
|
+
|
116
|
+
const deletionLeaks = (await Promise.all(killPromises)).filter(Boolean);
|
117
|
+
this._reportGlobalCleanupSummary(deletionLeaks);
|
118
|
+
}
|
119
|
+
|
120
|
+
emergencyCleanup() {
|
121
|
+
const instances = this._genyRegistry.getInstances();
|
122
|
+
this._reportGlobalCleanupSummary(instances);
|
123
|
+
}
|
124
|
+
|
71
125
|
_assertRecipe(deviceQuery, recipe) {
|
72
126
|
if (!recipe) {
|
73
127
|
throw new DetoxRuntimeError({
|
@@ -76,6 +130,24 @@ class GenyAllocDriver extends AllocationDriverBase {
|
|
76
130
|
});
|
77
131
|
}
|
78
132
|
}
|
133
|
+
|
134
|
+
_reportGlobalCleanupSummary(deletionLeaks) {
|
135
|
+
if (deletionLeaks.length) {
|
136
|
+
log.warn(events.GENYCLOUD_TEARDOWN, 'WARNING! Detected a Genymotion SaaS instance leakage, for the following instances:');
|
137
|
+
|
138
|
+
deletionLeaks.forEach(({ uuid, name, error }) => {
|
139
|
+
log.warn(events.GENYCLOUD_TEARDOWN, [
|
140
|
+
`Instance ${name} (${uuid})${error ? `: ${error}` : ''}`,
|
141
|
+
` Kill it by visiting https://cloud.geny.io/instance/${uuid}, or by running:`,
|
142
|
+
` gmsaas instances stop ${uuid}`,
|
143
|
+
].join('\n'));
|
144
|
+
});
|
145
|
+
|
146
|
+
log.info(events.GENYCLOUD_TEARDOWN, 'Instances teardown completed with warnings');
|
147
|
+
} else {
|
148
|
+
log.info(events.GENYCLOUD_TEARDOWN, 'Instances teardown completed successfully');
|
149
|
+
}
|
150
|
+
}
|
79
151
|
}
|
80
152
|
|
81
153
|
module.exports = GenyAllocDriver;
|
@@ -1,44 +1,49 @@
|
|
1
|
-
// @ts-nocheck
|
2
1
|
const DetoxRuntimeError = require('../../../../../errors/DetoxRuntimeError');
|
2
|
+
const logger = require('../../../../../utils/logger').child({ cat: 'device' });
|
3
3
|
const retry = require('../../../../../utils/retry');
|
4
|
-
const DeviceLauncher = require('../../../../common/drivers/DeviceLauncher');
|
5
4
|
|
6
|
-
|
7
|
-
constructor({ instanceLifecycleService, instanceLookupService, deviceCleanupRegistry, eventEmitter }) {
|
8
|
-
super(eventEmitter);
|
5
|
+
const GenyInstance = require('./services/dto/GenyInstance');
|
9
6
|
|
7
|
+
const events = {
|
8
|
+
CREATE_DEVICE: { event: 'CREATE_DEVICE' },
|
9
|
+
};
|
10
|
+
|
11
|
+
class GenyInstanceLauncher {
|
12
|
+
constructor({ genyCloudExec, instanceLifecycleService }) {
|
13
|
+
this._genyCloudExec = genyCloudExec;
|
10
14
|
this._instanceLifecycleService = instanceLifecycleService;
|
11
|
-
this._instanceLookupService = instanceLookupService;
|
12
|
-
this._deviceCleanupRegistry = deviceCleanupRegistry;
|
13
15
|
}
|
14
16
|
|
15
17
|
/**
|
16
|
-
*
|
17
|
-
*
|
18
|
-
* not launched per-se, as with local emulators. Rather, we just need to sync-up with
|
19
|
-
* them and connect, if needed.
|
20
|
-
*
|
21
|
-
* @param instance {GenyInstance} The freshly allocated cloud-instance.
|
22
|
-
* @param isNew { boolean }
|
18
|
+
* @param {import('./services/dto/GenyRecipe')} recipe
|
19
|
+
* @param {string} instanceName
|
23
20
|
* @returns {Promise<GenyInstance>}
|
24
21
|
*/
|
25
|
-
async launch(
|
26
|
-
|
27
|
-
|
28
|
-
}
|
29
|
-
instance
|
30
|
-
|
31
|
-
await this._notifyBootEvent(instance.adbName, instance.recipeName, isNew);
|
22
|
+
async launch(recipe, instanceName) {
|
23
|
+
logger.debug(events.CREATE_DEVICE, `Trying to create a device based on "${recipe}"`);
|
24
|
+
const instance = await this._instanceLifecycleService.createInstance(recipe.uuid, instanceName);
|
25
|
+
const { name, uuid } = instance;
|
26
|
+
logger.info(events.CREATE_DEVICE, `Allocating Genymotion Cloud instance ${name} for testing. To access it via a browser, go to: https://cloud.geny.io/instance/${uuid}`);
|
27
|
+
|
32
28
|
return instance;
|
33
29
|
}
|
34
30
|
|
35
|
-
|
36
|
-
|
31
|
+
/**
|
32
|
+
* @param {GenyInstance} instance The freshly allocated cloud-instance.
|
33
|
+
* @returns {Promise<GenyInstance>}
|
34
|
+
*/
|
35
|
+
async connect(instance) {
|
36
|
+
const bootedInstance = await this._waitForInstanceBoot(instance);
|
37
|
+
const connectedInstance = await this._adbConnectIfNeeded(bootedInstance);
|
38
|
+
|
39
|
+
return connectedInstance;
|
40
|
+
}
|
37
41
|
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
+
/**
|
43
|
+
* @param {string} instanceId
|
44
|
+
*/
|
45
|
+
async shutdown(instanceId) {
|
46
|
+
await this._instanceLifecycleService.deleteInstance(instanceId);
|
42
47
|
}
|
43
48
|
|
44
49
|
async _waitForInstanceBoot(instance) {
|
@@ -48,17 +53,21 @@ class GenyInstanceLauncher extends DeviceLauncher {
|
|
48
53
|
|
49
54
|
const options = {
|
50
55
|
backoff: 'none',
|
51
|
-
retries:
|
56
|
+
retries: 20,
|
52
57
|
interval: 5000,
|
53
58
|
initialSleep: 45000,
|
59
|
+
shouldUnref: true,
|
54
60
|
};
|
55
61
|
|
56
62
|
return await retry(options, async () => {
|
57
|
-
const _instance = await this.
|
58
|
-
|
63
|
+
const { instance: _instance } = await this._genyCloudExec.getInstance(instance.uuid);
|
64
|
+
const anInstance = new GenyInstance(_instance);
|
65
|
+
|
66
|
+
if (!anInstance.isOnline()) {
|
59
67
|
throw new DetoxRuntimeError(`Timeout waiting for instance ${instance.uuid} to be ready`);
|
60
68
|
}
|
61
|
-
|
69
|
+
|
70
|
+
return anInstance;
|
62
71
|
});
|
63
72
|
}
|
64
73
|
|
@@ -0,0 +1,121 @@
|
|
1
|
+
const { DetoxInternalError } = require('../../../../../errors');
|
2
|
+
|
3
|
+
class GenyRegistry {
|
4
|
+
constructor() {
|
5
|
+
/** @type {Map<string, import('./services/dto/GenyRecipe')>} */
|
6
|
+
this._recipes = new Map();
|
7
|
+
/** @type {Map<string, import('./services/dto/GenyInstance')>} */
|
8
|
+
this._freeInstances = new Map();
|
9
|
+
/** @type {Map<string, import('./services/dto/GenyInstance')>} */
|
10
|
+
this._busyInstances = new Map();
|
11
|
+
/** @type {Set<string>} */
|
12
|
+
this._newInstances = new Set();
|
13
|
+
}
|
14
|
+
|
15
|
+
/**
|
16
|
+
* @returns {import('./services/dto/GenyInstance')[]}
|
17
|
+
*/
|
18
|
+
getInstances() {
|
19
|
+
return [
|
20
|
+
...this._freeInstances.values(),
|
21
|
+
...this._busyInstances.values(),
|
22
|
+
];
|
23
|
+
}
|
24
|
+
|
25
|
+
/**
|
26
|
+
* @param {import('./services/dto/GenyInstance')} instance
|
27
|
+
* @param {import('./services/dto/GenyRecipe')} recipe
|
28
|
+
*/
|
29
|
+
addInstance(instance, recipe) {
|
30
|
+
this._recipes.set(instance.uuid, recipe);
|
31
|
+
this._busyInstances.set(instance.uuid, instance);
|
32
|
+
this._newInstances.add(instance.uuid);
|
33
|
+
}
|
34
|
+
|
35
|
+
/**
|
36
|
+
* @param {string} instanceId
|
37
|
+
* @returns {boolean}
|
38
|
+
*/
|
39
|
+
pollNewInstance(instanceId) {
|
40
|
+
const result = this._newInstances.has(instanceId);
|
41
|
+
this._newInstances.delete(instanceId);
|
42
|
+
return result;
|
43
|
+
}
|
44
|
+
|
45
|
+
/** @param {import('./services/dto/GenyInstance')} instance */
|
46
|
+
updateInstance(instance) {
|
47
|
+
let found = false;
|
48
|
+
|
49
|
+
if (this._freeInstances.has(instance.uuid)) {
|
50
|
+
this._freeInstances.set(instance.uuid, instance);
|
51
|
+
found = true;
|
52
|
+
}
|
53
|
+
|
54
|
+
if (this._busyInstances.has(instance.uuid)) {
|
55
|
+
this._busyInstances.set(instance.uuid, instance);
|
56
|
+
found = true;
|
57
|
+
}
|
58
|
+
|
59
|
+
if (!found) {
|
60
|
+
throw new DetoxInternalError(`Cannot update an unknown instance ${instance.uuid}`);
|
61
|
+
}
|
62
|
+
}
|
63
|
+
|
64
|
+
/** @param {string} instanceId */
|
65
|
+
removeInstance(instanceId) {
|
66
|
+
this._freeInstances.delete(instanceId);
|
67
|
+
this._busyInstances.delete(instanceId);
|
68
|
+
this._newInstances.delete(instanceId);
|
69
|
+
this._recipes.delete(instanceId);
|
70
|
+
}
|
71
|
+
|
72
|
+
/**
|
73
|
+
* @param {string} instanceId
|
74
|
+
* @returns {import('./services/dto/GenyInstance')}
|
75
|
+
*/
|
76
|
+
markAsBusy(instanceId) {
|
77
|
+
if (this._busyInstances.has(instanceId)) {
|
78
|
+
return this._busyInstances.get(instanceId);
|
79
|
+
}
|
80
|
+
|
81
|
+
const instance = this._freeInstances.get(instanceId);
|
82
|
+
if (!instance) {
|
83
|
+
throw new DetoxInternalError(`Cannot mark an unknown instance ${instanceId} as busy`);
|
84
|
+
}
|
85
|
+
|
86
|
+
this._busyInstances.set(instanceId, instance);
|
87
|
+
this._freeInstances.delete(instanceId);
|
88
|
+
return instance;
|
89
|
+
}
|
90
|
+
|
91
|
+
/**
|
92
|
+
* @param {string} instanceId
|
93
|
+
* @returns {import('./services/dto/GenyInstance')}
|
94
|
+
*/
|
95
|
+
markAsFree(instanceId) {
|
96
|
+
if (this._freeInstances.has(instanceId)) {
|
97
|
+
return this._freeInstances.get(instanceId);
|
98
|
+
}
|
99
|
+
|
100
|
+
const instance = this._busyInstances.get(instanceId);
|
101
|
+
if (!instance) {
|
102
|
+
throw new DetoxInternalError(`Cannot mark an unknown instance ${instanceId} as free`);
|
103
|
+
}
|
104
|
+
|
105
|
+
this._busyInstances.delete(instanceId);
|
106
|
+
this._freeInstances.set(instanceId, instance);
|
107
|
+
return instance;
|
108
|
+
}
|
109
|
+
|
110
|
+
/** @returns {import('./services/dto/GenyInstance') | undefined} */
|
111
|
+
findFreeInstance(recipe) {
|
112
|
+
for (const instance of this._freeInstances.values()) {
|
113
|
+
const aRecipe = this._recipes.get(instance.uuid);
|
114
|
+
if (recipe.uuid === aRecipe.uuid) {
|
115
|
+
return this.markAsBusy(instance.uuid);
|
116
|
+
}
|
117
|
+
}
|
118
|
+
}
|
119
|
+
}
|
120
|
+
|
121
|
+
module.exports = GenyRegistry;
|
package/src/devices/allocation/drivers/android/genycloud/services/GenyInstanceLifecycleService.js
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
const Instance = require('./dto/GenyInstance');
|
2
|
+
|
3
|
+
class GenyInstanceLifecycleService {
|
4
|
+
constructor(genyCloudExec) {
|
5
|
+
this._genyCloudExec = genyCloudExec;
|
6
|
+
}
|
7
|
+
|
8
|
+
async createInstance(recipeUUID, instanceName) {
|
9
|
+
const result = await this._genyCloudExec.startInstance(recipeUUID, instanceName);
|
10
|
+
return new Instance(result.instance);
|
11
|
+
}
|
12
|
+
|
13
|
+
async adbConnectInstance(instanceUUID) {
|
14
|
+
const result = (await this._genyCloudExec.adbConnect(instanceUUID));
|
15
|
+
return new Instance(result.instance);
|
16
|
+
}
|
17
|
+
|
18
|
+
async deleteInstance(instanceUUID) {
|
19
|
+
const result = await this._genyCloudExec.stopInstance(instanceUUID);
|
20
|
+
return new Instance(result.instance);
|
21
|
+
}
|
22
|
+
}
|
23
|
+
|
24
|
+
module.exports = GenyInstanceLifecycleService;
|