detox 20.12.1 → 20.13.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (141) hide show
  1. package/Detox-android/com/wix/detox/{20.12.1/detox-20.12.1-javadoc.jar → 20.13.0/detox-20.13.0-javadoc.jar} +0 -0
  2. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0-javadoc.jar.md5 +1 -0
  3. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0-javadoc.jar.sha1 +1 -0
  4. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0-javadoc.jar.sha256 +1 -0
  5. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0-javadoc.jar.sha512 +1 -0
  6. package/Detox-android/com/wix/detox/{20.12.1/detox-20.12.1-sources.jar → 20.13.0/detox-20.13.0-sources.jar} +0 -0
  7. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0-sources.jar.md5 +1 -0
  8. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0-sources.jar.sha1 +1 -0
  9. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0-sources.jar.sha256 +1 -0
  10. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0-sources.jar.sha512 +1 -0
  11. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0.aar +0 -0
  12. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0.aar.md5 +1 -0
  13. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0.aar.sha1 +1 -0
  14. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0.aar.sha256 +1 -0
  15. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0.aar.sha512 +1 -0
  16. package/Detox-android/com/wix/detox/{20.12.1/detox-20.12.1.pom → 20.13.0/detox-20.13.0.pom} +1 -7
  17. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0.pom.md5 +1 -0
  18. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0.pom.sha1 +1 -0
  19. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0.pom.sha256 +1 -0
  20. package/Detox-android/com/wix/detox/20.13.0/detox-20.13.0.pom.sha512 +1 -0
  21. package/Detox-android/com/wix/detox/maven-metadata.xml +4 -4
  22. package/Detox-android/com/wix/detox/maven-metadata.xml.md5 +1 -1
  23. package/Detox-android/com/wix/detox/maven-metadata.xml.sha1 +1 -1
  24. package/Detox-android/com/wix/detox/maven-metadata.xml.sha256 +1 -1
  25. package/Detox-android/com/wix/detox/maven-metadata.xml.sha512 +1 -1
  26. package/Detox-ios-src.tbz +0 -0
  27. package/Detox-ios.tbz +0 -0
  28. package/android/detox/build.gradle +4 -3
  29. package/android/detox/src/full/java/com/wix/detox/espresso/action/AdjustSliderToPositionAction.kt +2 -2
  30. package/android/detox/src/full/java/com/wix/detox/espresso/action/GetAttributesAction.kt +30 -31
  31. package/android/detox/src/full/java/com/wix/detox/espresso/common/MaterialSliderHelper.kt +21 -0
  32. package/android/detox/src/full/java/com/wix/detox/espresso/common/{SliderHelper.kt → ReactSliderHelper.kt} +6 -5
  33. package/android/detox/src/full/java/com/wix/detox/espresso/matcher/ViewMatchers.kt +2 -2
  34. package/android/detox/src/testFull/java/com/wix/detox/espresso/common/MaterialSliderHelperTest.kt +33 -0
  35. package/android/detox/src/testFull/java/com/wix/detox/espresso/common/{SliderHelperTest.kt → ReactSliderHelperTest.kt} +3 -3
  36. package/internals.d.ts +10 -1
  37. package/local-cli/reset-lock-file.js +5 -9
  38. package/package.json +5 -6
  39. package/runners/jest/reporters/DetoxReporter.js +127 -9
  40. package/runners/jest/testEnvironment/index.js +5 -0
  41. package/src/DetoxWorker.js +5 -11
  42. package/src/artifacts/providers/index.js +3 -3
  43. package/src/artifacts/screenshot/SimulatorScreenshotPlugin.js +0 -17
  44. package/src/configuration/composeLoggerConfig.js +1 -0
  45. package/src/devices/allocation/DeviceAllocator.js +66 -20
  46. package/src/devices/allocation/DeviceList.js +44 -0
  47. package/src/devices/allocation/DeviceRegistry.js +189 -0
  48. package/src/devices/allocation/drivers/AllocationDriverBase.d.ts +15 -0
  49. package/src/devices/{common/drivers/android/tools → allocation/drivers/android}/FreeDeviceFinder.js +11 -10
  50. package/src/devices/allocation/drivers/android/attached/AttachedAndroidAllocDriver.js +22 -17
  51. package/src/devices/allocation/drivers/android/emulator/EmulatorAllocDriver.js +97 -38
  52. package/src/devices/allocation/drivers/android/emulator/EmulatorLauncher.js +32 -45
  53. package/src/devices/allocation/drivers/android/emulator/FreeEmulatorFinder.js +1 -1
  54. package/src/devices/allocation/drivers/android/emulator/FreePortFinder.js +37 -0
  55. package/src/devices/allocation/drivers/android/emulator/launchEmulatorProcess.js +3 -3
  56. package/src/devices/allocation/drivers/android/genycloud/GenyAllocDriver.js +104 -32
  57. package/src/devices/allocation/drivers/android/genycloud/GenyInstanceLauncher.js +40 -31
  58. package/src/devices/allocation/drivers/android/genycloud/GenyRegistry.js +121 -0
  59. package/src/devices/allocation/drivers/android/genycloud/services/GenyInstanceLifecycleService.js +24 -0
  60. package/src/devices/{common → allocation}/drivers/android/genycloud/services/GenyRecipesService.js +1 -1
  61. package/src/devices/allocation/drivers/android/genycloud/services/dto/GenyInstance.js +83 -0
  62. package/src/devices/allocation/drivers/android/genycloud/services/dto/GenyRecipe.js +25 -0
  63. package/src/devices/allocation/drivers/ios/SimulatorAllocDriver.js +94 -51
  64. package/src/devices/allocation/drivers/ios/SimulatorLauncher.js +11 -7
  65. package/src/devices/allocation/drivers/ios/SimulatorQuery.js +24 -0
  66. package/src/devices/allocation/factories/android.js +29 -35
  67. package/src/devices/allocation/factories/ios.js +7 -5
  68. package/src/devices/common/drivers/DeviceCookie.d.ts +12 -0
  69. package/src/devices/common/drivers/android/cookies.d.ts +11 -0
  70. package/src/devices/common/drivers/android/emulator/exec/EmulatorExec.js +17 -5
  71. package/src/devices/common/drivers/android/exec/ADB.js +1 -0
  72. package/src/devices/common/drivers/ios/cookies.d.ts +9 -0
  73. package/src/devices/cookies/index.js +0 -6
  74. package/src/devices/runtime/drivers/android/genycloud/GenyCloudDriver.js +7 -6
  75. package/src/devices/runtime/factories/android.js +3 -11
  76. package/src/devices/runtime/factories/ios.js +3 -2
  77. package/src/{servicelocator → devices/servicelocator}/android/emulatorServiceLocator.js +1 -1
  78. package/src/devices/servicelocator/android/genycloudServiceLocator.js +17 -0
  79. package/src/devices/servicelocator/android/index.js +23 -0
  80. package/src/{validation → devices/validation}/EnvironmentValidatorBase.js +1 -0
  81. package/src/{validation → devices/validation}/android/GenycloudEnvValidator.js +2 -2
  82. package/src/{validation → devices/validation}/factories/index.js +1 -1
  83. package/src/{validation → devices/validation}/ios/IosSimulatorEnvValidator.js +2 -2
  84. package/src/environmentFactory.js +1 -11
  85. package/src/ipc/IPCClient.js +22 -1
  86. package/src/ipc/IPCServer.js +40 -1
  87. package/src/ipc/SessionState.js +1 -0
  88. package/src/logger/DetoxLogger.js +2 -2
  89. package/src/realms/DetoxContext.js +8 -0
  90. package/src/realms/DetoxInternalsFacade.js +1 -0
  91. package/src/realms/DetoxPrimaryContext.js +48 -42
  92. package/src/realms/DetoxSecondaryContext.js +27 -0
  93. package/src/realms/symbols.js +6 -0
  94. package/src/utils/PIDService.js +27 -0
  95. package/src/utils/environment.js +8 -15
  96. package/src/utils/errorUtils.js +3 -3
  97. package/tsconfig.json +5 -3
  98. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1-javadoc.jar.md5 +0 -1
  99. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1-javadoc.jar.sha1 +0 -1
  100. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1-javadoc.jar.sha256 +0 -1
  101. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1-javadoc.jar.sha512 +0 -1
  102. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1-sources.jar.md5 +0 -1
  103. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1-sources.jar.sha1 +0 -1
  104. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1-sources.jar.sha256 +0 -1
  105. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1-sources.jar.sha512 +0 -1
  106. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.aar +0 -0
  107. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.aar.md5 +0 -1
  108. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.aar.sha1 +0 -1
  109. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.aar.sha256 +0 -1
  110. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.aar.sha512 +0 -1
  111. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.pom.md5 +0 -1
  112. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.pom.sha1 +0 -1
  113. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.pom.sha256 +0 -1
  114. package/Detox-android/com/wix/detox/20.12.1/detox-20.12.1.pom.sha512 +0 -1
  115. package/src/devices/DeviceRegistry.js +0 -176
  116. package/src/devices/allocation/drivers/AllocationDriverBase.js +0 -30
  117. package/src/devices/allocation/drivers/android/attached/AttachedAndroidLauncher.js +0 -13
  118. package/src/devices/allocation/drivers/android/emulator/EmulatorAllocationHelper.js +0 -72
  119. package/src/devices/allocation/drivers/android/genycloud/GenyDeviceRegistryFactory.js +0 -16
  120. package/src/devices/allocation/drivers/android/genycloud/GenyInstanceAllocationHelper.js +0 -65
  121. package/src/devices/common/drivers/DeviceAllocationHelper.js +0 -20
  122. package/src/devices/common/drivers/DeviceLauncher.js +0 -19
  123. package/src/devices/common/drivers/android/genycloud/services/GenyInstanceLifecycleService.js +0 -25
  124. package/src/devices/common/drivers/android/genycloud/services/GenyInstanceLookupService.js +0 -38
  125. package/src/devices/common/drivers/android/genycloud/services/GenyInstanceNaming.js +0 -14
  126. package/src/devices/common/drivers/android/genycloud/services/dto/GenyInstance.js +0 -66
  127. package/src/devices/common/drivers/android/genycloud/services/dto/GenyRecipe.js +0 -13
  128. package/src/devices/cookies/AndroidDeviceCookie.js +0 -13
  129. package/src/devices/cookies/AndroidEmulatorCookie.js +0 -6
  130. package/src/devices/cookies/AttachedAndroidDeviceCookie.js +0 -12
  131. package/src/devices/cookies/DeviceCookie.js +0 -4
  132. package/src/devices/cookies/GenycloudEmulatorCookie.js +0 -20
  133. package/src/devices/cookies/IosCookie.js +0 -6
  134. package/src/devices/cookies/IosSimulatorCookie.js +0 -10
  135. package/src/devices/lifecycle/GenyGlobalLifecycleHandler.js +0 -71
  136. package/src/devices/lifecycle/factories/GenyGlobalLifecycleHandlerFactory.js +0 -18
  137. package/src/servicelocator/android/genycloudServiceLocator.js +0 -21
  138. package/src/servicelocator/android/index.js +0 -25
  139. package/src/servicelocator/ios.js +0 -7
  140. /package/src/devices/{common → allocation}/drivers/android/genycloud/exec/GenyCloudExec.js +0 -0
  141. /package/src/devices/{common → allocation}/drivers/android/genycloud/services/GenyAuthService.js +0 -0
@@ -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) {
@@ -0,0 +1,83 @@
1
+ const Recipe = require('./GenyRecipe');
2
+
3
+ const STATE_ONLINE = 'ONLINE';
4
+ const STATE_CREATING = 'CREATING';
5
+ const STATE_BOOTING = 'BOOTING';
6
+ const STATE_STARTING = 'STARTING';
7
+ const initStates = new Set([
8
+ STATE_CREATING,
9
+ STATE_BOOTING,
10
+ STATE_STARTING,
11
+ ]);
12
+
13
+ const data = Symbol('data');
14
+ const recipe = Symbol('recipe');
15
+
16
+ class GenyInstance {
17
+ constructor(rawInstance) {
18
+ this[data] = rawInstance;
19
+ this[recipe] = new Recipe(rawInstance.recipe);
20
+ }
21
+
22
+ get uuid() {
23
+ return this[data].uuid;
24
+ }
25
+
26
+ get name() {
27
+ return this[data].name;
28
+ }
29
+
30
+ /**
31
+ * According to Genymotion's API docs, state is an enum with these possible values (description is not official):
32
+ * - "CREATING": Handling instance creation request
33
+ * - "STARTING": Instance created but not yet available for usage
34
+ * - "BOOTING": Instance created & started; Android OS is not booting
35
+ * - "ONLINE": Instance is ready for action
36
+ * - "RECYCLED": Instance has been automatically shut down due an idle timeout
37
+ * - "STOPPING": Instance is being shut-down
38
+ *
39
+ * Additional states: "OFFLINE", "SAVING", "SAVED", "DELETING", "ERROR", "REVOKED", "EXPIRED".
40
+ */
41
+ get state() {
42
+ return this[data].state;
43
+ }
44
+
45
+ get adbName() {
46
+ return this[data].adb_serial;
47
+ }
48
+
49
+ get recipeName() {
50
+ return this[recipe].name;
51
+ }
52
+
53
+ get recipeUUID() {
54
+ return this[recipe].uuid;
55
+ }
56
+
57
+ isAdbConnected() {
58
+ return this.adbName !== '0.0.0.0';
59
+ }
60
+
61
+ isOnline() {
62
+ return this.state === STATE_ONLINE;
63
+ }
64
+
65
+ isInitializing() {
66
+ return initStates.has(this.state);
67
+ }
68
+
69
+ toString() {
70
+ const description = [
71
+ this.uuid,
72
+ this.adbName
73
+ ].filter(Boolean).join('/');
74
+
75
+ return `${this.name} (${description})`;
76
+ }
77
+
78
+ toJSON() {
79
+ return this[data];
80
+ }
81
+ }
82
+
83
+ module.exports = GenyInstance;
@@ -0,0 +1,25 @@
1
+ const data = Symbol('data');
2
+
3
+ class GenyRecipe {
4
+ constructor(rawRecipe) {
5
+ this[data] = rawRecipe;
6
+ }
7
+
8
+ get uuid() {
9
+ return this[data].uuid;
10
+ }
11
+
12
+ get name() {
13
+ return this[data].name || 'Anonymous GMSaaS Recipe';
14
+ }
15
+
16
+ toString() {
17
+ return this[data].name ? `${this.name} (${this.uuid})` : `Recipe of ${this.uuid}`;
18
+ }
19
+
20
+ toJSON() {
21
+ return this[data];
22
+ }
23
+ }
24
+
25
+ module.exports = GenyRecipe;
@@ -1,22 +1,37 @@
1
- // @ts-nocheck
1
+ /**
2
+ * @typedef {import('../AllocationDriverBase').AllocationDriverBase} AllocationDriverBase
3
+ * @typedef {import('../AllocationDriverBase').DeallocOptions} DeallocOptions
4
+ * @typedef {import('../../../common/drivers/ios/cookies').IosSimulatorCookie} IosSimulatorCookie
5
+ */
6
+
2
7
  const _ = require('lodash');
3
8
 
4
- const DetoxRuntimeError = require('../../../../errors/DetoxRuntimeError');
5
- const IosSimulatorCookie = require('../../../cookies/IosSimulatorCookie');
6
- const AllocationDriverBase = require('../AllocationDriverBase');
9
+ const { DetoxRuntimeError } = require('../../../../errors');
10
+ const log = require('../../../../utils/logger').child({ cat: 'device,device-allocation' });
11
+
12
+ const SimulatorQuery = require('./SimulatorQuery');
7
13
 
8
- class SimulatorAllocDriver extends AllocationDriverBase {
14
+ /**
15
+ * @implements {AllocationDriverBase}
16
+ */
17
+ class SimulatorAllocDriver {
9
18
  /**
10
- * @param deviceRegistry { DeviceRegistry }
11
- * @param applesimutils { AppleSimUtils }
12
- * @param simulatorLauncher { SimulatorLauncher }
19
+ * @param {object} options
20
+ * @param {import('../../DeviceRegistry')} options.deviceRegistry
21
+ * @param {DetoxInternals.RuntimeConfig} options.detoxConfig
22
+ * @param {import('../../../common/drivers/ios/tools/AppleSimUtils')} options.applesimutils
23
+ * @param {import('./SimulatorLauncher')} options.simulatorLauncher
13
24
  */
14
- constructor({ deviceRegistry, applesimutils, simulatorLauncher }) {
15
- super();
25
+ constructor({ detoxConfig, deviceRegistry, applesimutils, simulatorLauncher }) {
16
26
  this._deviceRegistry = deviceRegistry;
17
27
  this._applesimutils = applesimutils;
18
28
  this._simulatorLauncher = simulatorLauncher;
19
29
  this._launchInfo = {};
30
+ this._shouldShutdown = detoxConfig.behavior.cleanup.shutdownDevice;
31
+ }
32
+
33
+ async init() {
34
+ await this._deviceRegistry.unregisterZombieDevices();
20
35
  }
21
36
 
22
37
  /**
@@ -24,30 +39,37 @@ class SimulatorAllocDriver extends AllocationDriverBase {
24
39
  * @return {Promise<IosSimulatorCookie>}
25
40
  */
26
41
  async allocate(deviceConfig) {
27
- const deviceQuery = this._adaptQuery(deviceConfig.device);
42
+ const deviceQuery = new SimulatorQuery(deviceConfig.device);
28
43
 
29
44
  // TODO Delegate this onto a well tested allocator class
30
- const udid = await this._deviceRegistry.allocateDevice(async () => {
45
+ const udid = await this._deviceRegistry.registerDevice(async () => {
31
46
  return await this._findOrCreateDevice(deviceQuery);
32
47
  });
33
48
 
34
- const deviceComment = this._commentDevice(deviceQuery);
35
49
  if (!udid) {
36
- throw new DetoxRuntimeError(`Failed to find device matching ${deviceComment}`);
50
+ throw new DetoxRuntimeError(`Failed to find device matching ${deviceQuery.getDeviceComment()}`);
37
51
  }
38
52
 
39
53
  this._launchInfo[udid] = { deviceConfig };
40
- return new IosSimulatorCookie(udid);
54
+ return { id: udid, udid };
41
55
  }
42
56
 
43
57
  /**
44
58
  * @param {IosSimulatorCookie} deviceCookie
45
- * @returns {Promise<void>}
59
+ * @returns {Promise<IosSimulatorCookie>}
46
60
  */
47
61
  async postAllocate(deviceCookie) {
48
62
  const { udid } = deviceCookie;
49
63
  const { deviceConfig } = this._launchInfo[udid];
50
64
  await this._simulatorLauncher.launch(udid, deviceConfig.type, deviceConfig.bootArgs, deviceConfig.headless);
65
+
66
+ return {
67
+ id: udid,
68
+ udid,
69
+ type: deviceConfig.type,
70
+ bootArgs: deviceConfig.bootArgs,
71
+ headless: deviceConfig.headless,
72
+ };
51
73
  }
52
74
 
53
75
  /**
@@ -58,21 +80,40 @@ class SimulatorAllocDriver extends AllocationDriverBase {
58
80
  async free(cookie, options = {}) {
59
81
  const { udid } = cookie;
60
82
 
61
- await this._deviceRegistry.disposeDevice(udid);
62
-
63
83
  if (options.shutdown) {
84
+ await this._doShutdown(udid);
85
+ await this._deviceRegistry.unregisterDevice(udid);
86
+ } else {
87
+ await this._deviceRegistry.releaseDevice(udid);
88
+ }
89
+ }
90
+
91
+ async cleanup() {
92
+ if (this._shouldShutdown) {
93
+ const sessionDevices = await this._deviceRegistry.readSessionDevices();
94
+ const shutdownPromises = sessionDevices.getIds().map((udid) => this._doShutdown(udid));
95
+ await Promise.all(shutdownPromises);
96
+ }
97
+
98
+ await this._deviceRegistry.unregisterSessionDevices();
99
+ }
100
+
101
+ /**
102
+ * @param {string} udid
103
+ * @returns {Promise<void>}
104
+ * @private
105
+ */
106
+ async _doShutdown(udid) {
107
+ try {
64
108
  await this._simulatorLauncher.shutdown(udid);
109
+ } catch (err) {
110
+ log.warn({ err }, `Failed to shutdown simulator ${udid}`);
65
111
  }
66
112
  }
67
113
 
68
114
  /***
69
115
  * @private
70
- * @param deviceQuery {{
71
- * byId?: string;
72
- * byName?: string;
73
- * byType?: string;
74
- * byOS?: string;
75
- * }}
116
+ * @param {SimulatorQuery} deviceQuery
76
117
  * @returns {Promise<String>}
77
118
  */
78
119
  async _findOrCreateDevice(deviceQuery) {
@@ -83,6 +124,7 @@ class SimulatorAllocDriver extends AllocationDriverBase {
83
124
  if (_.isEmpty(free)) {
84
125
  const prototypeDevice = taken[0];
85
126
  udid = this._applesimutils.create(prototypeDevice);
127
+ await this._runScreenshotWorkaround(udid);
86
128
  } else {
87
129
  udid = free[0].udid;
88
130
  }
@@ -90,11 +132,30 @@ class SimulatorAllocDriver extends AllocationDriverBase {
90
132
  return udid;
91
133
  }
92
134
 
135
+ async _runScreenshotWorkaround(udid) {
136
+ await this._applesimutils.takeScreenshot(udid, '/dev/null').catch(() => {
137
+ log.debug({}, `
138
+ NOTE: For an unknown yet reason, taking the first screenshot is apt
139
+ to fail when booting iOS Simulator in a hidden window mode (or on CI).
140
+ Detox applies a workaround by taking a dummy screenshot to ensure
141
+ that the future ones are going to work fine. This screenshot is not
142
+ saved anywhere, and the error above is suppressed for all log levels
143
+ except for "debug" and "trace."
144
+ `.trim());
145
+ });
146
+ }
147
+
148
+ /**
149
+ * @private
150
+ * @param {SimulatorQuery} deviceQuery
151
+ */
93
152
  async _groupDevicesByStatus(deviceQuery) {
94
153
  const searchResults = await this._queryDevices(deviceQuery);
95
- const { rawDevices: takenDevices } = this._deviceRegistry.getRegisteredDevices();
96
- const takenUDIDs = new Set(_.map(takenDevices, 'id'));
97
- const { taken, free } = _.groupBy(searchResults, ({ udid }) => takenUDIDs.has(udid) ? 'taken' : 'free');
154
+ const takenDevices = this._deviceRegistry.getTakenDevicesSync();
155
+
156
+ const { taken, free } = _.groupBy(searchResults, ({ udid }) => {
157
+ return takenDevices.includes(udid) ? 'taken' : 'free';
158
+ });
98
159
 
99
160
  const targetOS = _.get(taken, '0.os.identifier');
100
161
  const isMatching = targetOS && { os: { identifier: targetOS } };
@@ -105,43 +166,25 @@ class SimulatorAllocDriver extends AllocationDriverBase {
105
166
  };
106
167
  }
107
168
 
169
+ /**
170
+ * @private
171
+ * @param {SimulatorQuery} deviceQuery
172
+ */
108
173
  async _queryDevices(deviceQuery) {
109
174
  const result = await this._applesimutils.list(
110
175
  deviceQuery,
111
- `Searching for device ${this._commentQuery(deviceQuery)} ...`
176
+ `Searching for device ${deviceQuery} ...`
112
177
  );
113
178
 
114
179
  if (_.isEmpty(result)) {
115
180
  throw new DetoxRuntimeError({
116
- message: `Failed to find a device ${this._commentQuery(deviceQuery)}`,
181
+ message: `Failed to find a device ${deviceQuery}`,
117
182
  hint: `Run 'applesimutils --list' to list your supported devices. ` +
118
183
  `It is advised only to specify a device type, e.g., "iPhone Xʀ" and avoid explicit search by OS version.`
119
184
  });
120
185
  }
121
186
  return result;
122
187
  }
123
-
124
- _adaptQuery({ id, name, os, type }) {
125
- return _.omitBy({
126
- byId: id,
127
- byName: name,
128
- byOS: os,
129
- byType: type,
130
- }, _.isUndefined);
131
- }
132
-
133
- _commentQuery({ byId, byName, byOS, byType }) {
134
- return _.compact([
135
- byId && `by UDID = ${JSON.stringify(byId)}`,
136
- byName && `by name = ${JSON.stringify(byName)}`,
137
- byType && `by type = ${JSON.stringify(byType)}`,
138
- byOS && `by OS = ${JSON.stringify(byOS)}`,
139
- ]).join(' and ');
140
- }
141
-
142
- _commentDevice({ byId, byName, byOS, byType }) {
143
- return byId || _.compact([byName, byType, byOS]).join(', ');
144
- }
145
188
  }
146
189
 
147
190
  module.exports = SimulatorAllocDriver;
@@ -1,20 +1,24 @@
1
- const DeviceLauncher = require('../../../common/drivers/DeviceLauncher');
2
-
3
- class SimulatorLauncher extends DeviceLauncher {
1
+ class SimulatorLauncher {
4
2
  constructor({ applesimutils, eventEmitter }) {
5
- super(eventEmitter);
6
3
  this._applesimutils = applesimutils;
4
+ this._eventEmitter = eventEmitter;
7
5
  }
8
6
 
9
7
  async launch(udid, type, bootArgs, headless) {
10
8
  const coldBoot = await this._applesimutils.boot(udid, bootArgs, headless);
11
- await this._notifyBootEvent(udid, type, coldBoot, headless);
9
+ return coldBoot;
12
10
  }
13
11
 
14
12
  async shutdown(udid) {
15
- await this._notifyPreShutdown(udid);
13
+ if (this._eventEmitter) {
14
+ await this._eventEmitter.emit('beforeShutdownDevice', { deviceId: udid });
15
+ }
16
+
16
17
  await this._applesimutils.shutdown(udid);
17
- await this._notifyShutdownCompleted(udid);
18
+
19
+ if (this._eventEmitter) {
20
+ await this._eventEmitter.emit('shutdownDevice', { deviceId: udid });
21
+ }
18
22
  }
19
23
  }
20
24
 
@@ -0,0 +1,24 @@
1
+ class SimulatorQuery {
2
+ /** @param {Partial<Detox.IosSimulatorQuery>} query */
3
+ constructor({ id, name, os, type }) {
4
+ if (id != null) this.byId = id;
5
+ if (name != null) this.byName = name;
6
+ if (os != null) this.byOS = os;
7
+ if (type != null) this.byType = type;
8
+ }
9
+
10
+ getDeviceComment() {
11
+ return this.byId || [this.byName, this.byType, this.byOS].filter(Boolean).join(', ');
12
+ }
13
+
14
+ toString() {
15
+ return [
16
+ this.byId && `by UDID = ${JSON.stringify(this.byId)}`,
17
+ this.byName && `by name = ${JSON.stringify(this.byName)}`,
18
+ this.byType && `by type = ${JSON.stringify(this.byType)}`,
19
+ this.byOS && `by OS = ${JSON.stringify(this.byOS)}`,
20
+ ].filter(Boolean).join(' and ');
21
+ }
22
+ }
23
+
24
+ module.exports = SimulatorQuery;