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,43 +1,89 @@
1
- // @ts-nocheck
2
- const log = require('../../utils/logger').child({ cat: 'device' });
1
+ /**
2
+ * @typedef {import('./drivers/AllocationDriverBase').AllocationDriverBase} AllocationDriverBase
3
+ * @typedef {import('./drivers/AllocationDriverBase').DeallocOptions} DeallocOptions
4
+ * @typedef {import('../common/drivers/DeviceCookie').DeviceCookie} DeviceCookie
5
+ */
6
+
7
+ const log = require('../../utils/logger').child({ cat: 'device,device-allocation' });
3
8
  const traceMethods = require('../../utils/traceMethods');
4
9
 
5
10
  class DeviceAllocator {
6
11
  /**
7
- * @param allocationDriver { AllocationDriverBase }
12
+ * @param {AllocationDriverBase} allocationDriver
8
13
  */
9
14
  constructor(allocationDriver) {
10
15
  this._driver = allocationDriver;
11
- traceMethods(log, this, ['allocate', 'postAllocate', 'free']);
16
+ this._counter = 0;
17
+ this._ids = new Map();
18
+ traceMethods(log, this, ['init', 'cleanup', 'emergencyCleanup']);
12
19
  }
13
20
 
14
21
  /**
15
- * @param deviceConfig { Object }
16
- * @return {Promise<DeviceCookie>}
22
+ * @returns {Promise<void>}
17
23
  */
18
- allocate(deviceConfig) {
19
- return this._driver.allocate(deviceConfig);
24
+ async init() {
25
+ if (typeof this._driver.init === 'function') {
26
+ await this._driver.init();
27
+ }
20
28
  }
21
29
 
22
30
  /**
23
- * @param {DeviceCookie} deviceCookie
24
- * @return {Promise<unknown>}
31
+ * @param {Detox.DetoxDeviceConfig} deviceConfig
32
+ * @returns {Promise<DeviceCookie>}
25
33
  */
26
- postAllocate(deviceCookie) {
27
- if (typeof this._driver.postAllocate !== 'function') {
28
- return Promise.resolve();
29
- }
34
+ async allocate(deviceConfig) {
35
+ const tid = this._counter++;
36
+ return await log.trace.complete({ data: deviceConfig, id: tid }, 'allocate', async () => {
37
+ const cookie = await this._driver.allocate(deviceConfig);
38
+ log.debug({ data: cookie }, `settled on ${cookie.name || cookie.id}`);
39
+ this._ids.set(cookie.id, tid);
40
+ return cookie;
41
+ });
42
+ }
30
43
 
31
- return this._driver.postAllocate(deviceCookie);
44
+ /**
45
+ * @param {DeviceCookie} cookie
46
+ * @returns {Promise<DeviceCookie>}
47
+ */
48
+ async postAllocate(cookie) {
49
+ const tid = this._ids.get(cookie.id);
50
+ return await log.trace.complete({ data: cookie, id: tid }, `post-allocate: ${cookie.id}`, async () => {
51
+ const updatedCookie = typeof this._driver.postAllocate === 'function'
52
+ ? await this._driver.postAllocate(cookie)
53
+ : undefined;
54
+
55
+ return updatedCookie || cookie;
56
+ });
32
57
  }
33
58
 
34
59
  /**
35
- * @param cookie { DeviceCookie }
36
- * @param options { DeallocOptions }
37
- * @return {Promise<void>}
60
+ * @param {DeviceCookie} cookie
61
+ * @param {DeallocOptions} options
62
+ * @returns {Promise<void>}
38
63
  */
39
- free(cookie, options) {
40
- return this._driver.free(cookie, options);
64
+ async free(cookie, options = {}) {
65
+ const tid = this._ids.get(cookie.id);
66
+ await log.trace.complete({ data: options, id: tid }, `free: ${cookie.id}`, async () => {
67
+ await this._driver.free(cookie, options);
68
+ });
69
+ }
70
+
71
+ /**
72
+ * @returns {Promise<void>}
73
+ */
74
+ async cleanup() {
75
+ if (typeof this._driver.cleanup === 'function') {
76
+ await this._driver.cleanup();
77
+ }
78
+ }
79
+
80
+ /**
81
+ * @returns {void}
82
+ */
83
+ emergencyCleanup() {
84
+ if (typeof this._driver.emergencyCleanup === 'function') {
85
+ this._driver.emergencyCleanup();
86
+ }
41
87
  }
42
88
  }
43
89
 
@@ -0,0 +1,44 @@
1
+ class DeviceListReadonly {
2
+ constructor(devices = []) {
3
+ this._devices = new Map(devices.map(device => [device.id, device]));
4
+ }
5
+
6
+ concat(other) {
7
+ return new DeviceListReadonly([...this, ...other]);
8
+ }
9
+
10
+ getIds() {
11
+ return [...this._devices.keys()];
12
+ }
13
+
14
+ [Symbol.iterator]() {
15
+ return this._devices.values();
16
+ }
17
+
18
+ /**
19
+ * @param {string} deviceId
20
+ * @returns {boolean}
21
+ */
22
+ includes(deviceId) {
23
+ return this._devices.has(deviceId);
24
+ }
25
+ }
26
+
27
+ class DeviceList extends DeviceListReadonly {
28
+ filter(predicate) {
29
+ return new DeviceListReadonly([...this].filter(predicate));
30
+ }
31
+
32
+ add(deviceId, data) {
33
+ this._devices.set(deviceId, {
34
+ id: deviceId,
35
+ ...data,
36
+ });
37
+ }
38
+
39
+ delete(deviceId) {
40
+ this._devices.delete(deviceId);
41
+ }
42
+ }
43
+
44
+ module.exports = DeviceList;
@@ -0,0 +1,189 @@
1
+ const ExclusiveLockfile = require('../../utils/ExclusiveLockfile');
2
+ const PIDService = require('../../utils/PIDService');
3
+ const { getDeviceRegistryPath } = require('../../utils/environment');
4
+ const safeAsync = require('../../utils/safeAsync');
5
+
6
+ const DeviceList = require('./DeviceList');
7
+
8
+ const readOptions = {
9
+ encoding: 'utf8',
10
+ };
11
+
12
+ class DeviceRegistry {
13
+ constructor({
14
+ lockfilePath = getDeviceRegistryPath(),
15
+ sessionId = '',
16
+ pidService = new PIDService(),
17
+ } = {}) {
18
+ /***
19
+ * @private
20
+ * @type {string}
21
+ */
22
+ this._lockfilePath = lockfilePath;
23
+ /***
24
+ * @private
25
+ * @type {string}
26
+ */
27
+ this._sessionId = sessionId;
28
+ /***
29
+ * @private
30
+ */
31
+ this._pidService = pidService;
32
+ /***
33
+ * @protected
34
+ * @type {ExclusiveLockfile}
35
+ */
36
+ this._lockfile = new ExclusiveLockfile(this._lockfilePath, {
37
+ getInitialState: this._getInitialLockFileState.bind(this),
38
+ readOptions,
39
+ });
40
+ }
41
+
42
+ get lockFilePath() {
43
+ return this._lockfilePath;
44
+ }
45
+
46
+ /**
47
+ * Safety method to ensure that there are no remains of previously crashed Detox sessions.
48
+ */
49
+ async reset() {
50
+ await this._lockfile.exclusively(() => {
51
+ const empty = this._getInitialLockFileState();
52
+ this._lockfile.write(empty);
53
+ });
54
+ }
55
+
56
+ /***
57
+ * @param {string|Function} getDeviceId
58
+ * @returns {Promise<string>}
59
+ */
60
+ async registerDevice(getDeviceId) {
61
+ return this._lockfile.exclusively(async () => {
62
+ const deviceId = await safeAsync(getDeviceId);
63
+ if (deviceId) {
64
+ this._upsertDevice(deviceId, {
65
+ busy: true,
66
+ sessionId: this._sessionId,
67
+ pid: this._pidService.getPid(),
68
+ });
69
+ }
70
+ return deviceId;
71
+ });
72
+ }
73
+
74
+ /***
75
+ * @param {string|Function} getDeviceId
76
+ * @returns {Promise<string>}
77
+ */
78
+ async releaseDevice(getDeviceId) {
79
+ return this._lockfile.exclusively(async () => {
80
+ const deviceId = await safeAsync(getDeviceId);
81
+ if (deviceId) {
82
+ this._upsertDevice(deviceId, {
83
+ busy: false,
84
+ sessionId: this._sessionId,
85
+ pid: this._pidService.getPid(),
86
+ });
87
+ }
88
+ return deviceId;
89
+ });
90
+ }
91
+
92
+ /***
93
+ * @param {string|Function} getDeviceId
94
+ * @returns {Promise<void>}
95
+ */
96
+ async unregisterDevice(getDeviceId) {
97
+ await this._lockfile.exclusively(async () => {
98
+ const deviceId = await safeAsync(getDeviceId);
99
+ if (deviceId) {
100
+ this._deleteDevice(deviceId);
101
+ }
102
+ });
103
+ }
104
+
105
+ async unregisterSessionDevices() {
106
+ await this._lockfile.exclusively(async () => {
107
+ const allDevices = this._getRegisteredDevices();
108
+ const sessionDevices = allDevices.filter(device => device.sessionId === this._sessionId);
109
+ for (const id of sessionDevices.getIds()) {
110
+ allDevices.delete(id);
111
+ }
112
+ this._lockfile.write([...allDevices]);
113
+ });
114
+ }
115
+
116
+ async unregisterZombieDevices() {
117
+ await this._lockfile.exclusively(async () => {
118
+ const allDevices = this._getRegisteredDevices();
119
+ const zombieDevices = allDevices.filter(device => {
120
+ return device.sessionId !== this._sessionId && !this._pidService.isAlive(device.pid);
121
+ });
122
+
123
+ for (const id of zombieDevices.getIds()) {
124
+ allDevices.delete(id);
125
+ }
126
+ this._lockfile.write([...allDevices]);
127
+ });
128
+ }
129
+
130
+ /**
131
+ * @returns {Promise<DeviceList>}
132
+ */
133
+ async readSessionDevices() {
134
+ let devices;
135
+ await this._lockfile.exclusively(() => {
136
+ devices = this._getSessionDevicesSync();
137
+ });
138
+ return devices;
139
+ }
140
+
141
+ getTakenDevicesSync() {
142
+ const allDevices = this._getRegisteredDevices();
143
+ const busyDevices = allDevices.filter(device => device.busy);
144
+ const externalDevices = allDevices.filter(device => device.sessionId !== this._sessionId);
145
+ return busyDevices.concat(externalDevices);
146
+ }
147
+
148
+ /***
149
+ * @private
150
+ */
151
+ _getInitialLockFileState() {
152
+ return [];
153
+ }
154
+
155
+ /**
156
+ * @private
157
+ */
158
+ _upsertDevice(deviceId, data) {
159
+ const devices = this._getRegisteredDevices();
160
+ devices.add(deviceId, data);
161
+ this._lockfile.write([...devices]);
162
+ }
163
+
164
+ /**
165
+ * @private
166
+ */
167
+ _deleteDevice(deviceId) {
168
+ const devices = this._getRegisteredDevices();
169
+ devices.delete(deviceId);
170
+ this._lockfile.write([...devices]);
171
+ }
172
+
173
+ /***
174
+ * @private
175
+ * @returns {DeviceList}
176
+ */
177
+ _getRegisteredDevices() {
178
+ const devices = this._lockfile.read();
179
+ return new DeviceList(devices);
180
+ }
181
+
182
+ _getSessionDevicesSync() {
183
+ const devices = this._getRegisteredDevices();
184
+ const sessionDevices = devices.filter(device => device.sessionId === this._sessionId);
185
+ return sessionDevices;
186
+ }
187
+ }
188
+
189
+ module.exports = DeviceRegistry;
@@ -0,0 +1,15 @@
1
+ /* eslint-disable import/no-unresolved,node/no-missing-import,node/no-unsupported-features/es-syntax */
2
+ import { DeviceCookie } from '../../common/drivers/DeviceCookie';
3
+
4
+ export interface DeallocOptions {
5
+ shutdown?: boolean;
6
+ }
7
+
8
+ export interface AllocationDriverBase {
9
+ init?(): Promise<void>;
10
+ allocate(deviceConfig: any): Promise<DeviceCookie>;
11
+ postAllocate?(deviceCookie: DeviceCookie): Promise<DeviceCookie | void>;
12
+ free(cookie: DeviceCookie, options: DeallocOptions): Promise<void>;
13
+ cleanup?(): Promise<void>;
14
+ emergencyCleanup?(): void;
15
+ }
@@ -1,6 +1,6 @@
1
- const log = require('../../../../../utils/logger').child({ cat: 'device' });
1
+ const log = require('../../../../utils/logger').child({ cat: 'device' });
2
2
 
3
- const DEVICE_LOOKUP_LOG_EVT = 'DEVICE_LOOKUP';
3
+ const DEVICE_LOOKUP = { event: 'DEVICE_LOOKUP' };
4
4
 
5
5
  class FreeDeviceFinder {
6
6
  constructor(adb, deviceRegistry) {
@@ -10,8 +10,9 @@ class FreeDeviceFinder {
10
10
 
11
11
  async findFreeDevice(deviceQuery) {
12
12
  const { devices } = await this.adb.devices();
13
+ const takenDevices = this.deviceRegistry.getTakenDevicesSync();
13
14
  for (const candidate of devices) {
14
- if (await this._isDeviceFreeAndMatching(candidate, deviceQuery)) {
15
+ if (await this._isDeviceFreeAndMatching(takenDevices, candidate, deviceQuery)) {
15
16
  return candidate.adbName;
16
17
  }
17
18
  }
@@ -19,30 +20,30 @@ class FreeDeviceFinder {
19
20
  }
20
21
 
21
22
  /**
22
- * @protected
23
+ * @private
23
24
  */
24
- async _isDeviceFreeAndMatching(candidate, deviceQuery) {
25
+ async _isDeviceFreeAndMatching(takenDevices, candidate, deviceQuery) {
25
26
  const { adbName } = candidate;
26
27
 
27
- const isTaken = this.deviceRegistry.includes(adbName);
28
+ const isTaken = takenDevices.includes(adbName);
28
29
  if (isTaken) {
29
- log.debug({ event: DEVICE_LOOKUP_LOG_EVT }, `Device ${adbName} is already taken, skipping...`);
30
+ log.debug(DEVICE_LOOKUP, `Device ${adbName} is already taken, skipping...`);
30
31
  return false;
31
32
  }
32
33
 
33
34
  const isOffline = candidate.status === 'offline';
34
35
  if (isOffline) {
35
- log.debug({ event: DEVICE_LOOKUP_LOG_EVT }, `Device ${adbName} is offline, skipping...`);
36
+ log.debug(DEVICE_LOOKUP, `Device ${adbName} is offline, skipping...`);
36
37
  return false;
37
38
  }
38
39
 
39
40
  const isMatching = await this._isDeviceMatching(candidate, deviceQuery);
40
41
  if (!isMatching) {
41
- log.debug({ event: DEVICE_LOOKUP_LOG_EVT }, `Device ${adbName} does not match "${deviceQuery}"`);
42
+ log.debug(DEVICE_LOOKUP, `Device ${adbName} does not match "${deviceQuery}"`);
42
43
  return false;
43
44
  }
44
45
 
45
- log.debug({ event: DEVICE_LOOKUP_LOG_EVT }, `Found a matching & free device ${candidate.adbName}`);
46
+ log.debug(DEVICE_LOOKUP, `Found a matching & free device ${candidate.adbName}`);
46
47
  return true;
47
48
  }
48
49
 
@@ -1,20 +1,26 @@
1
- // @ts-nocheck
2
- const AttachedAndroidDeviceCookie = require('../../../../cookies/AttachedAndroidDeviceCookie');
3
- const AllocationDriverBase = require('../../AllocationDriverBase');
1
+ /**
2
+ * @typedef {import('../../AllocationDriverBase').AllocationDriverBase} AllocationDriverBase
3
+ * @typedef {import('../../../../common/drivers/android/cookies').AndroidDeviceCookie} AndroidDeviceCookie
4
+ */
4
5
 
5
- class AttachedAndroidAllocDriver extends AllocationDriverBase {
6
+ /**
7
+ * @implements {AllocationDriverBase}
8
+ */
9
+ class AttachedAndroidAllocDriver {
6
10
  /**
7
- * @param adb { ADB }
8
- * @param deviceRegistry { DeviceRegistry }
9
- * @param freeDeviceFinder { FreeDeviceFinder }
10
- * @param attachedAndroidLauncher { AttachedAndroidLauncher }
11
+ * @param {object} options
12
+ * @param {import('../../../../common/drivers/android/exec/ADB')} options.adb
13
+ * @param {import('../../../DeviceRegistry')} options.deviceRegistry
14
+ * @param {import('../FreeDeviceFinder')} options.freeDeviceFinder
11
15
  */
12
- constructor({ adb, deviceRegistry, freeDeviceFinder, attachedAndroidLauncher }) {
13
- super();
16
+ constructor({ adb, deviceRegistry, freeDeviceFinder }) {
14
17
  this._adb = adb;
15
18
  this._deviceRegistry = deviceRegistry;
16
19
  this._freeDeviceFinder = freeDeviceFinder;
17
- this._attachedAndroidLauncher = attachedAndroidLauncher;
20
+ }
21
+
22
+ async init() {
23
+ await this._deviceRegistry.unregisterZombieDevices();
18
24
  }
19
25
 
20
26
  /**
@@ -23,13 +29,13 @@ class AttachedAndroidAllocDriver extends AllocationDriverBase {
23
29
  */
24
30
  async allocate(deviceConfig) {
25
31
  const adbNamePattern = deviceConfig.device.adbName;
26
- const adbName = await this._deviceRegistry.allocateDevice(() => this._freeDeviceFinder.findFreeDevice(adbNamePattern));
32
+ const adbName = await this._deviceRegistry.registerDevice(() => this._freeDeviceFinder.findFreeDevice(adbNamePattern));
27
33
 
28
- return new AttachedAndroidDeviceCookie(adbName);
34
+ return { id: adbName, adbName };
29
35
  }
30
36
 
31
37
  /**
32
- * @param {AttachedAndroidDeviceCookie} deviceCookie
38
+ * @param {AndroidDeviceCookie} deviceCookie
33
39
  * @returns {Promise<void>}
34
40
  */
35
41
  async postAllocate(deviceCookie) {
@@ -38,16 +44,15 @@ class AttachedAndroidAllocDriver extends AllocationDriverBase {
38
44
  // TODO Also disable native animations?
39
45
  await this._adb.apiLevel(adbName);
40
46
  await this._adb.unlockScreen(adbName);
41
- await this._attachedAndroidLauncher.notifyLaunchCompleted(adbName);
42
47
  }
43
48
 
44
49
  /**
45
- * @param cookie { AttachedAndroidDeviceCookie }
50
+ * @param cookie { AndroidDeviceCookie }
46
51
  * @return {Promise<void>}
47
52
  */
48
53
  async free(cookie) {
49
54
  const { adbName } = cookie;
50
- await this._deviceRegistry.disposeDevice(adbName);
55
+ await this._deviceRegistry.unregisterDevice(adbName);
51
56
  }
52
57
  }
53
58
 
@@ -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