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.
Files changed (185) hide show
  1. package/.eslintignore +1 -0
  2. 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
  3. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8-javadoc.jar.md5 +1 -0
  4. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8-javadoc.jar.sha1 +1 -0
  5. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8-javadoc.jar.sha256 +1 -0
  6. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8-javadoc.jar.sha512 +1 -0
  7. 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
  8. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8-sources.jar.md5 +1 -0
  9. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8-sources.jar.sha1 +1 -0
  10. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8-sources.jar.sha256 +1 -0
  11. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8-sources.jar.sha512 +1 -0
  12. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.aar +0 -0
  13. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.aar.md5 +1 -0
  14. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.aar.sha1 +1 -0
  15. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.aar.sha256 +1 -0
  16. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.aar.sha512 +1 -0
  17. 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
  18. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.pom.md5 +1 -0
  19. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.pom.sha1 +1 -0
  20. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.pom.sha256 +1 -0
  21. package/Detox-android/com/wix/detox/21.0.0-rc.8/detox-21.0.0-rc.8.pom.sha512 +1 -0
  22. package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
  23. package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
  24. package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
  25. package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
  26. package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
  27. package/Detox-ios-framework.tbz +0 -0
  28. package/Detox-ios-src.tbz +0 -0
  29. package/Detox-ios-xcuitest.tbz +0 -0
  30. package/android/detox/build.gradle +13 -8
  31. package/android/detox/src/full/java/com/wix/detox/espresso/DetoxAssertion.java +44 -25
  32. package/android/detox/src/full/java/com/wix/detox/espresso/EspressoDetox.java +6 -7
  33. package/android/detox/src/full/java/com/wix/detox/espresso/action/AdjustSliderToPositionAction.kt +2 -2
  34. package/android/detox/src/full/java/com/wix/detox/espresso/action/GetAttributesAction.kt +34 -35
  35. package/android/detox/src/full/java/com/wix/detox/espresso/common/MaterialSliderHelper.kt +21 -0
  36. package/android/detox/src/full/java/com/wix/detox/espresso/common/{SliderHelper.kt → ReactSliderHelper.kt} +6 -5
  37. package/android/detox/src/full/java/com/wix/detox/espresso/matcher/ViewMatchers.kt +2 -2
  38. package/android/detox/src/full/java/com/wix/detox/espresso/performer/MultipleViewsActionPerformer.kt +43 -0
  39. package/android/detox/src/full/java/com/wix/detox/espresso/performer/SingleViewActionPerformer.kt +19 -0
  40. package/android/detox/src/full/java/com/wix/detox/espresso/performer/ViewActionPerformer.kt +24 -0
  41. package/android/detox/src/full/java/com/wix/detox/espresso/web/WebElement.java +4 -4
  42. package/android/detox/src/full/java/com/wix/invoke/types/Invocation.java +7 -6
  43. package/android/detox/src/main/java/com/wix/detox/espresso/MultipleViewsAction.kt +4 -0
  44. package/android/detox/src/main/java/com/wix/detox/espresso/UiControllerSpy.kt +0 -1
  45. package/android/detox/src/main/java/com/wix/detox/espresso/ViewActionWithResult.kt +2 -1
  46. package/android/detox/src/main/java/com/wix/detox/espresso/action/common/MotionEvents.kt +60 -4
  47. package/android/detox/src/testFull/java/com/wix/detox/espresso/action/GetAttributesActionTest.kt +6 -5
  48. package/android/detox/src/testFull/java/com/wix/detox/espresso/common/MaterialSliderHelperTest.kt +33 -0
  49. package/android/detox/src/testFull/java/com/wix/detox/espresso/common/{SliderHelperTest.kt → ReactSliderHelperTest.kt} +3 -3
  50. package/android/detox/src/testFull/java/com/wix/detox/espresso/performer/ViewActionPerformerSpec.kt +37 -0
  51. package/android/detox/src/testFull/java/com/wix/invoke/JsonParserTest.java +23 -7
  52. package/android/detox/src/testFull/resources/targetInvocationEspressoWebDetoxScript.json +47 -0
  53. package/detox.d.ts +1840 -0
  54. package/globals.d.ts +23 -0
  55. package/index.d.ts +2 -1823
  56. package/internals.d.ts +11 -1
  57. package/jest.config.js +108 -0
  58. package/local-cli/reset-lock-file.js +5 -9
  59. package/local-cli/testCommand/TestRunnerCommand.js +26 -3
  60. package/local-cli/utils/interruptListeners.js +15 -0
  61. package/package.json +2 -100
  62. package/runners/jest/reporter.js +21 -1
  63. package/runners/jest/reporters/DetoxIPCReporter.js +34 -0
  64. package/runners/jest/reporters/DetoxReporterDispatcher.js +144 -0
  65. package/runners/jest/reporters/DetoxSummaryReporter.js +16 -0
  66. package/runners/jest/reporters/DetoxVerboseReporter.js +16 -0
  67. package/runners/jest/reporters/index.js +6 -0
  68. package/runners/jest/testEnvironment/index.js +11 -0
  69. package/src/DetoxWorker.js +5 -11
  70. package/src/android/core/NativeElement.js +26 -29
  71. package/src/android/core/WebElement.js +24 -6
  72. package/src/android/espressoapi/DetoxAssertion.js +16 -14
  73. package/src/android/espressoapi/EspressoDetox.js +9 -2
  74. package/src/android/espressoapi/web/WebElement.js +1 -4
  75. package/src/android/interactions/native.js +2 -3
  76. package/src/android/matchers/index.js +2 -1
  77. package/src/android/matchers/web.js +9 -1
  78. package/src/artifacts/providers/index.js +3 -3
  79. package/src/artifacts/screenshot/SimulatorScreenshotPlugin.js +0 -17
  80. package/src/configuration/composeLoggerConfig.js +1 -0
  81. package/src/configuration/composeRunnerConfig.js +3 -1
  82. package/src/devices/allocation/DeviceAllocator.js +66 -20
  83. package/src/devices/allocation/DeviceList.js +44 -0
  84. package/src/devices/allocation/DeviceRegistry.js +189 -0
  85. package/src/devices/allocation/drivers/AllocationDriverBase.d.ts +15 -0
  86. package/src/devices/{common/drivers/android/tools → allocation/drivers/android}/FreeDeviceFinder.js +11 -10
  87. package/src/devices/allocation/drivers/android/attached/AttachedAndroidAllocDriver.js +22 -17
  88. package/src/devices/allocation/drivers/android/emulator/EmulatorAllocDriver.js +97 -38
  89. package/src/devices/allocation/drivers/android/emulator/EmulatorLauncher.js +32 -45
  90. package/src/devices/allocation/drivers/android/emulator/FreeEmulatorFinder.js +1 -1
  91. package/src/devices/allocation/drivers/android/emulator/FreePortFinder.js +37 -0
  92. package/src/devices/allocation/drivers/android/emulator/launchEmulatorProcess.js +3 -3
  93. package/src/devices/allocation/drivers/android/genycloud/GenyAllocDriver.js +104 -32
  94. package/src/devices/allocation/drivers/android/genycloud/GenyInstanceLauncher.js +40 -31
  95. package/src/devices/allocation/drivers/android/genycloud/GenyRegistry.js +121 -0
  96. package/src/devices/allocation/drivers/android/genycloud/services/GenyInstanceLifecycleService.js +24 -0
  97. package/src/devices/{common → allocation}/drivers/android/genycloud/services/GenyRecipesService.js +1 -1
  98. package/src/devices/allocation/drivers/android/genycloud/services/dto/GenyInstance.js +83 -0
  99. package/src/devices/allocation/drivers/android/genycloud/services/dto/GenyRecipe.js +25 -0
  100. package/src/devices/allocation/drivers/ios/SimulatorAllocDriver.js +95 -54
  101. package/src/devices/allocation/drivers/ios/SimulatorQuery.js +24 -0
  102. package/src/devices/allocation/factories/android.js +29 -35
  103. package/src/devices/allocation/factories/ios.js +6 -7
  104. package/src/devices/common/drivers/DeviceCookie.d.ts +12 -0
  105. package/src/devices/common/drivers/android/cookies.d.ts +11 -0
  106. package/src/devices/common/drivers/android/emulator/exec/EmulatorExec.js +17 -5
  107. package/src/devices/common/drivers/android/exec/ADB.js +1 -0
  108. package/src/devices/common/drivers/android/tools/instrumentationArgs.js +7 -1
  109. package/src/devices/common/drivers/ios/cookies.d.ts +9 -0
  110. package/src/devices/common/drivers/ios/tools/AppleSimUtils.js +3 -1
  111. package/src/devices/cookies/index.js +0 -6
  112. package/src/devices/runtime/drivers/android/genycloud/GenyCloudDriver.js +7 -6
  113. package/src/devices/runtime/drivers/ios/SimulatorDriver.js +5 -4
  114. package/src/devices/runtime/factories/android.js +3 -11
  115. package/src/devices/runtime/factories/ios.js +3 -4
  116. package/src/{servicelocator → devices/servicelocator}/android/emulatorServiceLocator.js +1 -1
  117. package/src/devices/servicelocator/android/genycloudServiceLocator.js +17 -0
  118. package/src/devices/servicelocator/android/index.js +23 -0
  119. package/src/{validation → devices/validation}/EnvironmentValidatorBase.js +1 -0
  120. package/src/{validation → devices/validation}/android/GenycloudEnvValidator.js +2 -2
  121. package/src/{validation → devices/validation}/factories/index.js +1 -1
  122. package/src/{validation → devices/validation}/ios/IosSimulatorEnvValidator.js +2 -2
  123. package/src/environmentFactory.js +1 -11
  124. package/src/ios/web.js +27 -5
  125. package/src/ipc/IPCClient.js +22 -1
  126. package/src/ipc/IPCServer.js +42 -1
  127. package/src/ipc/SessionState.js +1 -0
  128. package/src/logger/DetoxLogger.js +2 -2
  129. package/src/realms/DetoxContext.js +8 -0
  130. package/src/realms/DetoxInternalsFacade.js +1 -0
  131. package/src/realms/DetoxPrimaryContext.js +48 -42
  132. package/src/realms/DetoxSecondaryContext.js +27 -0
  133. package/src/realms/symbols.js +6 -0
  134. package/src/utils/PIDService.js +27 -0
  135. package/src/utils/assertIsFunction.js +35 -0
  136. package/src/utils/environment.js +8 -15
  137. package/src/utils/errorUtils.js +3 -3
  138. package/src/utils/isArrowFunction.js +24 -0
  139. package/tsconfig.json +8 -3
  140. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6-javadoc.jar.md5 +0 -1
  141. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6-javadoc.jar.sha1 +0 -1
  142. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6-javadoc.jar.sha256 +0 -1
  143. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6-javadoc.jar.sha512 +0 -1
  144. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6-sources.jar.md5 +0 -1
  145. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6-sources.jar.sha1 +0 -1
  146. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6-sources.jar.sha256 +0 -1
  147. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6-sources.jar.sha512 +0 -1
  148. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.aar +0 -0
  149. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.aar.md5 +0 -1
  150. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.aar.sha1 +0 -1
  151. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.aar.sha256 +0 -1
  152. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.aar.sha512 +0 -1
  153. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.pom.md5 +0 -1
  154. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.pom.sha1 +0 -1
  155. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.pom.sha256 +0 -1
  156. package/Detox-android/com/wix/detox/21.0.0-rc.6/detox-21.0.0-rc.6.pom.sha512 +0 -1
  157. package/runners/jest/reporters/DetoxReporter.js +0 -36
  158. package/src/devices/DeviceRegistry.js +0 -176
  159. package/src/devices/allocation/drivers/AllocationDriverBase.js +0 -30
  160. package/src/devices/allocation/drivers/android/attached/AttachedAndroidLauncher.js +0 -13
  161. package/src/devices/allocation/drivers/android/emulator/EmulatorAllocationHelper.js +0 -72
  162. package/src/devices/allocation/drivers/android/genycloud/GenyDeviceRegistryFactory.js +0 -16
  163. package/src/devices/allocation/drivers/android/genycloud/GenyInstanceAllocationHelper.js +0 -65
  164. package/src/devices/allocation/drivers/ios/SimulatorLauncher.js +0 -21
  165. package/src/devices/common/drivers/DeviceAllocationHelper.js +0 -20
  166. package/src/devices/common/drivers/DeviceLauncher.js +0 -19
  167. package/src/devices/common/drivers/android/genycloud/services/GenyInstanceLifecycleService.js +0 -25
  168. package/src/devices/common/drivers/android/genycloud/services/GenyInstanceLookupService.js +0 -38
  169. package/src/devices/common/drivers/android/genycloud/services/GenyInstanceNaming.js +0 -14
  170. package/src/devices/common/drivers/android/genycloud/services/dto/GenyInstance.js +0 -66
  171. package/src/devices/common/drivers/android/genycloud/services/dto/GenyRecipe.js +0 -13
  172. package/src/devices/cookies/AndroidDeviceCookie.js +0 -13
  173. package/src/devices/cookies/AndroidEmulatorCookie.js +0 -6
  174. package/src/devices/cookies/AttachedAndroidDeviceCookie.js +0 -12
  175. package/src/devices/cookies/DeviceCookie.js +0 -4
  176. package/src/devices/cookies/GenycloudEmulatorCookie.js +0 -20
  177. package/src/devices/cookies/IosCookie.js +0 -6
  178. package/src/devices/cookies/IosSimulatorCookie.js +0 -10
  179. package/src/devices/lifecycle/GenyGlobalLifecycleHandler.js +0 -71
  180. package/src/devices/lifecycle/factories/GenyGlobalLifecycleHandlerFactory.js +0 -18
  181. package/src/servicelocator/android/genycloudServiceLocator.js +0 -21
  182. package/src/servicelocator/android/index.js +0 -25
  183. package/src/servicelocator/ios.js +0 -7
  184. /package/src/devices/{common → allocation}/drivers/android/genycloud/exec/GenyCloudExec.js +0 -0
  185. /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 extends DeviceLauncher {
14
- constructor({ adb, emulatorExec, eventEmitter }) {
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 avdName { String }
23
- * @param adbName { String }
24
- * @param isRunning { Boolean }
25
- * @param options { Object }
26
- * @param options.port { Number | undefined }
27
- * @param options.bootArgs { String | undefined }
28
- * @param options.gpuMode { String | undefined }
29
- * @param options.headless { Boolean }
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(avdName, adbName, isRunning, options = { port: undefined }) {
33
- if (!isRunning) {
34
- const launchCommand = new LaunchCommand(avdName, options);
35
- await retry({
36
- retries: 2,
37
- interval: 100,
38
- conditionFn: isUnknownEmulatorError,
39
- }, () => this._launchEmulator(avdName, launchCommand, adbName));
40
- }
41
- await this._awaitEmulatorBoot(adbName);
42
- await this._notifyBootEvent(adbName, avdName, !isRunning);
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
 
@@ -1,4 +1,4 @@
1
- const FreeDeviceFinder = require('../../../../common/drivers/android/tools/FreeDeviceFinder');
1
+ const FreeDeviceFinder = require('../FreeDeviceFinder');
2
2
 
3
3
  class FreeEmulatorFinder extends FreeDeviceFinder {
4
4
  /**
@@ -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(emulatorName, emulatorExec, emulatorLaunchCommand, adb, adbName) {
7
+ function launchEmulatorProcess(emulatorExec, adb, emulatorLaunchCommand) {
8
8
  let childProcessOutput;
9
9
  const portName = emulatorLaunchCommand.port ? `-${emulatorLaunchCommand.port}` : '';
10
- const tempLog = `./${emulatorName}${portName}.log`;
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 GenycloudEmulatorCookie = require('../../../../cookies/GenycloudEmulatorCookie');
4
- const AllocationDriverBase = require('../../AllocationDriverBase');
8
+ const log = require('../../../../../utils/logger').child({ cat: 'device' });
9
+
10
+ const GenyRegistry = require('./GenyRegistry');
5
11
 
6
- class GenyAllocDriver extends AllocationDriverBase {
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 {import('./GenyRecipeQuerying')} options.recipeQuerying
12
- * @param {import('./GenyInstanceAllocationHelper')} options.allocationHelper
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({ adb, recipeQuerying, allocationHelper, instanceLauncher }) {
16
- super();
17
-
28
+ constructor({
29
+ adb,
30
+ detoxSession,
31
+ genyRegistry = new GenyRegistry(),
32
+ instanceLauncher,
33
+ recipeQuerying,
34
+ }) {
18
35
  this._adb = adb;
19
- this._recipeQuerying = recipeQuerying;
36
+ this._detoxSessionId = detoxSession.id;
37
+ this._genyRegistry = genyRegistry;
20
38
  this._instanceLauncher = instanceLauncher;
21
- this._instanceAllocationHelper = allocationHelper;
22
- this._launchInfo = {};
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
- const { instance, isNew } = await this._instanceAllocationHelper.allocateDevice(recipe);
35
- this._launchInfo[instance.uuid] = { isNew };
36
- return new GenycloudEmulatorCookie(instance);
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 { instance } = cookie;
45
- const { isNew } = this._launchInfo[instance.uuid];
46
- const readyInstance = cookie.instance = await this._instanceLauncher.launch(instance, isNew);
47
-
48
- const { adbName } = readyInstance;
49
- await Timer.run(20000, 'waiting for device to respond', async () => {
50
- await this._adb.disableAndroidAnimations(adbName);
51
- await this._adb.setWiFiToggle(adbName, true);
52
- await this._adb.apiLevel(adbName);
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 { GenycloudEmulatorCookie }
58
- * @param options { DeallocOptions }
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
- const { instance } = cookie;
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
- await this._instanceLauncher.shutdown(instance);
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
- class GenyInstanceLauncher extends DeviceLauncher {
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
- * Note:
17
- * In the context of Genymotion-cloud (as opposed to local emulators), emulators are
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(instance, isNew = true) {
26
- if (isNew) {
27
- await this._deviceCleanupRegistry.allocateDevice(instance.uuid, { name: instance.name });
28
- }
29
- instance = await this._waitForInstanceBoot(instance);
30
- instance = await this._adbConnectIfNeeded(instance);
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
- async shutdown(instance) {
36
- const { uuid } = instance;
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
- await this._notifyPreShutdown(uuid);
39
- await this._instanceLifecycleService.deleteInstance(uuid);
40
- await this._deviceCleanupRegistry.disposeDevice(uuid);
41
- await this._notifyShutdownCompleted(uuid);
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: 25,
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._instanceLookupService.getInstance(instance.uuid);
58
- if (!_instance.isOnline()) {
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
- return _instance;
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;
@@ -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;
@@ -1,6 +1,6 @@
1
1
  const logger = require('../../../../../../utils/logger').child({ cat: 'device' });
2
2
 
3
- const Recipe = require('./dto/GenyRecipe');
3
+ const Recipe = require('./dto/GenyRecipe');
4
4
 
5
5
  class GenyRecipesService {
6
6
  constructor(genyCloudExec) {