Skip to content

Commit e20a4be

Browse files
feat: add a waitBeforeRetry option to jest.retryTimes (#14738)
1 parent 8725326 commit e20a4be

File tree

10 files changed

+166
-3
lines changed

10 files changed

+166
-3
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
### Features
44

55
- `[jest-circus, jest-cli, jest-config]` Add `waitNextEventLoopTurnForUnhandledRejectionEvents` flag to minimise performance impact of correct detection of unhandled promise rejections introduced in [#14315](https://github.com/jestjs/jest/pull/14315) ([#14681](https://github.com/jestjs/jest/pull/14681))
6+
- `[jest-circus]` Add a `waitBeforeRetry` option to `jest.retryTimes` ([#14738](https://github.com/jestjs/jest/pull/14738))
67
- `[jest-config]` [**BREAKING**] Add `mts` and `cts` to default `moduleFileExtensions` config ([#14369](https://github.com/facebook/jest/pull/14369))
78
- `[jest-config]` [**BREAKING**] Update `testMatch` and `testRegex` default option for supporting `mjs`, `cjs`, `mts`, and `cts` ([#14584](https://github.com/jestjs/jest/pull/14584))
89
- `[jest-config]` Loads config file from provided path in `package.json` ([#14044](https://github.com/facebook/jest/pull/14044))

docs/JestObjectAPI.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1071,6 +1071,16 @@ test('will fail', () => {
10711071
});
10721072
```
10731073

1074+
`waitBeforeRetry` is the number of milliseconds to wait before retrying.
1075+
1076+
```js
1077+
jest.retryTimes(3, {waitBeforeRetry: 1000});
1078+
1079+
test('will fail', () => {
1080+
expect(true).toBe(false);
1081+
});
1082+
```
1083+
10741084
Returns the `jest` object for chaining.
10751085

10761086
:::caution

e2e/__tests__/__snapshots__/testRetries.test.ts.snap

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,3 +37,79 @@ exports[`Test Retries logs error(s) before retry 1`] = `
3737
PASS __tests__/logErrorsBeforeRetries.test.js
3838
✓ retryTimes set"
3939
`;
40+
41+
exports[`Test Retries wait before retry 1`] = `
42+
"LOGGING RETRY ERRORS retryTimes set
43+
RETRY 1
44+
45+
expect(received).toBeFalsy()
46+
47+
Received: true
48+
49+
15 | expect(new Date().getTime() - startTimeInSeconds).toBeGreaterThan(200);
50+
16 | } else {
51+
> 17 | expect(true).toBeFalsy();
52+
| ^
53+
18 | }
54+
19 | });
55+
20 |
56+
57+
at Object.toBeFalsy (__tests__/waitBeforeRetry.test.js:17:18)
58+
59+
RETRY 2
60+
61+
expect(received).toBeFalsy()
62+
63+
Received: true
64+
65+
15 | expect(new Date().getTime() - startTimeInSeconds).toBeGreaterThan(200);
66+
16 | } else {
67+
> 17 | expect(true).toBeFalsy();
68+
| ^
69+
18 | }
70+
19 | });
71+
20 |
72+
73+
at Object.toBeFalsy (__tests__/waitBeforeRetry.test.js:17:18)
74+
75+
PASS __tests__/waitBeforeRetry.test.js
76+
✓ retryTimes set"
77+
`;
78+
79+
exports[`Test Retries wait before retry with fake timers 1`] = `
80+
"LOGGING RETRY ERRORS retryTimes set with fake timers
81+
RETRY 1
82+
83+
expect(received).toBeFalsy()
84+
85+
Received: true
86+
87+
16 | expect(new Date().getTime() - startTimeInSeconds).toBeGreaterThan(200);
88+
17 | } else {
89+
> 18 | expect(true).toBeFalsy();
90+
| ^
91+
19 | jest.runAllTimers();
92+
20 | }
93+
21 | });
94+
95+
at Object.toBeFalsy (__tests__/waitBeforeRetryFakeTimers.test.js:18:18)
96+
97+
RETRY 2
98+
99+
expect(received).toBeFalsy()
100+
101+
Received: true
102+
103+
16 | expect(new Date().getTime() - startTimeInSeconds).toBeGreaterThan(200);
104+
17 | } else {
105+
> 18 | expect(true).toBeFalsy();
106+
| ^
107+
19 | jest.runAllTimers();
108+
20 | }
109+
21 | });
110+
111+
at Object.toBeFalsy (__tests__/waitBeforeRetryFakeTimers.test.js:18:18)
112+
113+
PASS __tests__/waitBeforeRetryFakeTimers.test.js
114+
✓ retryTimes set with fake timers"
115+
`;

e2e/__tests__/testRetries.test.ts

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,24 @@ describe('Test Retries', () => {
4242
expect(extractSummary(result.stderr).rest).toMatchSnapshot();
4343
});
4444

45+
it('wait before retry', () => {
46+
const result = runJest('test-retries', ['waitBeforeRetry.test.js']);
47+
expect(result.exitCode).toBe(0);
48+
expect(result.failed).toBe(false);
49+
expect(result.stderr).toContain(logErrorsBeforeRetryErrorMessage);
50+
expect(extractSummary(result.stderr).rest).toMatchSnapshot();
51+
});
52+
53+
it('wait before retry with fake timers', () => {
54+
const result = runJest('test-retries', [
55+
'waitBeforeRetryFakeTimers.test.js',
56+
]);
57+
expect(result.exitCode).toBe(0);
58+
expect(result.failed).toBe(false);
59+
expect(result.stderr).toContain(logErrorsBeforeRetryErrorMessage);
60+
expect(extractSummary(result.stderr).rest).toMatchSnapshot();
61+
});
62+
4563
it('reporter shows more than 1 invocation if test is retried', () => {
4664
let jsonResult;
4765

@@ -54,7 +72,7 @@ describe('Test Retries', () => {
5472
runJest('test-retries', [
5573
'--config',
5674
JSON.stringify(reporterConfig),
57-
'retry.test.js',
75+
'__tests__/retry.test.js',
5876
]);
5977

6078
const testOutput = fs.readFileSync(outputFilePath, 'utf8');
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
'use strict';
8+
9+
let i = 0;
10+
const startTimeInSeconds = new Date().getTime();
11+
jest.retryTimes(3, {logErrorsBeforeRetry: true, waitBeforeRetry: 100});
12+
it('retryTimes set', () => {
13+
i++;
14+
if (i === 3) {
15+
expect(new Date().getTime() - startTimeInSeconds).toBeGreaterThan(200);
16+
} else {
17+
expect(true).toBeFalsy();
18+
}
19+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* Copyright (c) Meta Platforms, Inc. and affiliates.
3+
*
4+
* This source code is licensed under the MIT license found in the
5+
* LICENSE file in the root directory of this source tree.
6+
*/
7+
'use strict';
8+
9+
let i = 0;
10+
const startTimeInSeconds = new Date().getTime();
11+
jest.retryTimes(3, {logErrorsBeforeRetry: true, waitBeforeRetry: 100});
12+
it('retryTimes set with fake timers', () => {
13+
jest.useFakeTimers();
14+
i++;
15+
if (i === 3) {
16+
expect(new Date().getTime() - startTimeInSeconds).toBeGreaterThan(200);
17+
} else {
18+
expect(true).toBeFalsy();
19+
jest.runAllTimers();
20+
}
21+
});

packages/jest-circus/src/run.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ import shuffleArray, {
1515
rngBuilder,
1616
} from './shuffleArray';
1717
import {dispatch, getState} from './state';
18-
import {RETRY_TIMES} from './types';
18+
import {RETRY_TIMES, WAIT_BEFORE_RETRY} from './types';
1919
import {
2020
callAsyncCircusFn,
2121
getAllHooksForDescribe,
@@ -24,6 +24,10 @@ import {
2424
makeRunResult,
2525
} from './utils';
2626

27+
// Global values can be overwritten by mocks or tests. We'll capture
28+
// the original values in the variables before we require any files.
29+
const {setTimeout} = globalThis;
30+
2731
type ConcurrentTestEntry = Omit<Circus.TestEntry, 'fn'> & {
2832
fn: Circus.ConcurrentTestFn;
2933
};
@@ -67,6 +71,10 @@ const _runTestsForDescribeBlock = async (
6771
const retryTimes =
6872
// eslint-disable-next-line no-restricted-globals
6973
parseInt((global as Global.Global)[RETRY_TIMES] as string, 10) || 0;
74+
75+
const waitBeforeRetry =
76+
// eslint-disable-next-line no-restricted-globals
77+
parseInt((global as Global.Global)[WAIT_BEFORE_RETRY] as string, 10) || 0;
7078
const deferredRetryTests = [];
7179

7280
if (rng) {
@@ -102,6 +110,10 @@ const _runTestsForDescribeBlock = async (
102110
// Clear errors so retries occur
103111
await dispatch({name: 'test_retry', test});
104112

113+
if (waitBeforeRetry > 0) {
114+
await new Promise(resolve => setTimeout(resolve, waitBeforeRetry));
115+
}
116+
105117
await _runTest(test, isSkipped);
106118
numRetriesAvailable--;
107119
}

packages/jest-circus/src/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
export const STATE_SYM = Symbol('JEST_STATE_SYMBOL');
99
export const RETRY_TIMES = Symbol.for('RETRY_TIMES');
10+
export const WAIT_BEFORE_RETRY = Symbol.for('WAIT_BEFORE_RETRY');
1011
// To pass this value from Runtime object to state we need to use global[sym]
1112
export const TEST_TIMEOUT_SYMBOL = Symbol.for('TEST_TIMEOUT_SYMBOL');
1213
export const LOG_ERRORS_BEFORE_RETRY = Symbol.for('LOG_ERRORS_BEFORE_RETRY');

packages/jest-environment/src/index.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,12 +298,14 @@ export interface Jest {
298298
* the test to fail to the console, providing visibility on why a retry occurred.
299299
* retries is exhausted.
300300
*
301+
* `waitBeforeRetry` is the number of milliseconds to wait before retrying
302+
*
301303
* @remarks
302304
* Only available with `jest-circus` runner.
303305
*/
304306
retryTimes(
305307
numRetries: number,
306-
options?: {logErrorsBeforeRetry?: boolean},
308+
options?: {logErrorsBeforeRetry?: boolean; waitBeforeRetry?: number},
307309
): Jest;
308310
/**
309311
* Exhausts tasks queued by `setImmediate()`.

packages/jest-runtime/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,7 @@ type ResolveOptions = Parameters<typeof require.resolve>[1] & {
122122

123123
const testTimeoutSymbol = Symbol.for('TEST_TIMEOUT_SYMBOL');
124124
const retryTimesSymbol = Symbol.for('RETRY_TIMES');
125+
const waitBeforeRetrySymbol = Symbol.for('WAIT_BEFORE_RETRY');
125126
const logErrorsBeforeRetrySymbol = Symbol.for('LOG_ERRORS_BEFORE_RETRY');
126127

127128
const NODE_MODULES = `${path.sep}node_modules${path.sep}`;
@@ -2265,6 +2266,8 @@ export default class Runtime {
22652266
this._environment.global[retryTimesSymbol] = numTestRetries;
22662267
this._environment.global[logErrorsBeforeRetrySymbol] =
22672268
options?.logErrorsBeforeRetry;
2269+
this._environment.global[waitBeforeRetrySymbol] =
2270+
options?.waitBeforeRetry;
22682271

22692272
return jestObject;
22702273
};

0 commit comments

Comments
 (0)