detox 20.0.10-prerelease.0 → 20.0.12-prerelease.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (81) hide show
  1. package/Detox-android/com/wix/detox/{20.0.10-prerelease.0/detox-20.0.10-prerelease.0-javadoc.jar → 20.0.12-prerelease.0/detox-20.0.12-prerelease.0-javadoc.jar} +0 -0
  2. package/Detox-android/com/wix/detox/20.0.12-prerelease.0/detox-20.0.12-prerelease.0-javadoc.jar.md5 +1 -0
  3. package/Detox-android/com/wix/detox/20.0.12-prerelease.0/detox-20.0.12-prerelease.0-javadoc.jar.sha1 +1 -0
  4. package/Detox-android/com/wix/detox/20.0.12-prerelease.0/detox-20.0.12-prerelease.0-javadoc.jar.sha256 +1 -0
  5. package/Detox-android/com/wix/detox/20.0.12-prerelease.0/detox-20.0.12-prerelease.0-javadoc.jar.sha512 +1 -0
  6. package/Detox-android/com/wix/detox/{20.0.10-prerelease.0/detox-20.0.10-prerelease.0-sources.jar → 20.0.12-prerelease.0/detox-20.0.12-prerelease.0-sources.jar} +0 -0
  7. package/Detox-android/com/wix/detox/20.0.12-prerelease.0/detox-20.0.12-prerelease.0-sources.jar.md5 +1 -0
  8. package/Detox-android/com/wix/detox/20.0.12-prerelease.0/detox-20.0.12-prerelease.0-sources.jar.sha1 +1 -0
  9. package/Detox-android/com/wix/detox/20.0.12-prerelease.0/detox-20.0.12-prerelease.0-sources.jar.sha256 +1 -0
  10. package/Detox-android/com/wix/detox/20.0.12-prerelease.0/detox-20.0.12-prerelease.0-sources.jar.sha512 +1 -0
  11. package/Detox-android/com/wix/detox/{20.0.10-prerelease.0/detox-20.0.10-prerelease.0.aar → 20.0.12-prerelease.0/detox-20.0.12-prerelease.0.aar} +0 -0
  12. package/Detox-android/com/wix/detox/20.0.12-prerelease.0/detox-20.0.12-prerelease.0.aar.md5 +1 -0
  13. package/Detox-android/com/wix/detox/20.0.12-prerelease.0/detox-20.0.12-prerelease.0.aar.sha1 +1 -0
  14. package/Detox-android/com/wix/detox/20.0.12-prerelease.0/detox-20.0.12-prerelease.0.aar.sha256 +1 -0
  15. package/Detox-android/com/wix/detox/20.0.12-prerelease.0/detox-20.0.12-prerelease.0.aar.sha512 +1 -0
  16. package/Detox-android/com/wix/detox/{20.0.10-prerelease.0/detox-20.0.10-prerelease.0.pom → 20.0.12-prerelease.0/detox-20.0.12-prerelease.0.pom} +1 -1
  17. package/Detox-android/com/wix/detox/20.0.12-prerelease.0/detox-20.0.12-prerelease.0.pom.md5 +1 -0
  18. package/Detox-android/com/wix/detox/20.0.12-prerelease.0/detox-20.0.12-prerelease.0.pom.sha1 +1 -0
  19. package/Detox-android/com/wix/detox/20.0.12-prerelease.0/detox-20.0.12-prerelease.0.pom.sha256 +1 -0
  20. package/Detox-android/com/wix/detox/20.0.12-prerelease.0/detox-20.0.12-prerelease.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/README.md +1 -1
  29. package/android/detox/proguard-rules.pro +3 -0
  30. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/IdlingResourceDescription.kt +19 -13
  31. package/android/detox/src/full/java/com/wix/detox/reactnative/idlingresources/NetworkIdlingResource.java +33 -30
  32. package/android/detox/src/testFull/java/com/wix/detox/reactnative/idlingresources/NetworkIdlingResourcesTest.kt +61 -0
  33. package/index.d.ts +25 -2
  34. package/internals.d.ts +36 -14
  35. package/local-cli/cli.js +7 -5
  36. package/local-cli/init.js +2 -1
  37. package/local-cli/reset-lock-file.js +16 -0
  38. package/local-cli/templates/jest.js +3 -1
  39. package/local-cli/test.test.js +58 -14
  40. package/local-cli/testCommand/TestRunnerCommand.js +10 -3
  41. package/package.json +2 -2
  42. package/runners/deprecation.js +2 -2
  43. package/runners/jest/reporters/DetoxReporter.js +22 -2
  44. package/runners/jest/testEnvironment/index.js +83 -82
  45. package/runners/jest/testEnvironment/listeners/DetoxCoreListener.js +9 -24
  46. package/runners/jest/testEnvironment/listeners/DetoxPlatformFilterListener.js +1 -1
  47. package/src/client/actions/formatters/sync-resources/NetworkFormatter.js +1 -1
  48. package/src/configuration/composeAppsConfig.js +4 -0
  49. package/src/configuration/composeRunnerConfig.js +4 -2
  50. package/src/devices/allocation/drivers/android/emulator/AVDValidator.js +4 -4
  51. package/src/devices/allocation/drivers/android/emulator/EmulatorAllocDriver.js +1 -1
  52. package/src/devices/common/drivers/ios/tools/AppleSimUtils.js +25 -1
  53. package/src/devices/lifecycle/GenyGlobalLifecycleHandler.js +2 -2
  54. package/src/devices/runtime/RuntimeDevice.js +10 -0
  55. package/src/errors/DetoxConfigErrorComposer.js +8 -0
  56. package/src/ipc/IPCClient.js +9 -5
  57. package/src/ipc/IPCServer.js +17 -13
  58. package/src/ipc/SessionState.js +2 -4
  59. package/src/realms/DetoxContext.js +2 -2
  60. package/src/realms/DetoxInternalsFacade.js +1 -1
  61. package/src/realms/DetoxPrimaryContext.js +6 -8
  62. package/src/realms/DetoxSecondaryContext.js +2 -2
  63. package/src/symbols.js +2 -2
  64. package/src/utils/Timer.js +55 -38
  65. package/src/utils/errorUtils.js +20 -0
  66. package/Detox-android/com/wix/detox/20.0.10-prerelease.0/detox-20.0.10-prerelease.0-javadoc.jar.md5 +0 -1
  67. package/Detox-android/com/wix/detox/20.0.10-prerelease.0/detox-20.0.10-prerelease.0-javadoc.jar.sha1 +0 -1
  68. package/Detox-android/com/wix/detox/20.0.10-prerelease.0/detox-20.0.10-prerelease.0-javadoc.jar.sha256 +0 -1
  69. package/Detox-android/com/wix/detox/20.0.10-prerelease.0/detox-20.0.10-prerelease.0-javadoc.jar.sha512 +0 -1
  70. package/Detox-android/com/wix/detox/20.0.10-prerelease.0/detox-20.0.10-prerelease.0-sources.jar.md5 +0 -1
  71. package/Detox-android/com/wix/detox/20.0.10-prerelease.0/detox-20.0.10-prerelease.0-sources.jar.sha1 +0 -1
  72. package/Detox-android/com/wix/detox/20.0.10-prerelease.0/detox-20.0.10-prerelease.0-sources.jar.sha256 +0 -1
  73. package/Detox-android/com/wix/detox/20.0.10-prerelease.0/detox-20.0.10-prerelease.0-sources.jar.sha512 +0 -1
  74. package/Detox-android/com/wix/detox/20.0.10-prerelease.0/detox-20.0.10-prerelease.0.aar.md5 +0 -1
  75. package/Detox-android/com/wix/detox/20.0.10-prerelease.0/detox-20.0.10-prerelease.0.aar.sha1 +0 -1
  76. package/Detox-android/com/wix/detox/20.0.10-prerelease.0/detox-20.0.10-prerelease.0.aar.sha256 +0 -1
  77. package/Detox-android/com/wix/detox/20.0.10-prerelease.0/detox-20.0.10-prerelease.0.aar.sha512 +0 -1
  78. package/Detox-android/com/wix/detox/20.0.10-prerelease.0/detox-20.0.10-prerelease.0.pom.md5 +0 -1
  79. package/Detox-android/com/wix/detox/20.0.10-prerelease.0/detox-20.0.10-prerelease.0.pom.sha1 +0 -1
  80. package/Detox-android/com/wix/detox/20.0.10-prerelease.0/detox-20.0.10-prerelease.0.pom.sha256 +0 -1
  81. package/Detox-android/com/wix/detox/20.0.10-prerelease.0/detox-20.0.10-prerelease.0.pom.sha512 +0 -1
@@ -8,6 +8,8 @@ jest.mock('../src/devices/DeviceRegistry');
8
8
  jest.mock('../src/devices/allocation/drivers/android/genycloud/GenyDeviceRegistryFactory');
9
9
  jest.mock('./utils/jestInternals');
10
10
 
11
+ const cp = require('child_process');
12
+ const cpSpawn = cp.spawn;
11
13
  const os = require('os');
12
14
  const path = require('path');
13
15
 
@@ -28,6 +30,10 @@ describe('CLI', () => {
28
30
  let GenyDeviceRegistryFactory;
29
31
  let jestInternals;
30
32
 
33
+ afterEach(() => {
34
+ cp.spawn = cpSpawn;
35
+ });
36
+
31
37
  beforeEach(() => {
32
38
  _cliCallDump = undefined;
33
39
  _env = process.env;
@@ -139,11 +145,28 @@ describe('CLI', () => {
139
145
  });
140
146
 
141
147
  test.each([['-R'], ['--retries']])('%s <value> should execute unsuccessful run N extra times', async (__retries) => {
148
+ function toTestResult(testFilePath) {
149
+ return {
150
+ testFilePath,
151
+ success: false,
152
+ isPermanentFailure: false,
153
+ };
154
+ }
155
+
142
156
  const context = require('../internals');
143
- context.session.testFilesToRetry = ['e2e/failing1.test.js', 'e2e/failing2.test.js'];
144
- context.session.testFilesToRetry.splice = jest.fn()
145
- .mockImplementationOnce(() => ['e2e/failing1.test.js', 'e2e/failing2.test.js'])
146
- .mockImplementationOnce(() => ['e2e/failing2.test.js']);
157
+
158
+ jest.spyOn(cp, 'spawn')
159
+ .mockImplementationOnce((...args) => {
160
+ context.session.testResults = ['e2e/failing1.test.js', 'e2e/failing2.test.js'].map(toTestResult);
161
+ return cpSpawn(...args);
162
+ })
163
+ .mockImplementationOnce((...args) => {
164
+ context.session.testResults = ['e2e/failing2.test.js'].map(toTestResult);
165
+ return cpSpawn(...args);
166
+ })
167
+ .mockImplementationOnce((...args) => {
168
+ return cpSpawn(...args);
169
+ });
147
170
 
148
171
  mockExitCode(1);
149
172
 
@@ -154,18 +177,35 @@ describe('CLI', () => {
154
177
  expect(cliCall(2).argv).toEqual([expect.stringMatching(/executable$/), '--config', 'e2e/config.json', 'e2e/failing2.test.js']);
155
178
  });
156
179
 
157
- test.each([['-R'], ['--retries']])('%s <value> should bail if has permanently failed tests', async (__retries) => {
158
- const context = require('../internals');
159
- context.session.failedTestFiles = ['e2e/failing1.test.js'];
160
- context.session.testFilesToRetry = ['e2e/failing2.test.js'];
180
+ describe('when there are permanently failed tests', () => {
181
+ beforeEach(() => {
182
+ const context = require('../internals');
183
+ context.session.testResults = ['e2e/failing1.test.js', 'e2e/failing2.test.js'].map((testFilePath, index) => ({
184
+ testFilePath,
185
+ success: false,
186
+ isPermanentFailure: index > 0,
187
+ }));
161
188
 
162
- mockExitCode(1);
189
+ mockExitCode(1);
190
+ });
163
191
 
164
- await run(__retries, 2).catch(_.noop);
192
+ test.each([['-R'], ['--retries']])('%s <value> should not bail by default', async (__retries) => {
193
+ await run(__retries, 2).catch(_.noop);
165
194
 
166
- expect(cliCall(0).env).not.toHaveProperty('DETOX_RERUN_INDEX');
167
- expect(cliCall(0).argv).toEqual([expect.stringMatching(/executable$/), '--config', 'e2e/config.json']);
168
- expect(cliCall(1)).toBe(null);
195
+ expect(cliCall(0).env).not.toHaveProperty('DETOX_RERUN_INDEX');
196
+ expect(cliCall(0).argv).toEqual([expect.stringMatching(/executable$/), '--config', 'e2e/config.json']);
197
+ expect(cliCall(1).argv).toEqual([expect.stringMatching(/executable$/), '--config', 'e2e/config.json', 'e2e/failing1.test.js']);
198
+ // note that it does not take the permanently failed test
199
+ });
200
+
201
+ test.each([['-R'], ['--retries']])('%s <value> should bail if configured', async (__retries) => {
202
+ detoxConfig.testRunner.bail = true;
203
+ await run(__retries, 2).catch(_.noop);
204
+
205
+ expect(cliCall(0).env).not.toHaveProperty('DETOX_RERUN_INDEX');
206
+ expect(cliCall(0).argv).toEqual([expect.stringMatching(/executable$/), '--config', 'e2e/config.json']);
207
+ expect(cliCall(1)).toBe(null);
208
+ });
169
209
  });
170
210
 
171
211
  test.each([['-R'], ['--retries']])('%s <value> should not restart test runner if there are no failing tests paths', async (__retries) => {
@@ -178,7 +218,11 @@ describe('CLI', () => {
178
218
 
179
219
  test.each([['-R'], ['--retries']])('%s <value> should retain -- <...explicitPassthroughArgs>', async (__retries) => {
180
220
  const context = require('../internals');
181
- context.session.testFilesToRetry = ['tests/failing.test.js'];
221
+ context.session.testResults = [{
222
+ testFilePath: 'tests/failing.test.js',
223
+ success: false,
224
+ isPermanentFailure: false,
225
+ }];
182
226
 
183
227
  mockExitCode(1);
184
228
 
@@ -99,13 +99,20 @@ class TestRunnerCommand {
99
99
  } catch (e) {
100
100
  launchError = e;
101
101
 
102
- const { failedTestFiles, testFilesToRetry } = detox.session;
103
- if (!_.isEmpty(failedTestFiles) || _.isEmpty(testFilesToRetry)) {
102
+ const failedTestFiles = detox.session.testResults.filter(r => !r.success);
103
+
104
+ const { bail } = detox.config.testRunner;
105
+ if (bail && failedTestFiles.some(r => r.isPermanentFailure)) {
106
+ throw e;
107
+ }
108
+
109
+ const testFilesToRetry = failedTestFiles.filter(r => !r.isPermanentFailure).map(r => r.testFilePath);
110
+ if (_.isEmpty(testFilesToRetry)) {
104
111
  throw e;
105
112
  }
106
113
 
107
114
  if (--runsLeft > 0) {
108
- this._argv._ = testFilesToRetry.splice(0, Infinity);
115
+ this._argv._ = testFilesToRetry;
109
116
  // @ts-ignore
110
117
  detox.session.testSessionIndex++; // it is always a primary context, so we can update it
111
118
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "detox",
3
3
  "description": "E2E tests and automation for mobile",
4
- "version": "20.0.10-prerelease.0",
4
+ "version": "20.0.12-prerelease.0",
5
5
  "bin": {
6
6
  "detox": "local-cli/cli.js"
7
7
  },
@@ -184,5 +184,5 @@
184
184
  }
185
185
  }
186
186
  },
187
- "gitHead": "3116a188b0e4d57613dc2f7d26ca80292b74f192"
187
+ "gitHead": "3cd7feb90f69058ec124fa085cdacc8d167161a7"
188
188
  }
@@ -4,7 +4,7 @@ const chalk = require('chalk');
4
4
  console.error(chalk.yellow(`
5
5
  ========================= THE NEW JOURNEY BEGINS =============================
6
6
 
7
- https://github.com/wix/Detox/blob/master/docs/Guide.Jest.md
7
+ https://wix.github.io/Detox/docs/next/guide/migration
8
8
 
9
9
  _.-;-._ Sorry, traveler from the lands of Detox 19!
10
10
  ;_.JL___;
@@ -36,7 +36,7 @@ console.error(chalk.yellow(`
36
36
  snd || '---' '.___>
37
37
  Credit: "Gimli" by Shanaka Dias
38
38
 
39
- https://github.com/wix/Detox/blob/master/docs/Guide.Jest.md
39
+ https://wix.github.io/Detox/docs/next/guide/migration
40
40
 
41
41
  ========================= THE NEW JOURNEY BEGINS =============================
42
42
 
@@ -1,5 +1,25 @@
1
- const { VerboseReporter: JestVerboseReporter } = require('@jest/reporters'); // eslint-disable-line node/no-extraneous-require
1
+ const resolveFrom = require('resolve-from');
2
+ /** @type {typeof import('@jest/reporters').VerboseReporter} */
3
+ const JestVerboseReporter = require(resolveFrom(process.cwd(), '@jest/reporters')).VerboseReporter;
2
4
 
3
- class DetoxReporter extends JestVerboseReporter {}
5
+ const { config, reportTestResults } = require('../../../internals');
6
+
7
+ class DetoxReporter extends JestVerboseReporter {
8
+ /**
9
+ * @param {import('@jest/test-result').TestResult} testResult
10
+ */
11
+ async onTestResult(test, testResult, results) {
12
+ await super.onTestResult(test, testResult, results);
13
+
14
+ await reportTestResults([{
15
+ success: !testResult.failureMessage,
16
+ testFilePath: testResult.testFilePath,
17
+ testExecError: testResult.testExecError,
18
+ isPermanentFailure: config.testRunner.jest.retryAfterCircusRetries
19
+ ? false
20
+ : testResult.testResults.some(r => r.invocations > 1)
21
+ }]);
22
+ }
23
+ }
4
24
 
5
25
  module.exports = DetoxReporter;
@@ -1,6 +1,6 @@
1
1
  const resolveFrom = require('resolve-from');
2
2
  const maybeNodeEnvironment = require(resolveFrom(process.cwd(), 'jest-environment-node'));
3
- // @ts-ignore
3
+ /** @type {typeof import('@jest/environment').JestEnvironment} */
4
4
  const NodeEnvironment = maybeNodeEnvironment.default || maybeNodeEnvironment;
5
5
 
6
6
  const detox = require('../../../internals');
@@ -33,110 +33,98 @@ class DetoxCircusEnvironment extends NodeEnvironment {
33
33
  constructor(config, context) {
34
34
  super(assertJestCircus27(config), assertExistingContext(context));
35
35
 
36
- /** @private */
37
- this._timer = null;
38
- /** @private */
39
- this._listenerFactories = {
40
- DetoxInitErrorListener,
41
- DetoxPlatformFilterListener,
42
- DetoxCoreListener,
43
- SpecReporter,
44
- WorkerAssignReporter,
45
- };
46
36
  /** @private */
47
37
  this._shouldManageDetox = detox.getStatus() === 'inactive';
48
38
  /** @private */
49
- this._setupFailed = false;
39
+ this._timer = new Timer();
40
+
50
41
  /** @internal */
51
42
  this.testPath = context.testPath;
52
43
  /** @protected */
53
44
  this.testEventListeners = [];
54
45
  /** @protected */
55
- this.initTimeout = detox.config.testRunner.jest.initTimeout;
56
- /** @internal */
46
+ this.setupTimeout = detox.config.testRunner.jest.setupTimeout;
47
+ /** @protected */
48
+ this.teardownTimeout = detox.config.testRunner.jest.teardownTimeout;
49
+
57
50
  log.trace.begin(this.testPath);
58
51
 
59
- this.setup = log.trace.complete.bind(null, 'set up environment', this.setup.bind(this));
52
+ const _setup = this.setup.bind(this);
53
+ this.setup = async () => {
54
+ await log.trace.complete('set up environment', async () => {
55
+ try {
56
+ this._timer.schedule(this.setupTimeout);
57
+ await this._handleTestEventAsync({ name: 'environment_setup_start' });
58
+ await this._timer.run(`setting up Detox environment`, _setup);
59
+ await this._handleTestEventAsync({ name: 'environment_setup_success' });
60
+ } catch (error) {
61
+ this._timer.schedule(this.teardownTimeout);
62
+ await this._handleTestEventAsync({ name: 'environment_setup_failure', error });
63
+ throw error;
64
+ } finally {
65
+ this._timer.clear();
66
+ }
67
+ });
68
+ };
69
+
60
70
  const _teardown = this.teardown.bind(this);
61
71
  this.teardown = async () => {
62
- try {
63
- await log.trace.complete('tear down environment', _teardown);
64
- } finally {
65
- await log.trace.end();
66
- }
72
+ await log.trace.complete('tear down environment', async () => {
73
+ try {
74
+ this._timer.schedule(this.teardownTimeout);
75
+ await this._handleTestEventAsync({ name: 'environment_teardown_start' });
76
+ await this._timer.run(`tearing down Detox environment`, _teardown);
77
+ await this._handleTestEventAsync({ name: 'environment_teardown_success' });
78
+ } catch (error) {
79
+ if (this._timer.expired) {
80
+ this._timer.schedule(this.teardownTimeout);
81
+ }
82
+
83
+ await this._handleTestEventAsync({ name: 'environment_teardown_failure', error });
84
+ throw error;
85
+ } finally {
86
+ this._timer.clear();
87
+ log.trace.end();
88
+ }
89
+ });
67
90
  };
91
+
92
+ this.registerListeners({
93
+ DetoxInitErrorListener,
94
+ DetoxPlatformFilterListener,
95
+ DetoxCoreListener,
96
+ SpecReporter,
97
+ WorkerAssignReporter,
98
+ });
68
99
  }
69
100
 
70
101
  /** @override */
71
102
  async setup() {
72
- try {
73
- await super.setup();
74
- await Timer.run({
75
- description: `setting up Detox environment`,
76
- timeout: this.initTimeout,
77
- fn: async () => {
78
- await this.initDetox();
79
- this._instantiateListeners();
80
- },
81
- });
82
- } catch (e) {
83
- this._setupFailed = true;
84
- throw e;
85
- }
103
+ await this.initDetox();
86
104
  }
87
105
 
88
- /** @override */
89
- async handleTestEvent(event, state) {
90
- const { name } = event;
106
+ handleTestEvent = async (event, state) => {
107
+ this._timer.schedule(state.testTimeout != null ? state.testTimeout : this.setupTimeout);
91
108
 
92
- if (SYNC_CIRCUS_EVENTS.has(name)) {
93
- return this._handleTestEventSync(event, state);
94
- }
95
-
96
- this._timer = new Timer({
97
- description: `handling jest-circus "${name}" event`,
98
- timeout: state.testTimeout != null ? state.testTimeout : this.initTimeout,
99
- });
100
-
101
- try {
102
- for (const listener of this.testEventListeners) {
103
- if (typeof listener[name] !== 'function') {
104
- continue;
105
- }
106
-
107
- try {
108
- await this._timer.run(() => listener[name](event, state));
109
- } catch (listenerError) {
110
- log.error(listenerError);
111
- break;
112
- }
113
- }
114
- } finally {
115
- this._timer.dispose();
116
- this._timer = null;
109
+ if (SYNC_CIRCUS_EVENTS.has(event.name)) {
110
+ this._handleTestEventSync(event, state);
111
+ } else {
112
+ await this._handleTestEventAsync(event, state);
117
113
  }
118
- }
114
+ };
119
115
 
120
116
  /** @override */
121
117
  async teardown() {
122
- await Timer.run({
123
- description: `tearing down Detox environment`,
124
- timeout: this.initTimeout,
125
- fn: async () => {
126
- try {
127
- if (this._setupFailed) {
128
- await detox.reportFailedTests([this.testPath], false);
129
- }
130
- } finally {
131
- await this.cleanupDetox();
132
- }
133
- },
134
- });
118
+ await this.cleanupDetox();
135
119
  }
136
120
 
137
121
  /** @protected */
138
122
  registerListeners(map) {
139
- Object.assign(this._listenerFactories, map);
123
+ for (const Listener of Object.values(map)) {
124
+ this.testEventListeners.push(new Listener({
125
+ env: this,
126
+ }));
127
+ }
140
128
  }
141
129
 
142
130
  /**
@@ -153,6 +141,8 @@ class DetoxCircusEnvironment extends NodeEnvironment {
153
141
  } else {
154
142
  await detox.installWorker(opts);
155
143
  }
144
+
145
+ return detox.worker;
156
146
  }
157
147
 
158
148
  /** @protected */
@@ -176,11 +166,22 @@ class DetoxCircusEnvironment extends NodeEnvironment {
176
166
  }
177
167
 
178
168
  /** @private */
179
- _instantiateListeners() {
180
- for (const Listener of Object.values(this._listenerFactories)) {
181
- this.testEventListeners.push(new Listener({
182
- env: this,
183
- }));
169
+ async _handleTestEventAsync(event, state = null) {
170
+ const description = `handling ${state ? 'jest-circus' : 'jest-environment'} "${event.name}" event`;
171
+
172
+ for (const listener of this.testEventListeners) {
173
+ if (typeof listener[event.name] !== 'function') {
174
+ continue;
175
+ }
176
+
177
+ try {
178
+ await this._timer.run(description, () => listener[event.name](event, state));
179
+ } catch (listenerError) {
180
+ log.error(listenerError);
181
+ if (this._timer.expired) {
182
+ break;
183
+ }
184
+ }
184
185
  }
185
186
  }
186
187
  }
@@ -14,7 +14,7 @@ class DetoxCoreListener {
14
14
  this._startedTests = new WeakSet();
15
15
  this._testsFailedBeforeStart = new WeakSet();
16
16
  this._env = env;
17
- this._testRunTimes = 1;
17
+ this._circusRetryTimes = 1;
18
18
  }
19
19
 
20
20
  async setup() {
@@ -46,7 +46,7 @@ class DetoxCoreListener {
46
46
  }
47
47
 
48
48
  const circusRetryTimes = +this._env.global[RETRY_TIMES];
49
- this._testRunTimes = isNaN(circusRetryTimes) ? 1 : 1 + circusRetryTimes;
49
+ this._circusRetryTimes = isNaN(circusRetryTimes) ? 1 : 1 + circusRetryTimes;
50
50
  }
51
51
 
52
52
  async hook_start(event, state) {
@@ -58,7 +58,12 @@ class DetoxCoreListener {
58
58
  log.trace.end({ success: true });
59
59
  }
60
60
 
61
- async hook_failure({ error }) {
61
+ async hook_failure({ error, hook }) {
62
+ await detoxInternals.onHookFailure({
63
+ error,
64
+ hook: hook.type,
65
+ });
66
+
62
67
  log.trace.end({ success: false, error });
63
68
  }
64
69
 
@@ -94,14 +99,6 @@ class DetoxCoreListener {
94
99
  }
95
100
  }
96
101
 
97
- async run_finish(_event, state) {
98
- const hasFailedTests = this._hasFailedTests(state.rootDescribeBlock);
99
- if (hasFailedTests) {
100
- const handledByJestCircus = this._testRunTimes > 1 && !detoxInternals.config.testRunner.jest.retryAfterCircusRetries;
101
- await detoxInternals.reportFailedTests([this._env.testPath], handledByJestCircus);
102
- }
103
- }
104
-
105
102
  async _onBeforeActualTestStart(test) {
106
103
  if (!test || test.status === 'skip' || this._startedTests.has(test) || this._testsFailedBeforeStart.has(test)) {
107
104
  return false;
@@ -127,19 +124,7 @@ class DetoxCoreListener {
127
124
 
128
125
  _getTestInvocations(test) {
129
126
  const { testSessionIndex } = detoxInternals.session;
130
- return testSessionIndex * this._testRunTimes + test.invocations;
131
- }
132
-
133
- _hasFailedTests(block) {
134
- if (block.children) {
135
- for (const child of block.children) {
136
- if (this._hasFailedTests(child)) {
137
- return true;
138
- }
139
- }
140
- }
141
-
142
- return block.errors ? block.errors.length > 0 : false;
127
+ return testSessionIndex * this._circusRetryTimes + test.invocations;
143
128
  }
144
129
 
145
130
  _getTestMetadata(test) {
@@ -5,7 +5,7 @@ const { device } = require('../../../..');
5
5
  const PLATFORM_REGEXP = /^:([^:]+):/;
6
6
 
7
7
  class DetoxPlatformFilterListener {
8
- constructor() {
8
+ setup() {
9
9
  this._platform = device.getPlatform();
10
10
  }
11
11
 
@@ -1,7 +1,7 @@
1
1
  const { makeResourceTitle, makeResourceSubTitle } = require('./utils');
2
2
 
3
3
  function makeURLDescription(url, urlCount) {
4
- return makeResourceSubTitle(`URL #${urlCount}: ${url}.`);
4
+ return makeResourceSubTitle(`URL #${urlCount}: ${url}`);
5
5
  }
6
6
 
7
7
  module.exports = function(properties) {
@@ -144,6 +144,10 @@ function validateAppConfig({ appConfig, appPath, deviceConfig, errorComposer })
144
144
  if (appConfig.launchArgs && !_.isObject(appConfig.launchArgs)) {
145
145
  throw errorComposer.malformedAppLaunchArgs(appPath);
146
146
  }
147
+
148
+ if (appConfig.type !== 'android.apk' && appConfig.reversePorts) {
149
+ throw errorComposer.unsupportedReversePorts(appPath);
150
+ }
147
151
  }
148
152
 
149
153
  module.exports = composeAppsConfig;
@@ -32,8 +32,10 @@ function composeRunnerConfig(opts) {
32
32
  retries: 0,
33
33
  inspectBrk: inspectBrkHookDefault,
34
34
  forwardEnv: false,
35
+ bail: false,
35
36
  jest: {
36
- initTimeout: 300000,
37
+ setupTimeout: 300000,
38
+ teardownTimeout: 30000,
37
39
  retryAfterCircusRetries: false,
38
40
  reportSpecs: undefined,
39
41
  reportWorkerAssign: true,
@@ -88,7 +90,7 @@ function adaptLegacyRunnerConfig(globalConfig) {
88
90
  return globalConfig.testRunner;
89
91
  }
90
92
 
91
- log.warn(`Please migrate your Detox config according to the guide: [TODO: insert the migration guilde link]`);
93
+ log.warn(`Please migrate your Detox config according to the guide:\nhttps://wix.github.io/Detox/docs/next/guide/migration\n`);
92
94
  const testRunner = globalConfig[testRunnerKey];
93
95
  const runnerConfig = globalConfig[runnerConfigKey];
94
96
  const specs = globalConfig.specs != null ? String(globalConfig.specs) : undefined;
@@ -12,11 +12,11 @@ class AVDValidator {
12
12
  this._emulatorVersionResolver = emulatorVersionResolver;
13
13
  }
14
14
 
15
- async validate(avdName) {
15
+ async validate(avdName, isHeadless) {
16
16
  const avds = await this._avdsResolver.resolve(avdName);
17
17
  this._assertAVDs(avds);
18
18
  await this._assertAVDMatch(avds, avdName);
19
- await this._validateEmulatorVer();
19
+ await this._validateEmulatorVer(isHeadless);
20
20
  }
21
21
 
22
22
  _assertAVDs(avds) {
@@ -37,8 +37,8 @@ class AVDValidator {
37
37
  }
38
38
  }
39
39
 
40
- async _validateEmulatorVer() {
41
- const emulatorVersion = await this._emulatorVersionResolver.resolve();
40
+ async _validateEmulatorVer(isHeadless) {
41
+ const emulatorVersion = await this._emulatorVersionResolver.resolve(isHeadless);
42
42
  if (!emulatorVersion) {
43
43
  logger.warn({ event: 'AVD_VALIDATION' }, 'Emulator version detection failed (See previous logs)');
44
44
  return;
@@ -34,7 +34,7 @@ class EmulatorAllocDriver extends AllocationDriverBase {
34
34
  async allocate(deviceConfig) {
35
35
  const avdName = deviceConfig.device.avdName;
36
36
 
37
- await this._avdValidator.validate(avdName);
37
+ await this._avdValidator.validate(avdName, deviceConfig.headless);
38
38
  await this._fixAvdConfigIniSkinNameIfNeeded(avdName, deviceConfig.headless);
39
39
 
40
40
  const allocResult = await this._allocationHelper.allocateDevice(avdName);
@@ -157,6 +157,30 @@ class AppleSimUtils {
157
157
  }
158
158
 
159
159
  async sendToHome(udid) {
160
+ if (await this._isSpringBoardInaccessible(udid)) {
161
+ // SpringBoard is not directly accessible by Simctl on iOS 16.0 and above, therefore we launch and terminate the
162
+ // Settings app instead. This sends the currently open app to the background and brings the home screen to the
163
+ // foreground.
164
+ await this._launchAndTerminateSettings(udid);
165
+ return;
166
+ }
167
+
168
+ await this._launchSpringBoard(udid);
169
+ }
170
+
171
+ async _isSpringBoardInaccessible(udid) {
172
+ const device = await this._findDeviceByUDID(udid);
173
+ const majorIOSVersion = parseInt(device.os.version.split('.')[0]);
174
+ return majorIOSVersion >= 16;
175
+ }
176
+
177
+ async _launchAndTerminateSettings(udid) {
178
+ const bundleId = 'com.apple.Preferences';
179
+ await this._execSimctl({ cmd: `launch ${udid} ${bundleId}`, retries: 10 });
180
+ await this._execSimctl({ cmd: `terminate ${udid} ${bundleId}`, retries: 10 });
181
+ }
182
+
183
+ async _launchSpringBoard(udid) {
160
184
  await this._execSimctl({ cmd: `launch ${udid} com.apple.springboard`, retries: 10 });
161
185
  }
162
186
 
@@ -271,7 +295,7 @@ class AppleSimUtils {
271
295
  // ```
272
296
  // This workaround is done to ignore the error above, as we do not care if the app was running before, we just
273
297
  // want to make sure it isn't now.
274
- if (err.code === 3 &&
298
+ if (err.code === 3 &&
275
299
  (err.stderr.includes(`the app is not currently running`) ||
276
300
  err.stderr.includes(`The operation couldn’t be completed. found nothing to terminate`))) {
277
301
  return;
@@ -30,7 +30,7 @@ class GenyGlobalLifecycleHandler {
30
30
  }
31
31
 
32
32
  async function doSafeCleanup(instanceLifecycleService, instanceHandles) {
33
- logger.info(cleanupLogData, 'Initiating Genymotion cloud instances teardown...');
33
+ logger.info(cleanupLogData, 'Initiating Genymotion SaaS instances teardown...');
34
34
 
35
35
  const deletionLeaks = [];
36
36
  const killPromises = instanceHandles.map((instanceHandle) =>
@@ -43,7 +43,7 @@ async function doSafeCleanup(instanceLifecycleService, instanceHandles) {
43
43
 
44
44
  function reportGlobalCleanupSummary(deletionLeaks) {
45
45
  if (deletionLeaks.length) {
46
- logger.warn(cleanupLogData, 'WARNING! Detected a Genymotion cloud instance leakage, for the following instances:');
46
+ logger.warn(cleanupLogData, 'WARNING! Detected a Genymotion SaaS instance leakage, for the following instances:');
47
47
 
48
48
  deletionLeaks.forEach(({ uuid, name, error }) => {
49
49
  logger.warn(cleanupLogData, [
@@ -238,6 +238,16 @@ class RuntimeDevice {
238
238
  async installApp(binaryPath, testBinaryPath) {
239
239
  const currentApp = binaryPath ? { binaryPath, testBinaryPath } : this._getCurrentApp();
240
240
  await this.deviceDriver.installApp(currentApp.binaryPath, currentApp.testBinaryPath);
241
+
242
+ // This abstraction leaks because our drivers themselves leak,
243
+ // so don't blame me - DeviceBaseDriver itself has `reverseTcpPort`,
244
+ // setting a vicious downward spiral. I can't refactor everything
245
+ // in a single pull request, so let's bear with it for now.
246
+ if (Array.isArray(currentApp.reversePorts)) {
247
+ for (const port of currentApp.reversePorts) {
248
+ await this.reverseTcpPort(port);
249
+ }
250
+ }
241
251
  }
242
252
 
243
253
  async uninstallApp(bundleId) {
@@ -507,6 +507,14 @@ Examine your Detox config${this._atPath()}`,
507
507
  });
508
508
  }
509
509
 
510
+ unsupportedReversePorts(appPath) {
511
+ return new DetoxConfigError({
512
+ message: `Non-Android app configs cannot have "reversePorts" property:`,
513
+ debugInfo: this._focusOnAppConfig(appPath),
514
+ inspectOptions: { depth: 4 },
515
+ });
516
+ }
517
+
510
518
  missingAppBinaryPath(appPath) {
511
519
  return new DetoxConfigError({
512
520
  message: `Missing "binaryPath" property in the app config.\nExpected a string:`,