detox 21.0.0-rc.1 → 21.0.0-rc.10

Sign up to get free protection for your applications and to get access to all the features.
Files changed (202) hide show
  1. package/.eslintignore +3 -0
  2. package/.eslintrc.js +1 -40
  3. package/Detox-android/com/wix/detox/{21.0.0-rc.1/detox-21.0.0-rc.1-javadoc.jar → 21.0.0-rc.10/detox-21.0.0-rc.10-javadoc.jar} +0 -0
  4. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10-javadoc.jar.md5 +1 -0
  5. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10-javadoc.jar.sha1 +1 -0
  6. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10-javadoc.jar.sha256 +1 -0
  7. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10-javadoc.jar.sha512 +1 -0
  8. package/Detox-android/com/wix/detox/{21.0.0-rc.1/detox-21.0.0-rc.1-sources.jar → 21.0.0-rc.10/detox-21.0.0-rc.10-sources.jar} +0 -0
  9. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10-sources.jar.md5 +1 -0
  10. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10-sources.jar.sha1 +1 -0
  11. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10-sources.jar.sha256 +1 -0
  12. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10-sources.jar.sha512 +1 -0
  13. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10.aar +0 -0
  14. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10.aar.md5 +1 -0
  15. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10.aar.sha1 +1 -0
  16. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10.aar.sha256 +1 -0
  17. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10.aar.sha512 +1 -0
  18. package/Detox-android/com/wix/detox/{21.0.0-rc.1/detox-21.0.0-rc.1.pom → 21.0.0-rc.10/detox-21.0.0-rc.10.pom} +1 -7
  19. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10.pom.md5 +1 -0
  20. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10.pom.sha1 +1 -0
  21. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10.pom.sha256 +1 -0
  22. package/Detox-android/com/wix/detox/21.0.0-rc.10/detox-21.0.0-rc.10.pom.sha512 +1 -0
  23. package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
  24. package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
  25. package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
  26. package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
  27. package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
  28. package/Detox-ios-framework.tbz +0 -0
  29. package/Detox-ios-src.tbz +0 -0
  30. package/Detox-ios-xcuitest.tbz +0 -0
  31. package/android/build.gradle +20 -10
  32. package/android/detox/build.gradle +24 -12
  33. package/android/detox/src/full/java/com/wix/detox/espresso/DetoxAssertion.java +44 -25
  34. package/android/detox/src/full/java/com/wix/detox/espresso/DetoxMatcher.java +12 -12
  35. package/android/detox/src/full/java/com/wix/detox/espresso/EspressoDetox.java +6 -7
  36. package/android/detox/src/full/java/com/wix/detox/espresso/action/AdjustSliderToPositionAction.kt +2 -2
  37. package/android/detox/src/full/java/com/wix/detox/espresso/action/GetAttributesAction.kt +34 -35
  38. package/android/detox/src/full/java/com/wix/detox/espresso/common/MaterialSliderHelper.kt +21 -0
  39. package/android/detox/src/full/java/com/wix/detox/espresso/common/{SliderHelper.kt → ReactSliderHelper.kt} +7 -6
  40. package/android/detox/src/full/java/com/wix/detox/espresso/matcher/RegexMatcher.kt +56 -0
  41. package/android/detox/src/full/java/com/wix/detox/espresso/matcher/ViewMatchers.kt +18 -6
  42. package/android/detox/src/full/java/com/wix/detox/espresso/performer/MultipleViewsActionPerformer.kt +43 -0
  43. package/android/detox/src/full/java/com/wix/detox/espresso/performer/SingleViewActionPerformer.kt +19 -0
  44. package/android/detox/src/full/java/com/wix/detox/espresso/performer/ViewActionPerformer.kt +24 -0
  45. package/android/detox/src/full/java/com/wix/detox/espresso/web/WebElement.java +4 -4
  46. package/android/detox/src/full/java/com/wix/invoke/types/Invocation.java +7 -6
  47. package/android/detox/src/main/java/com/wix/detox/espresso/MultipleViewsAction.kt +4 -0
  48. package/android/detox/src/main/java/com/wix/detox/espresso/UiControllerSpy.kt +0 -1
  49. package/android/detox/src/main/java/com/wix/detox/espresso/ViewActionWithResult.kt +2 -1
  50. package/android/detox/src/main/java/com/wix/detox/espresso/action/common/MotionEvents.kt +60 -4
  51. package/android/detox/src/testFull/java/com/wix/detox/espresso/action/GetAttributesActionTest.kt +6 -5
  52. package/android/detox/src/testFull/java/com/wix/detox/espresso/common/MaterialSliderHelperTest.kt +33 -0
  53. package/android/detox/src/testFull/java/com/wix/detox/espresso/common/{SliderHelperTest.kt → ReactSliderHelperTest.kt} +3 -3
  54. package/android/detox/src/testFull/java/com/wix/detox/espresso/matcher/RegexMatcherTest.kt +52 -0
  55. package/android/detox/src/testFull/java/com/wix/detox/espresso/performer/ViewActionPerformerSpec.kt +37 -0
  56. package/android/detox/src/testFull/java/com/wix/invoke/JsonParserTest.java +23 -7
  57. package/android/detox/src/testFull/resources/targetInvocationEspressoWebDetoxScript.json +47 -0
  58. package/android/gradle/wrapper/gradle-wrapper.properties +1 -1
  59. package/android/rninfo.gradle +25 -0
  60. package/android/settings.gradle +2 -1
  61. package/detox.d.ts +1840 -0
  62. package/globals.d.ts +23 -0
  63. package/index.d.ts +2 -1789
  64. package/internals.d.ts +11 -1
  65. package/jest.config.js +108 -0
  66. package/local-cli/reset-lock-file.js +5 -9
  67. package/local-cli/startCommand/AppStartCommand.js +4 -1
  68. package/local-cli/testCommand/TestRunnerCommand.js +26 -3
  69. package/local-cli/utils/interruptListeners.js +15 -0
  70. package/package.json +15 -108
  71. package/runners/jest/reporter.js +21 -1
  72. package/runners/jest/reporters/DetoxIPCReporter.js +34 -0
  73. package/runners/jest/reporters/DetoxReporterDispatcher.js +144 -0
  74. package/runners/jest/reporters/DetoxSummaryReporter.js +16 -0
  75. package/runners/jest/reporters/DetoxVerboseReporter.js +16 -0
  76. package/runners/jest/reporters/index.js +6 -0
  77. package/runners/jest/testEnvironment/index.js +11 -0
  78. package/src/DetoxWorker.js +5 -11
  79. package/src/android/core/NativeElement.js +26 -29
  80. package/src/android/core/WebElement.js +24 -6
  81. package/src/android/espressoapi/DetoxAssertion.js +16 -14
  82. package/src/android/espressoapi/DetoxMatcher.js +24 -8
  83. package/src/android/espressoapi/EspressoDetox.js +9 -2
  84. package/src/android/espressoapi/web/WebElement.js +1 -4
  85. package/src/android/interactions/native.js +2 -3
  86. package/src/android/matchers/index.js +4 -0
  87. package/src/android/matchers/native.js +9 -4
  88. package/src/android/matchers/web.js +26 -1
  89. package/src/artifacts/providers/index.js +3 -3
  90. package/src/artifacts/screenshot/SimulatorScreenshotPlugin.js +0 -17
  91. package/src/configuration/composeLoggerConfig.js +1 -0
  92. package/src/configuration/composeRunnerConfig.js +3 -1
  93. package/src/devices/allocation/DeviceAllocator.js +66 -20
  94. package/src/devices/allocation/DeviceList.js +44 -0
  95. package/src/devices/allocation/DeviceRegistry.js +189 -0
  96. package/src/devices/allocation/drivers/AllocationDriverBase.d.ts +15 -0
  97. package/src/devices/{common/drivers/android/tools → allocation/drivers/android}/FreeDeviceFinder.js +11 -10
  98. package/src/devices/allocation/drivers/android/attached/AttachedAndroidAllocDriver.js +22 -17
  99. package/src/devices/allocation/drivers/android/emulator/EmulatorAllocDriver.js +97 -38
  100. package/src/devices/allocation/drivers/android/emulator/EmulatorLauncher.js +32 -45
  101. package/src/devices/allocation/drivers/android/emulator/FreeEmulatorFinder.js +1 -1
  102. package/src/devices/allocation/drivers/android/emulator/FreePortFinder.js +37 -0
  103. package/src/devices/allocation/drivers/android/emulator/launchEmulatorProcess.js +3 -3
  104. package/src/devices/allocation/drivers/android/genycloud/GenyAllocDriver.js +104 -32
  105. package/src/devices/allocation/drivers/android/genycloud/GenyInstanceLauncher.js +40 -31
  106. package/src/devices/allocation/drivers/android/genycloud/GenyRegistry.js +121 -0
  107. package/src/devices/allocation/drivers/android/genycloud/services/GenyInstanceLifecycleService.js +24 -0
  108. package/src/devices/{common → allocation}/drivers/android/genycloud/services/GenyRecipesService.js +1 -1
  109. package/src/devices/allocation/drivers/android/genycloud/services/dto/GenyInstance.js +83 -0
  110. package/src/devices/allocation/drivers/android/genycloud/services/dto/GenyRecipe.js +25 -0
  111. package/src/devices/allocation/drivers/ios/SimulatorAllocDriver.js +95 -54
  112. package/src/devices/allocation/drivers/ios/SimulatorQuery.js +24 -0
  113. package/src/devices/allocation/factories/android.js +29 -35
  114. package/src/devices/allocation/factories/ios.js +6 -7
  115. package/src/devices/common/drivers/DeviceCookie.d.ts +12 -0
  116. package/src/devices/common/drivers/android/cookies.d.ts +11 -0
  117. package/src/devices/common/drivers/android/emulator/exec/EmulatorExec.js +17 -5
  118. package/src/devices/common/drivers/android/exec/ADB.js +1 -0
  119. package/src/devices/common/drivers/android/tools/instrumentationArgs.js +7 -1
  120. package/src/devices/common/drivers/ios/cookies.d.ts +9 -0
  121. package/src/devices/common/drivers/ios/tools/AppleSimUtils.js +3 -1
  122. package/src/devices/cookies/index.js +0 -6
  123. package/src/devices/runtime/drivers/android/genycloud/GenyCloudDriver.js +7 -6
  124. package/src/devices/runtime/drivers/ios/SimulatorDriver.js +5 -4
  125. package/src/devices/runtime/drivers/ios/XCUITestUtils.js +21 -11
  126. package/src/devices/runtime/factories/android.js +3 -11
  127. package/src/devices/runtime/factories/ios.js +3 -4
  128. package/src/{servicelocator → devices/servicelocator}/android/emulatorServiceLocator.js +1 -1
  129. package/src/devices/servicelocator/android/genycloudServiceLocator.js +17 -0
  130. package/src/devices/servicelocator/android/index.js +23 -0
  131. package/src/{validation → devices/validation}/EnvironmentValidatorBase.js +1 -0
  132. package/src/{validation → devices/validation}/android/GenycloudEnvValidator.js +2 -2
  133. package/src/{validation → devices/validation}/factories/index.js +1 -1
  134. package/src/{validation → devices/validation}/ios/IosSimulatorEnvValidator.js +2 -2
  135. package/src/environmentFactory.js +1 -11
  136. package/src/invoke.js +0 -2
  137. package/src/ios/expectTwo.js +28 -11
  138. package/src/ios/web.js +302 -0
  139. package/src/ipc/IPCClient.js +22 -1
  140. package/src/ipc/IPCServer.js +42 -1
  141. package/src/ipc/SessionState.js +1 -0
  142. package/src/logger/DetoxLogger.js +2 -2
  143. package/src/realms/DetoxContext.js +8 -0
  144. package/src/realms/DetoxInternalsFacade.js +1 -0
  145. package/src/realms/DetoxPrimaryContext.js +49 -44
  146. package/src/realms/DetoxSecondaryContext.js +27 -0
  147. package/src/realms/symbols.js +6 -0
  148. package/src/utils/PIDService.js +27 -0
  149. package/src/utils/assertIsFunction.js +35 -0
  150. package/src/utils/environment.js +8 -15
  151. package/src/utils/errorUtils.js +3 -3
  152. package/src/utils/invocationTraceDescriptions.js +16 -0
  153. package/src/utils/isArrowFunction.js +24 -0
  154. package/src/utils/isRegExp.js +7 -0
  155. package/tsconfig.json +8 -3
  156. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1-javadoc.jar.md5 +0 -1
  157. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1-javadoc.jar.sha1 +0 -1
  158. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1-javadoc.jar.sha256 +0 -1
  159. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1-javadoc.jar.sha512 +0 -1
  160. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1-sources.jar.md5 +0 -1
  161. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1-sources.jar.sha1 +0 -1
  162. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1-sources.jar.sha256 +0 -1
  163. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1-sources.jar.sha512 +0 -1
  164. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1.aar +0 -0
  165. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1.aar.md5 +0 -1
  166. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1.aar.sha1 +0 -1
  167. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1.aar.sha256 +0 -1
  168. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1.aar.sha512 +0 -1
  169. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1.pom.md5 +0 -1
  170. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1.pom.sha1 +0 -1
  171. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1.pom.sha256 +0 -1
  172. package/Detox-android/com/wix/detox/21.0.0-rc.1/detox-21.0.0-rc.1.pom.sha512 +0 -1
  173. package/runners/jest/reporters/DetoxReporter.js +0 -36
  174. package/src/devices/DeviceRegistry.js +0 -176
  175. package/src/devices/allocation/drivers/AllocationDriverBase.js +0 -30
  176. package/src/devices/allocation/drivers/android/attached/AttachedAndroidLauncher.js +0 -13
  177. package/src/devices/allocation/drivers/android/emulator/EmulatorAllocationHelper.js +0 -72
  178. package/src/devices/allocation/drivers/android/genycloud/GenyDeviceRegistryFactory.js +0 -16
  179. package/src/devices/allocation/drivers/android/genycloud/GenyInstanceAllocationHelper.js +0 -65
  180. package/src/devices/allocation/drivers/ios/SimulatorLauncher.js +0 -21
  181. package/src/devices/common/drivers/DeviceAllocationHelper.js +0 -20
  182. package/src/devices/common/drivers/DeviceLauncher.js +0 -19
  183. package/src/devices/common/drivers/android/genycloud/services/GenyInstanceLifecycleService.js +0 -25
  184. package/src/devices/common/drivers/android/genycloud/services/GenyInstanceLookupService.js +0 -38
  185. package/src/devices/common/drivers/android/genycloud/services/GenyInstanceNaming.js +0 -14
  186. package/src/devices/common/drivers/android/genycloud/services/dto/GenyInstance.js +0 -66
  187. package/src/devices/common/drivers/android/genycloud/services/dto/GenyRecipe.js +0 -13
  188. package/src/devices/cookies/AndroidDeviceCookie.js +0 -13
  189. package/src/devices/cookies/AndroidEmulatorCookie.js +0 -6
  190. package/src/devices/cookies/AttachedAndroidDeviceCookie.js +0 -12
  191. package/src/devices/cookies/DeviceCookie.js +0 -4
  192. package/src/devices/cookies/GenycloudEmulatorCookie.js +0 -20
  193. package/src/devices/cookies/IosCookie.js +0 -6
  194. package/src/devices/cookies/IosSimulatorCookie.js +0 -10
  195. package/src/devices/lifecycle/GenyGlobalLifecycleHandler.js +0 -71
  196. package/src/devices/lifecycle/factories/GenyGlobalLifecycleHandlerFactory.js +0 -18
  197. package/src/invoke/EarlGrey.js +0 -8
  198. package/src/servicelocator/android/genycloudServiceLocator.js +0 -21
  199. package/src/servicelocator/android/index.js +0 -25
  200. package/src/servicelocator/ios.js +0 -7
  201. /package/src/devices/{common → allocation}/drivers/android/genycloud/exec/GenyCloudExec.js +0 -0
  202. /package/src/devices/{common → allocation}/drivers/android/genycloud/services/GenyAuthService.js +0 -0
@@ -1,32 +1,58 @@
1
- // @ts-nocheck
1
+ /**
2
+ * @typedef {import('../../AllocationDriverBase').AllocationDriverBase} AllocationDriverBase
3
+ * @typedef {import('../../../../common/drivers/android/cookies').AndroidDeviceCookie} AndroidDeviceCookie
4
+ */
5
+
2
6
  const _ = require('lodash');
3
7
 
4
- const AndroidEmulatorCookie = require('../../../../cookies/AndroidEmulatorCookie');
5
- const AllocationDriverBase = require('../../AllocationDriverBase');
8
+ const Deferred = require('../../../../../utils/Deferred');
9
+ const log = require('../../../../../utils/logger').child({ cat: 'device,device-allocation' });
6
10
 
7
11
  const { patchAvdSkinConfig } = require('./patchAvdSkinConfig');
8
12
 
9
- class EmulatorAllocDriver extends AllocationDriverBase {
13
+ /**
14
+ * @implements {AllocationDriverBase}
15
+ */
16
+ class EmulatorAllocDriver {
10
17
  /**
11
- * @param adb { ADB }
12
- * @param avdValidator { AVDValidator }
13
- * @param emulatorVersionResolver { EmulatorVersionResolver }
14
- * @param emulatorLauncher { EmulatorLauncher }
15
- * @param allocationHelper { EmulatorAllocationHelper }
18
+ * @param {object} options
19
+ * @param {import('../../../../common/drivers/android/exec/ADB')} options.adb
20
+ * @param {import('./AVDValidator')} options.avdValidator
21
+ * @param {DetoxInternals.RuntimeConfig} options.detoxConfig
22
+ * @param {import('../../../DeviceRegistry')} options.deviceRegistry
23
+ * @param {import('./FreeEmulatorFinder')} options.freeDeviceFinder
24
+ * @param {import('./FreePortFinder')} options.freePortFinder
25
+ * @param {import('./EmulatorLauncher')} options.emulatorLauncher
26
+ * @param {import('./EmulatorVersionResolver')} options.emulatorVersionResolver
16
27
  */
17
- constructor({ adb, avdValidator, emulatorVersionResolver, emulatorLauncher, allocationHelper }) {
18
- super();
28
+ constructor({
29
+ adb,
30
+ avdValidator,
31
+ detoxConfig,
32
+ deviceRegistry,
33
+ freeDeviceFinder,
34
+ freePortFinder,
35
+ emulatorVersionResolver,
36
+ emulatorLauncher
37
+ }) {
19
38
  this._adb = adb;
20
39
  this._avdValidator = avdValidator;
40
+ this._deviceRegistry = deviceRegistry;
21
41
  this._emulatorVersionResolver = emulatorVersionResolver;
22
42
  this._emulatorLauncher = emulatorLauncher;
23
- this._allocationHelper = allocationHelper;
24
- this._launchInfo = {};
43
+ this._freeDeviceFinder = freeDeviceFinder;
44
+ this._freePortFinder = freePortFinder;
45
+ this._shouldShutdown = detoxConfig.behavior.cleanup.shutdownDevice;
46
+ this._fixAvdConfigIniSkinNameIfNeeded = _.memoize(this._fixAvdConfigIniSkinNameIfNeeded.bind(this));
47
+ }
48
+
49
+ async init() {
50
+ await this._deviceRegistry.unregisterZombieDevices();
25
51
  }
26
52
 
27
53
  /**
28
54
  * @param deviceConfig
29
- * @returns {Promise<AndroidEmulatorCookie>}
55
+ * @returns {Promise<AndroidDeviceCookie>}
30
56
  */
31
57
  async allocate(deviceConfig) {
32
58
  const avdName = deviceConfig.device.avdName;
@@ -34,50 +60,83 @@ class EmulatorAllocDriver extends AllocationDriverBase {
34
60
  await this._avdValidator.validate(avdName, deviceConfig.headless);
35
61
  await this._fixAvdConfigIniSkinNameIfNeeded(avdName, deviceConfig.headless);
36
62
 
37
- const allocResult = await this._allocationHelper.allocateDevice(avdName);
38
- const { adbName } = allocResult;
39
-
40
- this._launchInfo[adbName] = {
41
- avdName,
42
- isRunning: allocResult.isRunning,
43
- launchOptions: {
44
- bootArgs: deviceConfig.bootArgs,
45
- gpuMode: deviceConfig.gpuMode,
46
- headless: deviceConfig.headless,
47
- readonly: deviceConfig.readonly,
48
- port: allocResult.placeholderPort,
49
- },
50
- };
63
+ const adbName = await this._deviceRegistry.registerDevice(async () => {
64
+ let adbName = await this._freeDeviceFinder.findFreeDevice(avdName);
65
+ if (!adbName) {
66
+ const port = await this._freePortFinder.findFreePort();
67
+ adbName = `emulator-${port}`;
68
+
69
+ await this._emulatorLauncher.launch({
70
+ bootArgs: deviceConfig.bootArgs,
71
+ gpuMode: deviceConfig.gpuMode,
72
+ headless: deviceConfig.headless,
73
+ readonly: deviceConfig.readonly,
74
+ avdName,
75
+ adbName,
76
+ port,
77
+ });
78
+ }
51
79
 
52
- return new AndroidEmulatorCookie(adbName);
80
+ return adbName;
81
+ });
82
+
83
+ return {
84
+ id: adbName,
85
+ adbName,
86
+ name: `${adbName} (${avdName})`,
87
+ };
53
88
  }
54
89
 
55
90
  /**
56
- * @param {AndroidEmulatorCookie} deviceCookie
57
- * @returns {Promise<void>}
91
+ * @param {AndroidDeviceCookie} deviceCookie
58
92
  */
59
93
  async postAllocate(deviceCookie) {
60
94
  const { adbName } = deviceCookie;
61
- const { avdName, isRunning, launchOptions } = this._launchInfo[adbName];
62
95
 
63
- await this._emulatorLauncher.launch(avdName, adbName, isRunning, launchOptions);
96
+ await this._emulatorLauncher.awaitEmulatorBoot(adbName);
64
97
  await this._adb.apiLevel(adbName);
65
98
  await this._adb.disableAndroidAnimations(adbName);
66
99
  await this._adb.unlockScreen(adbName);
67
100
  }
68
101
 
69
102
  /**
70
- * @param cookie { AndroidEmulatorCookie }
71
- * @param options { DeallocOptions }
72
- * @return { Promise<void> }
103
+ * @param cookie {AndroidDeviceCookie}
104
+ * @param options {Partial<import('../../AllocationDriverBase').DeallocOptions>}
105
+ * @return {Promise<void>}
73
106
  */
74
107
  async free(cookie, options = {}) {
75
108
  const { adbName } = cookie;
76
109
 
77
- await this._allocationHelper.deallocateDevice(adbName);
78
-
79
110
  if (options.shutdown) {
111
+ await this._doShutdown(adbName);
112
+ await this._deviceRegistry.unregisterDevice(adbName);
113
+ } else {
114
+ await this._deviceRegistry.releaseDevice(adbName);
115
+ }
116
+ }
117
+
118
+ async cleanup() {
119
+ if (this._shouldShutdown) {
120
+ const { devices } = await this._adb.devices();
121
+ const actualEmulators = devices.map((device) => device.adbName);
122
+ const sessionDevices = await this._deviceRegistry.readSessionDevices();
123
+ const emulatorsToShutdown = _.intersection(sessionDevices.getIds(), actualEmulators);
124
+ const shutdownPromises = emulatorsToShutdown.map((adbName) => this._doShutdown(adbName));
125
+ await Promise.all(shutdownPromises);
126
+ }
127
+
128
+ await this._deviceRegistry.unregisterSessionDevices();
129
+ }
130
+
131
+ /**
132
+ * @param {string} adbName
133
+ * @return {Promise<void>}
134
+ */
135
+ async _doShutdown(adbName) {
136
+ try {
80
137
  await this._emulatorLauncher.shutdown(adbName);
138
+ } catch (err) {
139
+ log.warn({ err }, `Failed to shutdown emulator ${adbName}`);
81
140
  }
82
141
  }
83
142
 
@@ -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