detox 20.0.1-breaking.new-global-lifecycle.0 → 20.0.4-breaking.new-global-lifecycle.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (61) hide show
  1. package/Detox-android/com/wix/detox/{20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0-javadoc.jar → 20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0-javadoc.jar} +0 -0
  2. package/Detox-android/com/wix/detox/20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0-javadoc.jar.md5 +1 -0
  3. package/Detox-android/com/wix/detox/20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0-javadoc.jar.sha1 +1 -0
  4. package/Detox-android/com/wix/detox/20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0-javadoc.jar.sha256 +1 -0
  5. package/Detox-android/com/wix/detox/20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0-javadoc.jar.sha512 +1 -0
  6. package/Detox-android/com/wix/detox/{20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0-sources.jar → 20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0-sources.jar} +0 -0
  7. package/Detox-android/com/wix/detox/20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0-sources.jar.md5 +1 -0
  8. package/Detox-android/com/wix/detox/20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0-sources.jar.sha1 +1 -0
  9. package/Detox-android/com/wix/detox/20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0-sources.jar.sha256 +1 -0
  10. package/Detox-android/com/wix/detox/20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0-sources.jar.sha512 +1 -0
  11. package/Detox-android/com/wix/detox/{20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0.aar → 20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0.aar} +0 -0
  12. package/Detox-android/com/wix/detox/{20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0.aar.md5 → 20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0.aar.md5} +0 -0
  13. package/Detox-android/com/wix/detox/{20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0.aar.sha1 → 20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0.aar.sha1} +0 -0
  14. package/Detox-android/com/wix/detox/{20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0.aar.sha256 → 20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0.aar.sha256} +0 -0
  15. package/Detox-android/com/wix/detox/{20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0.aar.sha512 → 20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0.aar.sha512} +0 -0
  16. package/Detox-android/com/wix/detox/{20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0.pom → 20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0.pom} +1 -1
  17. package/Detox-android/com/wix/detox/20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0.pom.md5 +1 -0
  18. package/Detox-android/com/wix/detox/20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0.pom.sha1 +1 -0
  19. package/Detox-android/com/wix/detox/20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.0.pom.sha256 +1 -0
  20. package/Detox-android/com/wix/detox/20.0.4-breaking.new-global-lifecycle.0/detox-20.0.4-breaking.new-global-lifecycle.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/index.d.ts +5 -0
  29. package/internals.d.ts +22 -10
  30. package/local-cli/build.js +1 -1
  31. package/local-cli/build.test.js +14 -14
  32. package/local-cli/test.js +3 -3
  33. package/local-cli/test.test.js +20 -14
  34. package/local-cli/testCommand/TestRunnerCommand.js +6 -7
  35. package/package.json +2 -2
  36. package/runners/jest/testEnvironment/index.js +1 -1
  37. package/runners/jest/testEnvironment/listeners/DetoxCoreListener.js +5 -9
  38. package/runners/jest/testEnvironment/listeners/SpecReporter.js +1 -1
  39. package/runners/jest/testEnvironment/listeners/WorkerAssignReporter.js +1 -1
  40. package/src/DetoxWorker.js +7 -1
  41. package/src/configuration/composeRunnerConfig.js +49 -1
  42. package/src/configuration/index.js +9 -8
  43. package/src/errors/DetoxConfigErrorComposer.js +4 -2
  44. package/src/ipc/IPCClient.js +3 -2
  45. package/src/ipc/IPCServer.js +9 -2
  46. package/src/ipc/state.js +14 -1
  47. package/src/realms/DetoxContext.js +2 -2
  48. package/src/realms/DetoxPrimaryContext.js +11 -6
  49. package/src/realms/DetoxSecondaryContext.js +31 -25
  50. package/Detox-android/com/wix/detox/20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0-javadoc.jar.md5 +0 -1
  51. package/Detox-android/com/wix/detox/20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0-javadoc.jar.sha1 +0 -1
  52. package/Detox-android/com/wix/detox/20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0-javadoc.jar.sha256 +0 -1
  53. package/Detox-android/com/wix/detox/20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0-javadoc.jar.sha512 +0 -1
  54. package/Detox-android/com/wix/detox/20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0-sources.jar.md5 +0 -1
  55. package/Detox-android/com/wix/detox/20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0-sources.jar.sha1 +0 -1
  56. package/Detox-android/com/wix/detox/20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0-sources.jar.sha256 +0 -1
  57. package/Detox-android/com/wix/detox/20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0-sources.jar.sha512 +0 -1
  58. package/Detox-android/com/wix/detox/20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0.pom.md5 +0 -1
  59. package/Detox-android/com/wix/detox/20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0.pom.sha1 +0 -1
  60. package/Detox-android/com/wix/detox/20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0.pom.sha256 +0 -1
  61. package/Detox-android/com/wix/detox/20.0.1-breaking.new-global-lifecycle.0/detox-20.0.1-breaking.new-global-lifecycle.0.pom.sha512 +0 -1
@@ -0,0 +1 @@
1
+ f73cf8db8e2a2d9f007e8f0e1b10c8a3206425ff5b48eecab8780d32adbb12fb
@@ -0,0 +1 @@
1
+ e21d314a45864e21cbea7bf17e9d23814def296e92b3b75ce64ca504f68d3b9aedc17c1beecd90407b9cb6e04803eb2d9ea4e2b9b4791ef34fc6aab597150770
@@ -0,0 +1 @@
1
+ 407c01beb305d15307e9defed1c41b643e01db58a1f15a4fa68b177e0ef7de49
@@ -0,0 +1 @@
1
+ 0f48a23d1840085fd5b5c044f6fd4a9e7b898f5498d4ec22b94b94d42eed3982060aea614347d73ee97e57de200d17d8100aee99c569fb8fc1d3e9c9a5fc7911
@@ -3,7 +3,7 @@
3
3
  <modelVersion>4.0.0</modelVersion>
4
4
  <groupId>com.wix</groupId>
5
5
  <artifactId>detox</artifactId>
6
- <version>20.0.1-breaking.new-global-lifecycle.0</version>
6
+ <version>20.0.4-breaking.new-global-lifecycle.0</version>
7
7
  <packaging>aar</packaging>
8
8
  <name>Detox</name>
9
9
  <description>Gray box end-to-end testing and automation library for mobile apps</description>
@@ -0,0 +1 @@
1
+ e5c436ef487d630e7685b412f6160f33be799f64b99b928cdfbaa2c41aa0561e
@@ -0,0 +1 @@
1
+ 6894a1d7a895abc41593948227786e0a41428bcaee4f4246cb9b8b3d40dc50f6b1404d622a07adbd992397ff7687ba97f8020528666ee77cf66db9377eb4b206
@@ -3,11 +3,11 @@
3
3
  <groupId>com.wix</groupId>
4
4
  <artifactId>detox</artifactId>
5
5
  <versioning>
6
- <latest>20.0.1-breaking.new-global-lifecycle.0</latest>
7
- <release>20.0.1-breaking.new-global-lifecycle.0</release>
6
+ <latest>20.0.4-breaking.new-global-lifecycle.0</latest>
7
+ <release>20.0.4-breaking.new-global-lifecycle.0</release>
8
8
  <versions>
9
- <version>20.0.1-breaking.new-global-lifecycle.0</version>
9
+ <version>20.0.4-breaking.new-global-lifecycle.0</version>
10
10
  </versions>
11
- <lastUpdated>20220810074907</lastUpdated>
11
+ <lastUpdated>20220815150714</lastUpdated>
12
12
  </versioning>
13
13
  </metadata>
@@ -1 +1 @@
1
- b2585c88d6aa7316562eefd1276b22b8
1
+ 9d4e72b06ca1b5fefd299e136c22ade7
@@ -1 +1 @@
1
- a08cf502a9a0fd7b00c98d8ac7406f638489da61
1
+ 2b6e30446d53ced376695d11cd1eb93b03c03741
@@ -1 +1 @@
1
- 5afa6a8108d9375f9e5ede74faae8dd51d73ae25dab9e7915cfe11503c0f4a36
1
+ 666b18918d13dcf788c142e10b4b14d4e3715836d3e3bcf637b07b6fe3ccf45d
@@ -1 +1 @@
1
- 701eeb42080f3cc2cf38ddb06f0a1185633a958dac2325d7225fc5a3aefcd982def05877242c10cec1bd569d8ac2f600f7b8f0d85c4399ccd61fdeb9e0d8f6b2
1
+ 2e8d99941b4da68f7a3740b3b4f3361e1d8c47f3abd49ba841d5523d722bdd13ebbfb09ecfc987f24d6add13b50fc318e3c172e67afda2c467fb24f337e7c62b
package/Detox-ios-src.tbz CHANGED
Binary file
package/Detox-ios.tbz CHANGED
Binary file
package/index.d.ts CHANGED
@@ -141,6 +141,11 @@ declare global {
141
141
  * Device init timeout
142
142
  */
143
143
  initTimeout?: number | undefined;
144
+ /**
145
+ * Insist on CLI-based retry mechanism even when the failed tests have been handled
146
+ * by jest.retryTimes(n) mechanism from Jest Circus.
147
+ */
148
+ retryAfterCircusRetries?: boolean;
144
149
  reportSpecs?: boolean | undefined;
145
150
  reportWorkerAssign?: boolean | undefined;
146
151
  };
package/internals.d.ts CHANGED
@@ -47,9 +47,14 @@ declare global {
47
47
  onRunFinish(event: unknown): Promise<void>;
48
48
 
49
49
  /**
50
- * Powers the "--retries <N>" of Detox CLI under the hood.
50
+ * Reports to Detox CLI about failed tests that could have been re-run if
51
+ * {@link Detox.DetoxTestRunnerConfig#retries} is set to a non-zero.
52
+ *
53
+ * @param testFilePaths array of failed test files' paths
54
+ * @param permanent whether the failure is permanent, and the tests
55
+ * should not be re-run.
51
56
  */
52
- reportFailedTests(testFilePaths: string[]): Promise<void>;
57
+ reportFailedTests(testFilePaths: string[], permanent?: boolean): Promise<void>;
53
58
  // endregion
54
59
 
55
60
  readonly config: RuntimeConfig;
@@ -74,20 +79,27 @@ declare global {
74
79
 
75
80
  type SessionState = Readonly<{
76
81
  failedTestFiles: string[];
82
+ testFilesToRetry: string[];
83
+ testSessionIndex: number;
77
84
  workersCount: number;
78
85
  }>;
79
86
 
80
87
  type RuntimeConfig = Readonly<{
81
88
  configurationName: string;
82
89
 
83
- appsConfig: Record<string, Readonly<Detox.DetoxAppConfig>>;
84
- artifactsConfig: Detox.DetoxArtifactsConfig;
85
- behaviorConfig: Detox.DetoxBehaviorConfig;
86
- cliConfig: DetoxCLIConfig;
87
- deviceConfig: Detox.DetoxDeviceConfig;
88
- loggerConfig: Detox.DetoxLoggerConfig;
89
- runnerConfig: Detox.DetoxTestRunnerConfig;
90
- sessionConfig: Detox.DetoxSessionConfig;
90
+ /**
91
+ * Dictionary of app configurations,
92
+ * where the keys are defined by {@link Detox.DetoxAppConfig#name}
93
+ * or equal to "default" if the name is not configured.
94
+ */
95
+ apps: Record<string, Readonly<Detox.DetoxAppConfig>>;
96
+ artifacts: Readonly<Detox.DetoxArtifactsConfig>;
97
+ behavior: Readonly<Detox.DetoxBehaviorConfig>;
98
+ cli: Readonly<DetoxCLIConfig>;
99
+ device: Readonly<Detox.DetoxDeviceConfig>;
100
+ logger: Readonly<Detox.DetoxLoggerConfig>;
101
+ testRunner: Readonly<Detox.DetoxTestRunnerConfig>;
102
+ session: Readonly<Detox.DetoxSessionConfig>;
91
103
  }>;
92
104
 
93
105
  type DetoxCLIConfig = Readonly<Partial<{
@@ -37,7 +37,7 @@ module.exports.builder = {
37
37
  };
38
38
 
39
39
  module.exports.handler = async function build(argv) {
40
- const { appsConfig, errorComposer } = await detox.resolveConfig({ argv });
40
+ const { apps: appsConfig, errorComposer } = await detox.resolveConfig({ argv });
41
41
  const apps = _.entries(appsConfig);
42
42
 
43
43
  for (const [appName, app] of apps) {
@@ -14,12 +14,12 @@ describe('build', () => {
14
14
  const DetoxConfigErrorComposer = require('../src/errors/DetoxConfigErrorComposer');
15
15
 
16
16
  const config = {
17
- appsConfig: {},
18
- artifactsConfig: {},
19
- behaviorConfig: {},
17
+ apps: {},
18
+ artifacts: {},
19
+ behavior: {},
20
20
  errorComposer: new DetoxConfigErrorComposer(),
21
- deviceConfig: {},
22
- sessionConfig: {}
21
+ device: {},
22
+ session: {}
23
23
  };
24
24
 
25
25
  return ({
@@ -44,14 +44,14 @@ describe('build', () => {
44
44
  });
45
45
 
46
46
  it('runs the build script from the composed device config', async () => {
47
- detox.config.appsConfig.default = { build: 'yet another command' };
47
+ detox.config.apps.default = { build: 'yet another command' };
48
48
 
49
49
  await callCli('./build', 'build');
50
50
  expect(execSync).toHaveBeenCalledWith('yet another command', expect.anything());
51
51
  });
52
52
 
53
53
  it('skips building the app if the binary exists and --if-missing flag is set', async () => {
54
- detox.config.appsConfig.default = { build: 'yet another command', binaryPath: __filename };
54
+ detox.config.apps.default = { build: 'yet another command', binaryPath: __filename };
55
55
 
56
56
  await callCli('./build', 'build -i');
57
57
  expect(execSync).not.toHaveBeenCalled();
@@ -63,32 +63,32 @@ describe('build', () => {
63
63
  });
64
64
 
65
65
  it('fails with an error if a build script has not been found', async () => {
66
- detox.config.appsConfig.default = {};
66
+ detox.config.apps.default = {};
67
67
  await expect(callCli('./build', 'build')).rejects.toThrowError(/Failed to build/);
68
68
  });
69
69
 
70
70
  it('should ignore missing build command with -s, --silent flag', async () => {
71
- detox.config.appsConfig.default = {};
71
+ detox.config.apps.default = {};
72
72
  await expect(callCli('./build', 'build -s')).resolves.not.toThrowError();
73
73
  expect(detox.log.warn).not.toHaveBeenCalled();
74
74
  });
75
75
 
76
76
  it('should print a warning upon user build script failure', async () => {
77
- detox.config.appsConfig.default = { build: 'a command' };
77
+ detox.config.apps.default = { build: 'a command' };
78
78
  execSync.mockImplementation(() => { throw new Error('Build failure'); });
79
79
  await expect(callCli('./build', 'build')).rejects.toThrowError(/Build failure/);
80
80
  expect(detox.log.warn).toHaveBeenCalledWith(expect.stringContaining('You are responsible'));
81
81
  });
82
82
 
83
83
  it('should print a warning if app is not found at binary path', async () => {
84
- detox.config.appsConfig.default = { binaryPath: tempfile() };
84
+ detox.config.apps.default = { binaryPath: tempfile() };
85
85
  await expect(callCli('./build', 'build -s')).resolves.not.toThrowError();
86
86
  expect(detox.log.warn).toHaveBeenCalledWith(expect.stringContaining('could not find your app at the given binary path'));
87
87
  });
88
88
 
89
89
  it('should print extra message with the app name before building (in a multi-app configuration)', async () => {
90
- detox.config.appsConfig.app1 = { binaryPath: tempfile(), build: ':' };
91
- detox.config.appsConfig.app2 = { binaryPath: tempfile(), build: ':' };
90
+ detox.config.apps.app1 = { binaryPath: tempfile(), build: ':' };
91
+ detox.config.apps.app2 = { binaryPath: tempfile(), build: ':' };
92
92
 
93
93
  await expect(callCli('./build', 'build -s')).resolves.not.toThrowError();
94
94
  expect(detox.log.info).toHaveBeenCalledWith(expect.stringContaining('app1'));
@@ -96,7 +96,7 @@ describe('build', () => {
96
96
  });
97
97
 
98
98
  it('should not print that extra message when the app is single', async () => {
99
- detox.config.appsConfig.default = { binaryPath: tempfile(), build: ':' };
99
+ detox.config.apps.default = { binaryPath: tempfile(), build: ':' };
100
100
 
101
101
  await expect(callCli('./build', 'build -s')).resolves.not.toThrowError();
102
102
  expect(detox.log.info).not.toHaveBeenCalledWith(expect.stringContaining('default'));
package/local-cli/test.js CHANGED
@@ -15,9 +15,9 @@ module.exports.handler = async function test({ detoxArgs, runnerArgs }) {
15
15
  });
16
16
 
17
17
  const runnerCommand = new TestRunnerCommand()
18
- .setRunnerConfig(detox.config.runnerConfig)
19
- .setDeviceConfig(detox.config.deviceConfig)
20
- .replicateCLIConfig(detox.config.cliConfig);
18
+ .setRunnerConfig(detox.config.testRunner)
19
+ .setDeviceConfig(detox.config.device)
20
+ .replicateCLIConfig(detox.config.cli);
21
21
 
22
22
  await runnerCommand.execute();
23
23
  } finally {
@@ -116,7 +116,7 @@ describe('CLI', () => {
116
116
  });
117
117
  });
118
118
 
119
- test('should use runnerConfig.specs as default specs', async () => {
119
+ test('should use testRunner.args._ as default specs', async () => {
120
120
  detoxConfig.testRunner.args._ = ['e2e/sanity'];
121
121
  await run();
122
122
  expect(_.last(cliCall().argv)).toEqual('e2e/sanity');
@@ -140,26 +140,32 @@ describe('CLI', () => {
140
140
 
141
141
  test.each([['-R'], ['--retries']])('%s <value> should execute unsuccessful run N extra times', async (__retries) => {
142
142
  const context = require('../internals');
143
- context.session.failedTestFiles = ['e2e/failing1.test.js', 'e2e/failing2.test.js'];
144
- context.session.failedTestFiles.splice = jest.fn(() => {
145
- context.session.failedTestFiles = ['e2e/failing2.test.js'];
146
- });
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']);
147
147
 
148
148
  mockExitCode(1);
149
149
 
150
150
  await run(__retries, 2).catch(_.noop);
151
151
 
152
- expect(cliCall(0).env).not.toHaveProperty('DETOX_RERUN_INDEX');
153
152
  expect(cliCall(0).argv).toEqual([expect.stringMatching(/executable$/), '--config', 'e2e/config.json']);
154
- expect(cliCall(0).fullCommand).not.toMatch(/DETOX_RERUN_INDEX/);
155
-
156
- expect(cliCall(1).env.DETOX_RERUN_INDEX).toBe('1');
157
153
  expect(cliCall(1).argv).toEqual([expect.stringMatching(/executable$/), '--config', 'e2e/config.json', 'e2e/failing1.test.js', 'e2e/failing2.test.js']);
158
- expect(cliCall(1).fullCommand).not.toMatch(/DETOX_RERUN_INDEX/);
159
-
160
154
  expect(cliCall(2).argv).toEqual([expect.stringMatching(/executable$/), '--config', 'e2e/config.json', 'e2e/failing2.test.js']);
161
- expect(cliCall(2).env.DETOX_RERUN_INDEX).toBe('2');
162
- expect(cliCall(2).fullCommand).not.toMatch(/DETOX_RERUN_INDEX/);
155
+ });
156
+
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'];
161
+
162
+ mockExitCode(1);
163
+
164
+ await run(__retries, 2).catch(_.noop);
165
+
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);
163
169
  });
164
170
 
165
171
  test.each([['-R'], ['--retries']])('%s <value> should not restart test runner if there are no failing tests paths', async (__retries) => {
@@ -172,7 +178,7 @@ describe('CLI', () => {
172
178
 
173
179
  test.each([['-R'], ['--retries']])('%s <value> should retain -- <...explicitPassthroughArgs>', async (__retries) => {
174
180
  const context = require('../internals');
175
- context.session.failedTestFiles = ['tests/failing.test.js'];
181
+ context.session.testFilesToRetry = ['tests/failing.test.js'];
176
182
 
177
183
  mockExitCode(1);
178
184
 
@@ -88,7 +88,7 @@ class TestRunnerCommand {
88
88
  try {
89
89
  if (launchError) {
90
90
  const list = this._argv._.map((file, index) => ` ${index + 1}. ${file}`).join('\n');
91
- detox.log.error(
91
+ detox.log.error({ event: 'RETRY_RUN' },
92
92
  `There were failing tests in the following files:\n${list}\n\n` +
93
93
  'Detox CLI is going to restart the test runner with those files...\n'
94
94
  );
@@ -99,15 +99,14 @@ class TestRunnerCommand {
99
99
  } catch (e) {
100
100
  launchError = e;
101
101
 
102
- // @ts-ignore
103
- const { failedTestFiles } = detox.session;
104
- if (_.isEmpty(failedTestFiles)) {
102
+ const { failedTestFiles, testFilesToRetry } = detox.session;
103
+ if (!_.isEmpty(failedTestFiles) || _.isEmpty(testFilesToRetry)) {
105
104
  throw e;
106
105
  }
107
106
 
108
- this._argv._ = failedTestFiles.slice();
109
- this._env.DETOX_RERUN_INDEX = 1 + (this._env.DETOX_RERUN_INDEX || 0);
110
- failedTestFiles.splice(0, Infinity);
107
+ this._argv._ = testFilesToRetry.splice(0, Infinity);
108
+ // @ts-ignore
109
+ detox.session.testSessionIndex++; // it is always a primary context, so we can update it
111
110
  }
112
111
  } while (launchError && --runsLeft > 0);
113
112
 
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.1-breaking.new-global-lifecycle.0",
4
+ "version": "20.0.4-breaking.new-global-lifecycle.0",
5
5
  "bin": {
6
6
  "detox": "local-cli/cli.js"
7
7
  },
@@ -183,5 +183,5 @@
183
183
  }
184
184
  }
185
185
  },
186
- "gitHead": "b05792476b6eaedeb998b8625ecc78132aee361f"
186
+ "gitHead": "5a4d2fa00b359b139d9bda7f5128b9d75cfa838b"
187
187
  }
@@ -46,7 +46,7 @@ class DetoxCircusEnvironment extends NodeEnvironment {
46
46
  /** @protected */
47
47
  this.testEventListeners = [];
48
48
  /** @protected */
49
- this.initTimeout = detox.config.runnerConfig.jest.initTimeout;
49
+ this.initTimeout = detox.config.testRunner.jest.initTimeout;
50
50
  }
51
51
 
52
52
  /** @override */
@@ -16,18 +16,13 @@ class DetoxCoreListener {
16
16
  }
17
17
 
18
18
  _getTestInvocations(test) {
19
- const { DETOX_RERUN_INDEX } = process.env;
20
-
21
- if (!isNaN(DETOX_RERUN_INDEX)) {
22
- return Number(DETOX_RERUN_INDEX) * this._testRunTimes + test.invocations;
23
- } else {
24
- return test.invocations;
25
- }
19
+ const { testSessionIndex } = detoxInternals.session;
20
+ return testSessionIndex * this._testRunTimes + test.invocations;
26
21
  }
27
22
 
28
23
  async setup() {
29
24
  // Workaround to override Jest's expect
30
- if (detoxInternals.config.behaviorConfig.init.exposeGlobals) {
25
+ if (detoxInternals.config.behavior.init.exposeGlobals) {
31
26
  this._env.global.expect = detox.expect;
32
27
  }
33
28
  }
@@ -103,7 +98,8 @@ class DetoxCoreListener {
103
98
 
104
99
  async run_finish(_event, state) {
105
100
  if (this._hasFailedTests(state.rootDescribeBlock)) {
106
- await detoxInternals.reportFailedTests([this._env.testPath]);
101
+ const handledByJestCircus = this._testRunTimes > 1 && !detoxInternals.config.testRunner.jest.retryAfterCircusRetries;
102
+ await detoxInternals.reportFailedTests([this._env.testPath], handledByJestCircus);
107
103
  }
108
104
  }
109
105
 
@@ -16,7 +16,7 @@ class SpecReporter {
16
16
  }
17
17
 
18
18
  get enabled() {
19
- const jestSection = config.runnerConfig.jest;
19
+ const jestSection = config.testRunner.jest;
20
20
  const reportSpecs = jestSection && jestSection.reportSpecs;
21
21
 
22
22
  return reportSpecs !== undefined ? reportSpecs : session.workersCount === 1;
@@ -12,7 +12,7 @@ class WorkerAssignReporter {
12
12
  }
13
13
 
14
14
  run_start() {
15
- if (config.runnerConfig.jest.reportWorkerAssign) {
15
+ if (config.testRunner.jest.reportWorkerAssign) {
16
16
  log.info({ event: 'WORKER_ASSIGN' }, `${this._formatTestName()} is assigned to ${this._formatDeviceName()}`);
17
17
  }
18
18
  }
@@ -56,7 +56,13 @@ class DetoxWorker {
56
56
  async init() {
57
57
  if (this._isCleaningUp) return;
58
58
 
59
- const { appsConfig, artifactsConfig, behaviorConfig, deviceConfig, sessionConfig } = this._config;
59
+ const {
60
+ apps: appsConfig,
61
+ artifacts: artifactsConfig,
62
+ behavior: behaviorConfig,
63
+ device: deviceConfig,
64
+ session: sessionConfig
65
+ } = this._config;
60
66
  this._appsConfig = appsConfig;
61
67
  this._artifactsConfig = artifactsConfig;
62
68
  this._behaviorConfig = behaviorConfig;
@@ -2,6 +2,8 @@ const os = require('os');
2
2
 
3
3
  const _ = require('lodash');
4
4
 
5
+ const log = require('../utils/logger');
6
+
5
7
  /**
6
8
  * @param {object} opts
7
9
  * @param {Detox.DetoxConfig} opts.globalConfig
@@ -12,7 +14,7 @@ const _ = require('lodash');
12
14
  * @returns {Detox.DetoxTestRunnerConfig} opts.testRunnerArgv
13
15
  */
14
16
  function composeRunnerConfig(opts) {
15
- const globalConfig = opts.globalConfig.testRunner;
17
+ const globalConfig = adaptLegacyRunnerConfig(opts.globalConfig);
16
18
  if (globalConfig != null && typeof globalConfig !== 'object') {
17
19
  throw opts.errorComposer.invalidTestRunnerProperty(true);
18
20
  }
@@ -31,6 +33,7 @@ function composeRunnerConfig(opts) {
31
33
  inspectBrk: inspectBrkHookDefault,
32
34
  jest: {
33
35
  initTimeout: 300000,
36
+ retryAfterCircusRetries: false,
34
37
  reportSpecs: undefined,
35
38
  reportWorkerAssign: true,
36
39
  },
@@ -60,6 +63,51 @@ function composeRunnerConfig(opts) {
60
63
  return merged;
61
64
  }
62
65
 
66
+ function adaptLegacyRunnerConfig(globalConfig) {
67
+ let isLegacy = false;
68
+
69
+ const runnerConfigKey = 'runnerConfig' in globalConfig ? 'runnerConfig' : 'runner-config';
70
+ if (_.isString(globalConfig[runnerConfigKey])) {
71
+ isLegacy = true;
72
+ log.warn(`Detected a deprecated "${runnerConfigKey}" property (string).`);
73
+ }
74
+
75
+ const testRunnerKey = 'testRunner' in globalConfig ? 'testRunner' : 'test-runner';
76
+ if (_.isString(globalConfig[testRunnerKey])) {
77
+ isLegacy = true;
78
+ log.warn(`Detected a deprecated "${testRunnerKey}" property (string).`);
79
+ }
80
+
81
+ if (globalConfig.specs != null) {
82
+ isLegacy = true;
83
+ log.warn(`Detected a deprecated "specs" property.`);
84
+ }
85
+
86
+ if (!isLegacy) {
87
+ return globalConfig.testRunner;
88
+ }
89
+
90
+ log.warn(`Please migrate your Detox config according to the guide: [TODO: insert the migration guilde link]`);
91
+ const testRunner = globalConfig[testRunnerKey];
92
+ const runnerConfig = globalConfig[runnerConfigKey];
93
+ const specs = globalConfig.specs != null ? String(globalConfig.specs) : undefined;
94
+
95
+ const args = {};
96
+ if (_.isString(testRunner)) {
97
+ args.$0 = testRunner;
98
+ }
99
+
100
+ if (_.isString(runnerConfig)) {
101
+ args.config = runnerConfig;
102
+ }
103
+
104
+ if (specs) {
105
+ args._ = [specs];
106
+ }
107
+
108
+ return { args };
109
+ }
110
+
63
111
  function hasEmptyPositionalArgs(value, key) {
64
112
  return key === '_' ? _.isEmpty(value) : false;
65
113
  }
@@ -105,16 +105,17 @@ async function composeDetoxConfig({
105
105
  });
106
106
 
107
107
  const result = {
108
- appsConfig,
109
- artifactsConfig,
110
- behaviorConfig,
111
- cliConfig,
112
108
  configurationName,
113
- deviceConfig,
114
109
  errorComposer,
115
- loggerConfig,
116
- runnerConfig,
117
- sessionConfig,
110
+
111
+ apps: appsConfig,
112
+ artifacts: artifactsConfig,
113
+ behavior: behaviorConfig,
114
+ cli: cliConfig,
115
+ device: deviceConfig,
116
+ logger: loggerConfig,
117
+ testRunner: runnerConfig,
118
+ session: sessionConfig,
118
119
  };
119
120
 
120
121
  return result;
@@ -656,12 +656,14 @@ Examine your Detox config${this._atPath()}`,
656
656
  }
657
657
 
658
658
  invalidTestRunnerProperty(isGlobal) {
659
+ const testRunner = _.get(this.contents, ['testRunner']);
660
+
659
661
  return new DetoxConfigError({
660
- message: `testRunner should be an object, not a string`,
662
+ message: `testRunner should be an object, not a ${typeof testRunner}`,
661
663
  hint: `Check that in your Detox config${this._atPath()}`,
662
664
  inspectOptions: { depth: isGlobal ? 0 : 3 },
663
665
  debugInfo: isGlobal ? {
664
- testRunner: _.get(this.contents, ['testRunner']),
666
+ testRunner,
665
667
  ...this.contents,
666
668
  } : {
667
669
  ...this._focusOnConfiguration(c => _.pick(c, ['testRunner'])),
@@ -53,9 +53,10 @@ class IPCClient {
53
53
 
54
54
  /**
55
55
  * @param {string[]} testFilePaths
56
+ * @param {Boolean} permanent
56
57
  */
57
- async reportFailedTests(testFilePaths) {
58
- await this._emit('failedTests', { testFilePaths });
58
+ async reportFailedTests(testFilePaths, permanent) {
59
+ await this._emit('failedTests', { testFilePaths, permanent });
59
60
  }
60
61
 
61
62
  async _connectToServer() {
@@ -57,6 +57,8 @@ class IPCServer {
57
57
 
58
58
  this._ipc.server.emit(socket, 'registerContextDone', {
59
59
  failedTestFiles: this._sessionState.failedTestFiles,
60
+ testFilesToRetry: this._sessionState.testFilesToRetry,
61
+ testSessionIndex: this._sessionState.testSessionIndex,
60
62
  });
61
63
  }
62
64
 
@@ -71,8 +73,12 @@ class IPCServer {
71
73
  }
72
74
  }
73
75
 
74
- onFailedTests({ testFilePaths }, socket) {
75
- this._sessionState.failedTestFiles.push(...testFilePaths);
76
+ onFailedTests({ testFilePaths, permanent }, socket = null) {
77
+ if (permanent) {
78
+ this._sessionState.failedTestFiles.push(...testFilePaths);
79
+ } else {
80
+ this._sessionState.testFilesToRetry.push(...testFilePaths);
81
+ }
76
82
 
77
83
  if (socket) {
78
84
  this._ipc.server.emit(socket, 'failedTestsDone', {});
@@ -80,6 +86,7 @@ class IPCServer {
80
86
 
81
87
  this._ipc.server.broadcast('sessionStateUpdate', {
82
88
  failedTestFiles: this._sessionState.failedTestFiles,
89
+ testFilesToRetry: this._sessionState.testFilesToRetry,
83
90
  });
84
91
  }
85
92
  }
package/src/ipc/state.js CHANGED
@@ -44,6 +44,8 @@ class SecondarySessionState extends SessionState {
44
44
  detoxConfig = null,
45
45
  detoxIPCServer = '',
46
46
  failedTestFiles = [],
47
+ testFilesToRetry = [],
48
+ testSessionIndex = 0,
47
49
  workerId = undefined,
48
50
  workersCount = 0
49
51
  }) {
@@ -54,18 +56,29 @@ class SecondarySessionState extends SessionState {
54
56
  this.detoxConfig = detoxConfig;
55
57
  this.detoxIPCServer = detoxIPCServer;
56
58
  this.failedTestFiles = failedTestFiles;
59
+ this.testFilesToRetry = testFilesToRetry;
60
+ this.testSessionIndex = testSessionIndex;
57
61
  this.workerId = workerId;
58
62
  this.workersCount = workersCount;
59
63
  }
60
64
  }
61
65
 
62
66
  class PrimarySessionState extends SecondarySessionState {
63
- constructor({ contexts = [], failedTestFiles = [], logFiles = [], ...baseOpts }) {
67
+ constructor({
68
+ contexts = [],
69
+ logFiles = [],
70
+ failedTestFiles = [],
71
+ testFilesToRetry = [],
72
+ testSessionIndex = 0,
73
+ ...baseOpts
74
+ }) {
64
75
  super(baseOpts);
65
76
 
66
77
  this.contexts = contexts;
67
78
  this.failedTestFiles = failedTestFiles;
68
79
  this.logFiles = logFiles;
80
+ this.testSessionIndex = testSessionIndex;
81
+ this.testFilesToRetry = testFilesToRetry;
69
82
  }
70
83
  }
71
84
 
@@ -27,7 +27,7 @@ class DetoxContext {
27
27
  this[$sessionState] = this[$restoreSessionState]();
28
28
 
29
29
  const loggerConfig = this[$sessionState].detoxConfig
30
- ? this[$sessionState].detoxConfig.loggerConfig
30
+ ? this[$sessionState].detoxConfig.logger
31
31
  : undefined;
32
32
 
33
33
  /**
@@ -93,7 +93,7 @@ class DetoxContext {
93
93
  [symbols.config] = funpermaproxy(() => this[symbols.session].detoxConfig);
94
94
  [symbols.session] = funpermaproxy(() => this[$sessionState]);
95
95
  /** @abstract */
96
- [symbols.reportFailedTests](_testFilePaths) {}
96
+ [symbols.reportFailedTests](_testFilePaths, _permanent) {}
97
97
  /**
98
98
  * @abstract
99
99
  * @param {Partial<DetoxInternals.DetoxGlobalSetupOptions>} _opts
@@ -34,9 +34,9 @@ class DetoxPrimaryContext extends DetoxContext {
34
34
  }
35
35
 
36
36
  //#region Internal members
37
- async [symbols.reportFailedTests](testFilePaths) {
37
+ async [symbols.reportFailedTests](testFilePaths, permanent = false) {
38
38
  if (this[_ipcServer]) {
39
- this[_ipcServer].onFailedTests({ testFilePaths });
39
+ this[_ipcServer].onFailedTests({ testFilePaths, permanent });
40
40
  }
41
41
  }
42
42
 
@@ -65,7 +65,12 @@ class DetoxPrimaryContext extends DetoxContext {
65
65
  this[_dirty] = true;
66
66
  const detoxConfig = await this[symbols.resolveConfig](opts);
67
67
 
68
- const { behaviorConfig, deviceConfig, loggerConfig, sessionConfig } = detoxConfig;
68
+ const {
69
+ behavior: behaviorConfig,
70
+ device: deviceConfig,
71
+ logger: loggerConfig,
72
+ session: sessionConfig
73
+ } = detoxConfig;
69
74
  await this[$logger].setConfig(loggerConfig);
70
75
 
71
76
  this.trace.begin({
@@ -119,7 +124,7 @@ class DetoxPrimaryContext extends DetoxContext {
119
124
  * @override
120
125
  * @param {Partial<DetoxInternals.DetoxConfigurationSetupOptions>} [opts]
121
126
  */
122
- async [symbols.setup](opts) {
127
+ async [symbols.setup](opts = {}) {
123
128
  const workerId = opts.workerId || 1;
124
129
  this[$sessionState].workerId = workerId;
125
130
  this[_ipcServer].onRegisterWorker({ workerId });
@@ -177,7 +182,7 @@ class DetoxPrimaryContext extends DetoxContext {
177
182
  }
178
183
 
179
184
  const streamUtils = require('../utils/streamUtils');
180
- const { rootDir, plugins } = this[symbols.config].artifactsConfig || {};
185
+ const { rootDir, plugins } = this[symbols.config].artifacts || {};
181
186
  const logConfig = plugins && plugins.log || 'none';
182
187
  const enabled = rootDir && (typeof logConfig === 'string' ? logConfig !== 'none' : logConfig.enabled);
183
188
 
@@ -204,7 +209,7 @@ class DetoxPrimaryContext extends DetoxContext {
204
209
  async[_resetLockFile]() {
205
210
  const DeviceRegistry = require('../devices/DeviceRegistry');
206
211
 
207
- const deviceType = this[symbols.config].deviceConfig.type;
212
+ const deviceType = this[symbols.config].device.type;
208
213
 
209
214
  switch (deviceType) {
210
215
  case 'ios.none':
@@ -8,22 +8,28 @@ const DetoxContext = require('./DetoxContext');
8
8
 
9
9
  const { $logger, $restoreSessionState, $sessionState } = DetoxContext.protected;
10
10
  const _ipcClient = Symbol('ipcClient');
11
+ const _shortLifecycle = Symbol('shortLifecycle');
11
12
 
12
13
  class DetoxSecondaryContext extends DetoxContext {
13
14
  constructor() {
14
15
  super();
15
16
 
16
17
  /**
17
- * @protected
18
- * @type {*}
18
+ * @private
19
+ * @type {import('../ipc/IPCClient')}
19
20
  */
20
21
  this[_ipcClient] = null;
22
+ /**
23
+ * @private
24
+ * @type {undefined | boolean}
25
+ */
26
+ this[_shortLifecycle] = false;
21
27
  }
22
28
 
23
29
  //#region Internal members
24
- async [symbols.reportFailedTests](testFilePaths) {
30
+ async [symbols.reportFailedTests](testFilePaths, permanent = false) {
25
31
  if (this[_ipcClient]) {
26
- await this[_ipcClient].reportFailedTests(testFilePaths);
32
+ await this[_ipcClient].reportFailedTests(testFilePaths, permanent);
27
33
  } else {
28
34
  throw new DetoxInternalError('Detected an attempt to report failed tests using a non-initialized context.');
29
35
  }
@@ -34,23 +40,7 @@ class DetoxSecondaryContext extends DetoxContext {
34
40
  }
35
41
 
36
42
  /** @override */
37
- async [symbols.globalSetup](_opts = {}) {
38
- // This is a no-op function.
39
- // It is forbidden to add any logic to `globalSetup` of the secondary context.
40
- // Violating this principle will almost definitely break some flows, where it is
41
- // not guaranteed that `globalSetup` will ever get called. See UML diagrams.
42
- }
43
-
44
- /** @override */
45
- async [symbols.globalTeardown]() {
46
- // This is a no-op function.
47
- // It is forbidden to add any logic to `globalTeardown` of the secondary context.
48
- // Violating this principle will almost definitely break some flows, where it is
49
- // not guaranteed that `globalTeardown` will ever get called. See UML diagrams.
50
- }
51
-
52
- /** @override */
53
- async [symbols.setup](opts) {
43
+ async [symbols.globalSetup]() {
54
44
  const IPCClient = require('../ipc/IPCClient');
55
45
 
56
46
  this[_ipcClient] = new IPCClient({
@@ -59,8 +49,25 @@ class DetoxSecondaryContext extends DetoxContext {
59
49
  logger: this[$logger],
60
50
  });
61
51
 
62
- const workerId = opts.workerId || 1;
63
52
  await this[_ipcClient].init();
53
+ }
54
+
55
+ /** @override */
56
+ async [symbols.globalTeardown]() {
57
+ if (this[_ipcClient]) {
58
+ await this[_ipcClient].dispose();
59
+ this[_ipcClient] = null;
60
+ }
61
+ }
62
+
63
+ /** @override */
64
+ async [symbols.setup](opts = {}) {
65
+ if (!this[_ipcClient]) {
66
+ this[_shortLifecycle] = true;
67
+ await this[symbols.globalSetup]();
68
+ }
69
+
70
+ const workerId = opts.workerId || 1;
64
71
  await this[_ipcClient].registerWorker(workerId);
65
72
  await super[symbols.setup](opts);
66
73
  }
@@ -70,9 +77,8 @@ class DetoxSecondaryContext extends DetoxContext {
70
77
  try {
71
78
  await super[symbols.teardown]();
72
79
  } finally {
73
- if (this[_ipcClient]) {
74
- await this[_ipcClient].dispose();
75
- this[_ipcClient] = null;
80
+ if (this[_shortLifecycle]) {
81
+ await this[symbols.globalTeardown]();
76
82
  }
77
83
  }
78
84
  }
@@ -1 +0,0 @@
1
- cea1094d1b55bc0da25e7d50cb0ab6c46054e43bd7466923c9ec3306675d5bba
@@ -1 +0,0 @@
1
- 76e5f493a41f718bff430572aa2215675e5767827dc44f6f44dda78d64768e124c9b2078d4b1669537fa00e3f34b045eadc22168fb210cab2fbc976470431913
@@ -1 +0,0 @@
1
- 081bddb05b360d8ff578b2dafa006f38bf2e52dd7e0cd7b942764a4ecf391e86
@@ -1 +0,0 @@
1
- 616c8bf9cecb7f03331a785c32f1449672e8179c849e997e5ef9ab210d25a95e16efc618c1660757dc0da90b1e09fc90df6cf1e68a0f92a6c33f331f02d74ba2
@@ -1 +0,0 @@
1
- 4a844b7f2bf2ff0c50a742633c34e8e477e45bae50a2a0126ca2ebdbfd6ee213
@@ -1 +0,0 @@
1
- 22ccfbb1ddd925f4892bed13b9bbb5061f20a022155d0c8d79bc78c993e1d3f326c67560d92f25223d9de30d7e0432af4f09ed35db9f36282bf2b790c6d215fb