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
package/src/ios/web.js ADDED
@@ -0,0 +1,302 @@
1
+ const assert = require('assert');
2
+
3
+ const _ = require('lodash');
4
+
5
+ const { DetoxRuntimeError } = require('../errors');
6
+ const { webViewActionDescription, expectDescription } = require('../utils/invocationTraceDescriptions');
7
+ const log = require('../utils/logger').child({ cat: 'ws-client, ws' });
8
+ const traceInvocationCall = require('../utils/traceInvocationCall').bind(null, log);
9
+
10
+
11
+ class WebExpect {
12
+ constructor(invocationManager, element) {
13
+ this._invocationManager = invocationManager;
14
+ this.element = element;
15
+ this.modifiers = [];
16
+ }
17
+
18
+ toHaveText(text) {
19
+ const traceDescription = expectDescription.toHaveText(text);
20
+ return this.expect('toHaveText', traceDescription, text);
21
+ }
22
+
23
+ toExist() {
24
+ const traceDescription = expectDescription.toExist();
25
+ return this.expect('toExist', traceDescription);
26
+ }
27
+
28
+ get not() {
29
+ this.modifiers.push('not');
30
+ return this;
31
+ }
32
+ getText;
33
+ createInvocation(webExpectation, ...params) {
34
+ const definedParams = _.without(params, undefined);
35
+ return {
36
+ type: 'webExpectation',
37
+ ...(this.element.webViewElement !== undefined) && {
38
+ predicate: this.element.webViewElement.matcher.predicate,
39
+ ...(this.element.webViewElement.index !== undefined && { atIndex: this.element.webViewElement.index }),
40
+ },
41
+ webPredicate: this.element.matcher.predicate,
42
+ ...(this.element.index !== undefined && { webAtIndex: this.element.index }),
43
+ ...(this.modifiers.length !== 0 && { webModifiers: this.modifiers }),
44
+ webExpectation,
45
+ ...(definedParams.length !== 0 && { params: definedParams })
46
+ };
47
+ }
48
+
49
+ expect(expectation, traceDescription, ...params) {
50
+ assert(traceDescription, `must provide trace description for expectation: \n ${JSON.stringify(expectation)}`);
51
+
52
+ const invocation = this.createInvocation(expectation, ...params);
53
+ traceDescription = expectDescription.full(traceDescription, this.modifiers.includes('not'));
54
+ return _executeInvocation(this._invocationManager, invocation, traceDescription);
55
+ }
56
+ }
57
+
58
+ class WebElement {
59
+ constructor(invocationManager, emitter, webViewElement, matcher, index) {
60
+ this._invocationManager = invocationManager;
61
+ this._emitter = emitter;
62
+ this.webViewElement = webViewElement;
63
+ this.matcher = matcher;
64
+ this.index = index;
65
+ }
66
+
67
+ atIndex(index) {
68
+ if (typeof index !== 'number') throw new DetoxRuntimeError(`atIndex argument must be a number, got ${typeof index}`);
69
+ this.index = index;
70
+ return this;
71
+ }
72
+
73
+ tap() {
74
+ const traceDescription = webViewActionDescription.tap();
75
+ return this.withAction('tap', traceDescription);
76
+ }
77
+
78
+ typeText(text, isContentEditable = false) {
79
+ const traceDescription = webViewActionDescription.typeText(text, isContentEditable);
80
+ return this.withAction('typeText', traceDescription, text, isContentEditable);
81
+ }
82
+
83
+ replaceText(text) {
84
+ const traceDescription = webViewActionDescription.replaceText(text);
85
+ return this.withAction('replaceText', traceDescription, text);
86
+ }
87
+
88
+ clearText() {
89
+ const traceDescription = webViewActionDescription.clearText();
90
+ return this.withAction('clearText', traceDescription);
91
+ }
92
+
93
+ selectAllText() {
94
+ const traceDescription = webViewActionDescription.selectAllText();
95
+ return this.withAction('selectAllText', traceDescription);
96
+ }
97
+
98
+ async getText() {
99
+ const traceDescription = webViewActionDescription.getText();
100
+ let result = await this.withAction('getText', traceDescription);
101
+
102
+ if (result['text']) {
103
+ return result['text'];
104
+ } else {
105
+ throw new DetoxRuntimeError(`Failed to extract text from result: ${JSON.stringify(result)}`);
106
+ }
107
+ }
108
+
109
+ scrollToView() {
110
+ const traceDescription = webViewActionDescription.scrollToView();
111
+ return this.withAction('scrollToView', traceDescription);
112
+ }
113
+
114
+ focus() {
115
+ const traceDescription = webViewActionDescription.focus();
116
+ return this.withAction('focus', traceDescription);
117
+ }
118
+
119
+ moveCursorToEnd() {
120
+ const traceDescription = webViewActionDescription.moveCursorToEnd();
121
+ return this.withAction('moveCursorToEnd', traceDescription);
122
+ }
123
+
124
+ async runScript(script, args) {
125
+ if (args !== undefined && args.length !== 0) {
126
+ return await this.runScriptWithArgs(script, args);
127
+ }
128
+
129
+ const traceDescription = webViewActionDescription.runScript(script);
130
+ const result = await this.withAction('runScript', traceDescription, script);
131
+
132
+ if (result['result'] !== undefined) {
133
+ return result['result'];
134
+ } else {
135
+ throw new DetoxRuntimeError(`Failed to extract result from result: ${JSON.stringify(result)}`);
136
+ }
137
+ }
138
+
139
+ async runScriptWithArgs(script, args) {
140
+ const traceDescription = webViewActionDescription.runScriptWithArgs(script, args);
141
+ const result = await this.withAction('runScriptWithArgs', traceDescription, script, args);
142
+
143
+ if (result['result'] !== undefined) {
144
+ return result['result'];
145
+ } else {
146
+ throw new DetoxRuntimeError(`Failed to extract result from result: ${JSON.stringify(result)}`);
147
+ }
148
+ }
149
+
150
+ async getCurrentUrl() {
151
+ const traceDescription = webViewActionDescription.getCurrentUrl();
152
+ let result = await this.withAction('getCurrentUrl', traceDescription);
153
+
154
+ if (result['url']) {
155
+ return result['url'];
156
+ } else {
157
+ throw new DetoxRuntimeError(`Failed to extract url from result: ${JSON.stringify(result)}`);
158
+ }
159
+ }
160
+
161
+ async getTitle() {
162
+ const traceDescription = webViewActionDescription.getTitle();
163
+ let result = await this.withAction('getTitle', traceDescription);
164
+
165
+ if (result['title']) {
166
+ return result['title'];
167
+ } else {
168
+ throw new DetoxRuntimeError(`Failed to extract title from result: ${JSON.stringify(result)}`);
169
+ }
170
+ }
171
+
172
+ withAction(action, traceDescription, ...params) {
173
+ assert(traceDescription, `must provide trace description for action: \n ${JSON.stringify(action)}`);
174
+
175
+ const invocation = {
176
+ type: 'webAction',
177
+ ...(this.webViewElement !== undefined) && {
178
+ predicate: this.webViewElement.matcher.predicate,
179
+ ...(this.webViewElement.index !== undefined && { atIndex: this.webViewElement.index }),
180
+ },
181
+ webPredicate: this.matcher.predicate,
182
+ ...(this.index !== undefined && { webAtIndex: this.index }),
183
+ webAction: action,
184
+ ...(params.length !== 0 && { params }),
185
+ };
186
+ traceDescription = webViewActionDescription.full(traceDescription);
187
+ return _executeInvocation(this._invocationManager, invocation, traceDescription);
188
+ }
189
+ }
190
+
191
+ class WebElementMatcher {
192
+ id(id) {
193
+ if (typeof id !== 'string') throw new DetoxRuntimeError('id should be a string, but got ' + (id + (' (' + (typeof id + ')'))));
194
+ this.predicate = { type: 'id', value: id.toString() };
195
+ return this;
196
+ }
197
+
198
+ className(className) {
199
+ if (typeof className !== 'string') throw new DetoxRuntimeError('className should be a string, but got ' + (className + (' (' + (typeof className + ')'))));
200
+ this.predicate = { type: 'class', value: className.toString() };
201
+ return this;
202
+ }
203
+
204
+ cssSelector(cssSelector) {
205
+ if (typeof cssSelector !== 'string') throw new DetoxRuntimeError('cssSelector should be a string, but got ' + (cssSelector + (' (' + (typeof cssSelector + ')'))));
206
+ this.predicate = { type: 'css', value: cssSelector.toString() };
207
+ return this;
208
+ }
209
+
210
+ name(name) {
211
+ if (typeof name !== 'string') throw new DetoxRuntimeError('name should be a string, but got ' + (name + (' (' + (typeof name + ')'))));
212
+ this.predicate = { type: 'name', value: name.toString() };
213
+ return this;
214
+ }
215
+
216
+ xpath(xpath) {
217
+ if (typeof xpath !== 'string') throw new DetoxRuntimeError('xpath should be a string, but got ' + (xpath + (' (' + (typeof xpath + ')'))));
218
+ this.predicate = { type: 'xpath', value: xpath.toString() };
219
+ return this;
220
+ }
221
+
222
+ href(href) {
223
+ if (typeof href !== 'string') throw new DetoxRuntimeError('href should be a string, but got ' + (href + (' (' + (typeof href + ')'))));
224
+ this.predicate = { type: 'href', value: href.toString() };
225
+ return this;
226
+ }
227
+
228
+ hrefContains(href) {
229
+ if (typeof href !== 'string') throw new DetoxRuntimeError('href should be a string, but got ' + (href + (' (' + (typeof href + ')'))));
230
+ this.predicate = { type: 'hrefContains', value: href.toString() };
231
+ return this;
232
+ }
233
+
234
+ tag(tag) {
235
+ if (typeof tag !== 'string') throw new DetoxRuntimeError('tag should be a string, but got ' + (tag + (' (' + (typeof tag + ')'))));
236
+ this.predicate = { type: 'tag', value: tag.toString() };
237
+ return this;
238
+ }
239
+
240
+ label(label) {
241
+ if (typeof label !== 'string') throw new DetoxRuntimeError('label should be a string, but got ' + (label + (' (' + (typeof label + ')'))));
242
+ this.predicate = { type: 'label', value: label.toString() };
243
+ return this;
244
+ }
245
+
246
+ value(value) {
247
+ if (typeof value !== 'string') throw new DetoxRuntimeError('value should be a string, but got ' + (value + (' (' + (typeof value + ')'))));
248
+ this.predicate = { type: 'value', value: value.toString() };
249
+ return this;
250
+ }
251
+
252
+ accessibilityType(type) {
253
+ if (typeof type !== 'string') throw new DetoxRuntimeError('accessibilityType should be a string, but got ' + (type + (' (' + (typeof type + ')'))));
254
+ this.predicate = { type: 'accessibilityType', value: type.toString() };
255
+ return this;
256
+ }
257
+ }
258
+
259
+ function webMatcher() {
260
+ return new WebElementMatcher();
261
+ }
262
+
263
+ function webElement(invocationManager, emitter, webViewElement, matcher) {
264
+ if (!(matcher instanceof WebElementMatcher)) {
265
+ throwWebViewMatcherError(matcher);
266
+ }
267
+
268
+ return new WebElement(invocationManager, emitter, webViewElement, matcher);
269
+ }
270
+
271
+ function throwWebViewMatcherError(param) {
272
+ const paramDescription = JSON.stringify(param);
273
+ throw new DetoxRuntimeError(`${paramDescription} is not a Detox web-view matcher. More about web-view matchers here: https://wix.github.io/Detox/docs/api/webviews`);
274
+ }
275
+
276
+ function webExpect(invocationManager, element) {
277
+ if (!(element instanceof WebElement)) {
278
+ throwWebElementError(element);
279
+ }
280
+
281
+ return new WebExpect(invocationManager, element);
282
+ }
283
+
284
+ function throwWebElementError(param) {
285
+ const paramDescription = JSON.stringify(param);
286
+ throw new DetoxRuntimeError(`${paramDescription} is not a web element. More about web elements here: https://wix.github.io/Detox/docs/api/webviews`);
287
+ }
288
+
289
+ function _executeInvocation(invocationManager, invocation, traceDescription) {
290
+ return traceInvocationCall(traceDescription, invocation, invocationManager.execute(invocation));
291
+ }
292
+
293
+ function isWebElement(element) {
294
+ return element instanceof WebElement;
295
+ }
296
+
297
+ module.exports = {
298
+ webMatcher,
299
+ webElement,
300
+ webExpect,
301
+ isWebElement
302
+ };
@@ -1,7 +1,7 @@
1
1
  const { IPC } = require('node-ipc');
2
2
 
3
3
  const { DetoxInternalError } = require('../errors');
4
- const { serializeObjectWithError } = require('../utils/errorUtils');
4
+ const { serializeObjectWithError, deserializeObjectWithError } = require('../utils/errorUtils');
5
5
 
6
6
  class IPCClient {
7
7
  constructor({ id, logger, sessionState }) {
@@ -60,6 +60,22 @@ class IPCClient {
60
60
  this._sessionState.patch(sessionState);
61
61
  }
62
62
 
63
+ async allocateDevice() {
64
+ const { deviceCookie, error } = deserializeObjectWithError(await this._emit('allocateDevice', {}));
65
+ if (error) {
66
+ throw error;
67
+ }
68
+
69
+ return deviceCookie;
70
+ }
71
+
72
+ async deallocateDevice(deviceCookie) {
73
+ const { error } = deserializeObjectWithError(await this._emit('deallocateDevice', { deviceCookie }));
74
+ if (error) {
75
+ throw error;
76
+ }
77
+ }
78
+
63
79
  /**
64
80
  * @param {DetoxInternals.DetoxTestFileReport[]} testResults
65
81
  */
@@ -70,6 +86,11 @@ class IPCClient {
70
86
  this._sessionState.patch(sessionState);
71
87
  }
72
88
 
89
+ async conductEarlyTeardown({ permanent }) {
90
+ const sessionState = await this._emit('conductEarlyTeardown', { permanent });
91
+ this._sessionState.patch(sessionState);
92
+ }
93
+
73
94
  async _connectToServer() {
74
95
  const serverId = this.serverId;
75
96
 
@@ -8,11 +8,15 @@ class IPCServer {
8
8
  * @param {object} options
9
9
  * @param {import('./SessionState')} options.sessionState
10
10
  * @param {Detox.Logger} options.logger
11
+ * @param {object} options.callbacks
12
+ * @param {() => Promise<any>} options.callbacks.onAllocateDevice
13
+ * @param {(cookie: any) => Promise<void>} options.callbacks.onDeallocateDevice
11
14
  */
12
- constructor({ sessionState, logger }) {
15
+ constructor({ sessionState, logger, callbacks }) {
13
16
  this._sessionState = sessionState;
14
17
  this._logger = logger.child({ cat: 'ipc,ipc-server' });
15
18
  this._ipc = null;
19
+ this._callbacks = callbacks;
16
20
  this._workers = new Set();
17
21
  this._contexts = new Set();
18
22
  }
@@ -38,9 +42,12 @@ class IPCServer {
38
42
  await new Promise((resolve) => {
39
43
  // TODO: handle reject
40
44
  this._ipc.serve(() => resolve());
45
+ this._ipc.server.on('conductEarlyTeardown', this.onConductEarlyTeardown.bind(this));
41
46
  this._ipc.server.on('registerContext', this.onRegisterContext.bind(this));
42
47
  this._ipc.server.on('registerWorker', this.onRegisterWorker.bind(this));
43
48
  this._ipc.server.on('reportTestResults', this.onReportTestResults.bind(this));
49
+ this._ipc.server.on('allocateDevice', this.onAllocateDevice.bind(this));
50
+ this._ipc.server.on('deallocateDevice', this.onDeallocateDevice.bind(this));
44
51
  this._ipc.server.start();
45
52
  });
46
53
  }
@@ -66,6 +73,7 @@ class IPCServer {
66
73
  this._ipc.server.emit(socket, 'registerContextDone', {
67
74
  testResults: this._sessionState.testResults,
68
75
  testSessionIndex: this._sessionState.testSessionIndex,
76
+ unsafe_earlyTeardown: this._sessionState.unsafe_earlyTeardown,
69
77
  });
70
78
  }
71
79
 
@@ -83,6 +91,39 @@ class IPCServer {
83
91
  }
84
92
  }
85
93
 
94
+ onConductEarlyTeardown({ permanent }, socket = null) {
95
+ const newState = { unsafe_earlyTeardown: true };
96
+ if (permanent) {
97
+ Object.assign(this._sessionState, newState);
98
+ }
99
+
100
+ if (socket) {
101
+ this._ipc.server.emit(socket, 'conductEarlyTeardownDone', newState);
102
+ }
103
+
104
+ this._ipc.server.broadcast('sessionStateUpdate', newState);
105
+ }
106
+
107
+ async onAllocateDevice(_payload, socket) {
108
+ let deviceCookie;
109
+
110
+ try {
111
+ deviceCookie = await this._callbacks.onAllocateDevice();
112
+ this._ipc.server.emit(socket, 'allocateDeviceDone', { deviceCookie });
113
+ } catch (error) {
114
+ this._ipc.server.emit(socket, 'allocateDeviceDone', serializeObjectWithError({ error }));
115
+ }
116
+ }
117
+
118
+ async onDeallocateDevice({ deviceCookie }, socket) {
119
+ try {
120
+ await this._callbacks.onDeallocateDevice(deviceCookie);
121
+ this._ipc.server.emit(socket, 'deallocateDeviceDone', {});
122
+ } catch (error) {
123
+ this._ipc.server.emit(socket, 'deallocateDeviceDone', serializeObjectWithError({ error }));
124
+ }
125
+ }
126
+
86
127
  onReportTestResults({ testResults }, socket = null) {
87
128
  const merged = uniqBy([
88
129
  ...testResults.map(r => serializeObjectWithError(r, 'testExecError')),
@@ -20,6 +20,7 @@ class SessionState {
20
20
  this.detoxIPCServer = detoxIPCServer;
21
21
  this.testResults = testResults;
22
22
  this.testSessionIndex = testSessionIndex;
23
+ this.unsafe_earlyTeardown = undefined;
23
24
  this.workersCount = workersCount;
24
25
  }
25
26
 
@@ -249,7 +249,7 @@ class DetoxLogger {
249
249
  _complete(level, maybeContext, maybeMessage, maybeAction) {
250
250
  const action = typeof maybeContext !== 'string' ? maybeAction : maybeMessage;
251
251
  const args = maybeAction === action ? [maybeContext, maybeMessage] : [maybeContext];
252
- const { context, msg } = this._parseArgs(null, args);
252
+ const { context, msg } = this._parseArgs({ ph: 'B' }, args);
253
253
  const end = (ctx) => this[level].end({
254
254
  id: context.id,
255
255
  cat: context.cat,
@@ -257,7 +257,7 @@ class DetoxLogger {
257
257
  });
258
258
 
259
259
  let result;
260
- this._beginInternal(level, { ...context, ph: 'B' }, msg);
260
+ this._beginInternal(level, context, msg);
261
261
  try {
262
262
  result = typeof action === 'function'
263
263
  ? action()
@@ -101,6 +101,8 @@ class DetoxContext {
101
101
  });
102
102
  /** @abstract */
103
103
  [symbols.reportTestResults](_testResults) {}
104
+ /** @abstract */
105
+ [symbols.conductEarlyTeardown](_permanent) {}
104
106
  /**
105
107
  * @abstract
106
108
  * @param {Partial<DetoxInternals.DetoxInitOptions>} _opts
@@ -149,6 +151,12 @@ class DetoxContext {
149
151
  await this[$worker].init();
150
152
  }
151
153
 
154
+ /** @abstract */
155
+ async [symbols.allocateDevice]() {}
156
+
157
+ /** @abstract */
158
+ async [symbols.deallocateDevice]() {}
159
+
152
160
  async [symbols.uninstallWorker]() {
153
161
  try {
154
162
  if (this[$worker]) {
@@ -24,6 +24,7 @@ class DetoxInternalsFacade {
24
24
  this.resolveConfig = context[symbols.resolveConfig];
25
25
  this.session = context[symbols.session];
26
26
  this.tracing = context[symbols.tracing];
27
+ this.unsafe_conductEarlyTeardown = context[symbols.conductEarlyTeardown];
27
28
  this.worker = funpermaproxy(() => context[symbols.worker]);
28
29
  }
29
30
  }
@@ -16,15 +16,14 @@ const symbols = require('./symbols');
16
16
  const { $logFinalizer, $restoreSessionState, $sessionState, $worker } = DetoxContext.protected;
17
17
 
18
18
  //#region Private symbols
19
- const _globalLifecycleHandler = Symbol('globalLifecycleHandler');
20
19
  const _ipcServer = Symbol('ipcServer');
21
- const _resetLockFile = Symbol('resetLockFile');
22
20
  const _wss = Symbol('wss');
23
21
  const _dirty = Symbol('dirty');
24
22
  const _emergencyTeardown = Symbol('emergencyTeardown');
25
23
  const _lifecycleLogger = Symbol('lifecycleLogger');
26
24
  const _sessionFile = Symbol('sessionFile');
27
25
  const _logFinalError = Symbol('logFinalError');
26
+ const _deviceAllocator = Symbol('deviceAllocator');
28
27
  //#endregion
29
28
 
30
29
  class DetoxPrimaryContext extends DetoxContext {
@@ -33,7 +32,8 @@ class DetoxPrimaryContext extends DetoxContext {
33
32
 
34
33
  this[_dirty] = false;
35
34
  this[_wss] = null;
36
- this[_globalLifecycleHandler] = null;
35
+ this[_deviceAllocator] = null;
36
+
37
37
  /** Path to file where the initial session object is serialized */
38
38
  this[_sessionFile] = '';
39
39
  /**
@@ -51,8 +51,13 @@ class DetoxPrimaryContext extends DetoxContext {
51
51
  }
52
52
  }
53
53
 
54
- async [symbols.resolveConfig](opts = { argv: { configuration: process.env.DETOX_DBG_SELECTED_CFG } })
55
- {
54
+ [symbols.conductEarlyTeardown] = async (permanent = false) => {
55
+ if (this[_ipcServer]) {
56
+ await this[_ipcServer].onConductEarlyTeardown({ permanent });
57
+ }
58
+ };
59
+
60
+ async [symbols.resolveConfig](opts = {}) {
56
61
  const session = this[$sessionState];
57
62
  if (!session.detoxConfig) {
58
63
  const configuration = require('../configuration');
@@ -80,7 +85,6 @@ class DetoxPrimaryContext extends DetoxContext {
80
85
  const detoxConfig = await this[symbols.resolveConfig](opts);
81
86
 
82
87
  const {
83
- behavior: behaviorConfig,
84
88
  device: deviceConfig,
85
89
  logger: loggerConfig,
86
90
  session: sessionConfig
@@ -97,20 +101,23 @@ class DetoxPrimaryContext extends DetoxContext {
97
101
  this[_ipcServer] = new IPCServer({
98
102
  sessionState: this[$sessionState],
99
103
  logger: this[symbols.logger],
104
+ callbacks: {
105
+ onAllocateDevice: this[symbols.allocateDevice].bind(this),
106
+ onDeallocateDevice: this[symbols.deallocateDevice].bind(this),
107
+ },
100
108
  });
101
109
 
102
110
  await this[_ipcServer].init();
103
111
 
104
112
  const environmentFactory = require('../environmentFactory');
105
- this[_globalLifecycleHandler] = await environmentFactory.createGlobalLifecycleHandler(deviceConfig);
106
113
 
107
- if (this[_globalLifecycleHandler]) {
108
- await this[_globalLifecycleHandler].globalInit();
109
- }
114
+ const { deviceAllocatorFactory } = environmentFactory.createFactories(deviceConfig);
115
+ this[_deviceAllocator] = deviceAllocatorFactory.createDeviceAllocator({
116
+ detoxConfig,
117
+ detoxSession: this[$sessionState],
118
+ });
110
119
 
111
- if (!behaviorConfig.init.keepLockFile) {
112
- await this[_resetLockFile]();
113
- }
120
+ await this[_deviceAllocator].init();
114
121
 
115
122
  // TODO: Detox-server creation ought to be delegated to a generator/factory.
116
123
  const DetoxServer = require('../server/DetoxServer');
@@ -154,6 +161,29 @@ class DetoxPrimaryContext extends DetoxContext {
154
161
  await super[symbols.installWorker]({ ...opts, workerId });
155
162
  }
156
163
 
164
+ /** @override */
165
+ async [symbols.allocateDevice]() {
166
+ const { device } = this[$sessionState].detoxConfig;
167
+ const deviceCookie = await this[_deviceAllocator].allocate(device);
168
+
169
+ try {
170
+ return await this[_deviceAllocator].postAllocate(deviceCookie);
171
+ } catch (e) {
172
+ try {
173
+ await this[_deviceAllocator].free(deviceCookie, { shutdown: true });
174
+ } catch (e2) {
175
+ this[symbols.logger].error({ cat: 'device', err: e2 }, `Failed to free ${deviceCookie.name || deviceCookie.id} after a failed allocation`);
176
+ }
177
+
178
+ throw e;
179
+ }
180
+ }
181
+
182
+ /** @override */
183
+ async [symbols.deallocateDevice](cookie) {
184
+ await this[_deviceAllocator].free(cookie);
185
+ }
186
+
157
187
  /** @override */
158
188
  async [symbols.cleanup]() {
159
189
  try {
@@ -161,9 +191,9 @@ class DetoxPrimaryContext extends DetoxContext {
161
191
  await this[symbols.uninstallWorker]();
162
192
  }
163
193
  } finally {
164
- if (this[_globalLifecycleHandler]) {
165
- await this[_globalLifecycleHandler].globalCleanup();
166
- this[_globalLifecycleHandler] = null;
194
+ if (this[_deviceAllocator]) {
195
+ await this[_deviceAllocator].cleanup();
196
+ this[_deviceAllocator] = null;
167
197
  }
168
198
 
169
199
  if (this[_wss]) {
@@ -197,9 +227,9 @@ class DetoxPrimaryContext extends DetoxContext {
197
227
  return;
198
228
  }
199
229
 
200
- if (this[_globalLifecycleHandler]) {
201
- this[_globalLifecycleHandler].emergencyCleanup();
202
- this[_globalLifecycleHandler] = null;
230
+ if (this[_deviceAllocator]) {
231
+ this[_deviceAllocator].emergencyCleanup();
232
+ this[_deviceAllocator] = null;
203
233
  }
204
234
 
205
235
  if (this[_wss]) {
@@ -242,31 +272,6 @@ class DetoxPrimaryContext extends DetoxContext {
242
272
  });
243
273
  }
244
274
  //#endregion
245
-
246
- //#region Private members
247
- async[_resetLockFile]() {
248
- const DeviceRegistry = require('../devices/DeviceRegistry');
249
-
250
- const deviceType = this[symbols.config].device.type;
251
-
252
- switch (deviceType) {
253
- case 'ios.none':
254
- case 'ios.simulator':
255
- await DeviceRegistry.forIOS().reset();
256
- break;
257
- case 'android.attached':
258
- case 'android.emulator':
259
- case 'android.genycloud':
260
- await DeviceRegistry.forAndroid().reset();
261
- break;
262
- }
263
-
264
- if (deviceType === 'android.genycloud') {
265
- const GenyDeviceRegistryFactory = require('../devices/allocation/drivers/android/genycloud/GenyDeviceRegistryFactory');
266
- await GenyDeviceRegistryFactory.forGlobalShutdown().reset();
267
- }
268
- }
269
- //#endregion
270
275
  }
271
276
 
272
277
  module.exports = DetoxPrimaryContext;
@@ -33,6 +33,14 @@ class DetoxSecondaryContext extends DetoxContext {
33
33
  }
34
34
  }
35
35
 
36
+ [symbols.conductEarlyTeardown] = async (permanent = false) => {
37
+ if (this[_ipcClient]) {
38
+ await this[_ipcClient].conductEarlyTeardown({ permanent });
39
+ } else {
40
+ throw new DetoxInternalError('Detected an attempt to report early teardown using a non-initialized context.');
41
+ }
42
+ };
43
+
36
44
  async [symbols.resolveConfig]() {
37
45
  return this[symbols.config];
38
46
  }
@@ -54,6 +62,25 @@ class DetoxSecondaryContext extends DetoxContext {
54
62
  }
55
63
  }
56
64
 
65
+ /** @override */
66
+ async [symbols.allocateDevice]() {
67
+ if (this[_ipcClient]) {
68
+ const deviceCookie = await this[_ipcClient].allocateDevice();
69
+ return deviceCookie;
70
+ } else {
71
+ throw new DetoxInternalError('Detected an attempt to allocate a device using a non-initialized context.');
72
+ }
73
+ }
74
+
75
+ /** @override */
76
+ async [symbols.deallocateDevice](deviceCookie) {
77
+ if (this[_ipcClient]) {
78
+ await this[_ipcClient].deallocateDevice(deviceCookie);
79
+ } else {
80
+ throw new DetoxInternalError('Detected an attempt to allocate a device using a non-initialized context.');
81
+ }
82
+ }
83
+
57
84
  /** @override */
58
85
  async [symbols.cleanup]() {
59
86
  try {