diff --git a/CHANGELOG.md b/CHANGELOG.md index 102e9f3337b0..e575da0d2e14 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,8 +2,11 @@ ### Features +- `[@jest/cli, jest-config]` A seed for the test run will be randomly generated, or set by a CLI option ([#13400](https://github.com/facebook/jest/pull/13400)) +- `[@jest/cli, jest-config]` `--show-seed` will display the seed value in the report, and can be set via a CLI flag or through the config file ([#13400](https://github.com/facebook/jest/pull/13400)) - `[jest-config]` Add `readInitialConfig` utility function ([#13356](https://github.com/facebook/jest/pull/13356)) - `[jest-core]` Enable testResultsProcessor to be async ([#13343](https://github.com/facebook/jest/pull/13343)) +- `[@jest/environment, jest-environment-node, jest-environment-jsdom, jest-runtime]` Add `getSeed()` to the `jest` object ([#13400](https://github.com/facebook/jest/pull/13400)) - `[expect, @jest/expect-utils]` Allow `isA` utility to take a type argument ([#13355](https://github.com/facebook/jest/pull/13355)) - `[expect]` Expose `AsyncExpectationResult` and `SyncExpectationResult` types ([#13411](https://github.com/facebook/jest/pull/13411)) diff --git a/docs/CLI.md b/docs/CLI.md index 75eaa78a052f..57c37d248985 100644 --- a/docs/CLI.md +++ b/docs/CLI.md @@ -350,6 +350,20 @@ The default regex matching works fine on small runs, but becomes slow if provide ::: +### `--seed=` + +Sets a seed value that can be retrieved in a test file via [`jest.getSeed()`](JestObjectAPI.md#jestgetseed). The seed value must be between `-0x80000000` and `0x7fffffff` inclusive (`-2147483648` (`-(2 ** 31)`) and `2147483647` (`2 ** 31 - 1`) in decimal). + +```bash +jest --seed=1324 +``` + +:::tip + +If this option is not specified Jest will randomly generate the value. You can use the [`--showSeed`](#--showseed) flag to print the seed in the test report summary. + +::: + ### `--selectProjects ... ` Run the tests of the specified projects. Jest uses the attribute `displayName` in the configuration to identify each project. If you use this option, you should provide a `displayName` to all your projects. @@ -380,6 +394,12 @@ jest --shard=3/3 Print your Jest config and then exits. +### `--showSeed` + +Prints the seed value in the test report summary. See [`--seed=`](#--seednum) for the details. + +Can also be set in configuration. See [`showSeed`](Configuration.md#showseed-boolean). + ### `--silent` Prevent tests from printing messages through the console. diff --git a/docs/Configuration.md b/docs/Configuration.md index 5ecbeef85358..2b9f4f0a28c1 100644 --- a/docs/Configuration.md +++ b/docs/Configuration.md @@ -1614,6 +1614,12 @@ const config: Config = { export default config; ``` +### `showSeed` \[boolean] + +Default: `false` + +The equivalent of the [`--showSeed`](CLI.md#--showseed) flag to print the seed in the test report summary. + ### `slowTestThreshold` \[number] Default: `5` diff --git a/docs/JestObjectAPI.md b/docs/JestObjectAPI.md index d91ed7d768e2..6314b7f0797c 100644 --- a/docs/JestObjectAPI.md +++ b/docs/JestObjectAPI.md @@ -908,6 +908,16 @@ This function is not available when using legacy fake timers implementation. ## Misc +### `jest.getSeed()` + +Every time Jest runs a seed value is randomly generated which you could use in a pseudorandom number generator or anywhere else. + +:::tip + +Use the [`--showSeed`](CLI.md#--showseed) flag to print the seed in the test report summary. To manually set the value of the seed use [`--seed=`](CLI.md#--seednum) CLI argument. + +::: + ### `jest.setTimeout(timeout)` Set the default timeout interval (in milliseconds) for all tests and before/after hooks in the test file. This only affects the test file from which this function is called. The default timeout interval is 5 seconds if this method is not called. diff --git a/e2e/Utils.ts b/e2e/Utils.ts index 112f7412b181..f46860eb286e 100644 --- a/e2e/Utils.ts +++ b/e2e/Utils.ts @@ -157,6 +157,9 @@ export const copyDir = (src: string, dest: string) => { } }; +export const replaceSeed = (str: string) => + str.replace(/Seed: {8}(-?\d+)/g, 'Seed: <>'); + export const replaceTime = (str: string) => str .replace(/\d*\.?\d+ m?s\b/g, '<>') @@ -201,7 +204,7 @@ export const extractSummary = (stdout: string) => { const match = stdout .replace(/(?:\\[rn])+/g, '\n') .match( - /Test Suites:.*\nTests.*\nSnapshots.*\nTime.*(\nRan all test suites)*.*\n*$/gm, + /(Seed:.*\n)?Test Suites:.*\nTests.*\nSnapshots.*\nTime.*(\nRan all test suites)*.*\n*$/gm, ); if (!match) { throw new Error(dedent` @@ -254,7 +257,7 @@ export const extractSummaries = ( stdout: string, ): Array<{rest: string; summary: string}> => { const regex = - /Test Suites:.*\nTests.*\nSnapshots.*\nTime.*(\nRan all test suites)*.*\n*$/gm; + /(Seed:.*\n)?Test Suites:.*\nTests.*\nSnapshots.*\nTime.*(\nRan all test suites)*.*\n*$/gm; let match = regex.exec(stdout); const matches: Array = []; diff --git a/e2e/__tests__/__snapshots__/showConfig.test.ts.snap b/e2e/__tests__/__snapshots__/showConfig.test.ts.snap index 5bedd1a8bd54..79a0e38b919b 100644 --- a/e2e/__tests__/__snapshots__/showConfig.test.ts.snap +++ b/e2e/__tests__/__snapshots__/showConfig.test.ts.snap @@ -125,6 +125,7 @@ exports[`--showConfig outputs config info and exits 1`] = ` "projects": [], "rootDir": "<>", "runTestsByPath": false, + "seed": <>, "skipFilter": false, "snapshotFormat": { "escapeString": false, diff --git a/e2e/__tests__/jestObject.test.ts b/e2e/__tests__/jestObject.test.ts new file mode 100644 index 000000000000..0818da27bc9f --- /dev/null +++ b/e2e/__tests__/jestObject.test.ts @@ -0,0 +1,26 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as path from 'path'; +import runJest from '../runJest'; + +const dir = path.resolve(__dirname, '../jest-object'); + +test('passes with seed', () => { + const result = runJest(dir, ['get-seed.test.js', '--seed', '1234']); + expect(result.exitCode).toBe(0); +}); + +test('fails with wrong seed', () => { + const result = runJest(dir, ['get-seed.test.js', '--seed', '1111']); + expect(result.exitCode).toBe(1); +}); + +test('seed always exists', () => { + const result = runJest(dir, ['any-seed.test.js']); + expect(result.exitCode).toBe(0); +}); diff --git a/e2e/__tests__/showConfig.test.ts b/e2e/__tests__/showConfig.test.ts index 41570e07e1da..13be5474965d 100644 --- a/e2e/__tests__/showConfig.test.ts +++ b/e2e/__tests__/showConfig.test.ts @@ -37,7 +37,8 @@ test('--showConfig outputs config info and exits', () => { .replace(/"version": "(.+)"/g, '"version": "[version]"') .replace(/"maxWorkers": (\d+)/g, '"maxWorkers": "[maxWorkers]"') .replace(/"\S*show-config-test/gm, '"<>') - .replace(/"\S*\/jest\/packages/gm, '"<>'); + .replace(/"\S*\/jest\/packages/gm, '"<>') + .replace(/"seed": (-?\d+)/g, '"seed": <>'); expect(stdout).toMatchSnapshot(); }); diff --git a/e2e/__tests__/showSeed.test.ts b/e2e/__tests__/showSeed.test.ts new file mode 100644 index 000000000000..d7574e166945 --- /dev/null +++ b/e2e/__tests__/showSeed.test.ts @@ -0,0 +1,52 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import * as path from 'path'; +import {extractSummary, replaceSeed} from '../Utils'; +import runJest from '../runJest'; + +const dir = path.resolve(__dirname, '../jest-object'); + +const randomSeedValueRegExp = /Seed:\s+<>/; +const seedValueRegExp = /Seed:\s+1234/; + +test('--showSeed changes report to output seed', () => { + const {stderr} = runJest(dir, ['--showSeed', '--no-cache']); + + const {summary} = extractSummary(stderr); + + expect(replaceSeed(summary)).toMatch(randomSeedValueRegExp); +}); + +test('if --showSeed is not present the report will not show the seed', () => { + const {stderr} = runJest(dir, ['--seed', '1234']); + + const {summary} = extractSummary(stderr); + + expect(replaceSeed(summary)).not.toMatch(randomSeedValueRegExp); +}); + +test('if showSeed is present in the config the report will show the seed', () => { + const {stderr} = runJest(dir, [ + '--seed', + '1234', + '--config', + 'different-config.json', + ]); + + const {summary} = extractSummary(stderr); + + expect(summary).toMatch(seedValueRegExp); +}); + +test('--seed --showSeed will show the seed in the report', () => { + const {stderr} = runJest(dir, ['--showSeed', '--seed', '1234']); + + const {summary} = extractSummary(stderr); + + expect(summary).toMatch(seedValueRegExp); +}); diff --git a/e2e/jest-object/__tests__/any-seed.test.js b/e2e/jest-object/__tests__/any-seed.test.js new file mode 100644 index 000000000000..89dd98360471 --- /dev/null +++ b/e2e/jest-object/__tests__/any-seed.test.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +test('ensure seed exists', () => { + expect(jest.getSeed()).toEqual(expect.any(Number)); +}); diff --git a/e2e/jest-object/__tests__/get-seed.test.js b/e2e/jest-object/__tests__/get-seed.test.js new file mode 100644 index 000000000000..2055f0223cf5 --- /dev/null +++ b/e2e/jest-object/__tests__/get-seed.test.js @@ -0,0 +1,10 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +test('getSeed', () => { + expect(jest.getSeed()).toBe(1234); +}); diff --git a/e2e/jest-object/different-config.json b/e2e/jest-object/different-config.json new file mode 100644 index 000000000000..ae9b2166eaff --- /dev/null +++ b/e2e/jest-object/different-config.json @@ -0,0 +1,4 @@ +{ + "displayName": "Config from different-config.json file", + "showSeed": true +} diff --git a/e2e/jest-object/package.json b/e2e/jest-object/package.json new file mode 100644 index 000000000000..148788b25446 --- /dev/null +++ b/e2e/jest-object/package.json @@ -0,0 +1,5 @@ +{ + "jest": { + "testEnvironment": "node" + } +} diff --git a/packages/jest-cli/src/args.ts b/packages/jest-cli/src/args.ts index f58194047977..1ed7606b37a1 100644 --- a/packages/jest-cli/src/args.ts +++ b/packages/jest-cli/src/args.ts @@ -510,6 +510,11 @@ export const options: {[key: string]: Options} = { "Allows to use a custom runner instead of Jest's default test runner.", type: 'string', }, + seed: { + description: + 'Sets a seed value that can be retrieved in a tests file via `jest.getSeed()`. If this option is not specified Jest will randomly generate the value. The seed value must be between `-0x80000000` and `0x7fffffff` inclusive.', + type: 'number', + }, selectProjects: { description: 'Run the tests of the specified projects. ' + @@ -541,6 +546,11 @@ export const options: {[key: string]: Options} = { description: 'Print your jest config and then exits.', type: 'boolean', }, + showSeed: { + description: + 'Prints the seed value in the test report summary. See `--seed` for how to set this value', + type: 'boolean', + }, silent: { description: 'Prevent tests from printing messages through the console.', type: 'boolean', diff --git a/packages/jest-config/src/ValidConfig.ts b/packages/jest-config/src/ValidConfig.ts index 1526b0cbbeb6..5239ffd8709d 100644 --- a/packages/jest-config/src/ValidConfig.ts +++ b/packages/jest-config/src/ValidConfig.ts @@ -137,6 +137,7 @@ const initialOptions: Config.InitialOptions = { sandboxInjectedGlobals: [], setupFiles: ['/setup.js'], setupFilesAfterEnv: ['/testSetupFile.js'], + showSeed: false, silent: true, skipFilter: false, skipNodeResolution: false, diff --git a/packages/jest-config/src/__tests__/normalize.test.ts b/packages/jest-config/src/__tests__/normalize.test.ts index 0bed6f9f11e1..60176360650d 100644 --- a/packages/jest-config/src/__tests__/normalize.test.ts +++ b/packages/jest-config/src/__tests__/normalize.test.ts @@ -114,14 +114,12 @@ it('keeps custom ids based on the rootDir', async () => { }); it('minimal config is stable across runs', async () => { - const firstNormalization = await normalize( - {rootDir: '/root/path/foo'}, - {} as Config.Argv, - ); - const secondNormalization = await normalize( - {rootDir: '/root/path/foo'}, - {} as Config.Argv, - ); + const firstNormalization = await normalize({rootDir: '/root/path/foo'}, { + seed: 55555, + } as Config.Argv); + const secondNormalization = await normalize({rootDir: '/root/path/foo'}, { + seed: 55555, + } as Config.Argv); expect(firstNormalization).toEqual(secondNormalization); expect(JSON.stringify(firstNormalization)).toBe( @@ -2113,3 +2111,56 @@ it('parses workerIdleMemoryLimit', async () => { expect(options.workerIdleMemoryLimit).toBe(47185920); }); + +describe('seed', () => { + it('generates seed when not specified', async () => { + const {options} = await normalize({rootDir: '/root/'}, {} as Config.Argv); + expect(options.seed).toEqual(expect.any(Number)); + }); + + it('uses seed specified', async () => { + const {options} = await normalize({rootDir: '/root/'}, { + seed: 4321, + } as Config.Argv); + expect(options.seed).toBe(4321); + }); + + it('throws if seed is too large or too small', async () => { + await expect( + normalize({rootDir: '/root/'}, { + seed: 2 ** 33, + } as Config.Argv), + ).rejects.toThrow( + 'seed value must be between `-0x80000000` and `0x7fffffff` inclusive - is 8589934592', + ); + await expect( + normalize({rootDir: '/root/'}, { + seed: -(2 ** 33), + } as Config.Argv), + ).rejects.toThrow( + 'seed value must be between `-0x80000000` and `0x7fffffff` inclusive - is -8589934592', + ); + }); +}); + +describe('showSeed', () => { + test('showSeed is set when argv flag is set', async () => { + const {options} = await normalize({rootDir: '/root/'}, { + showSeed: true, + } as Config.Argv); + expect(options.showSeed).toBe(true); + }); + + test('showSeed is set when the config is set', async () => { + const {options} = await normalize( + {rootDir: '/root/', showSeed: true}, + {} as Config.Argv, + ); + expect(options.showSeed).toBe(true); + }); + + test('showSeed is false when neither is set', async () => { + const {options} = await normalize({rootDir: '/root/'}, {} as Config.Argv); + expect(options.showSeed).toBeFalsy(); + }); +}); diff --git a/packages/jest-config/src/index.ts b/packages/jest-config/src/index.ts index f8fb20d3590a..8a8142c502be 100644 --- a/packages/jest-config/src/index.ts +++ b/packages/jest-config/src/index.ts @@ -115,7 +115,9 @@ const groupOptions = ( reporters: options.reporters, rootDir: options.rootDir, runTestsByPath: options.runTestsByPath, + seed: options.seed, shard: options.shard, + showSeed: options.showSeed, silent: options.silent, skipFilter: options.skipFilter, snapshotFormat: options.snapshotFormat, diff --git a/packages/jest-config/src/normalize.ts b/packages/jest-config/src/normalize.ts index 3d13af7e2bb7..d20a36437d1b 100644 --- a/packages/jest-config/src/normalize.ts +++ b/packages/jest-config/src/normalize.ts @@ -918,6 +918,7 @@ export default async function normalize( case 'runTestsByPath': case 'sandboxInjectedGlobals': case 'silent': + case 'showSeed': case 'skipFilter': case 'skipNodeResolution': case 'slowTestThreshold': @@ -1021,6 +1022,24 @@ export default async function normalize( newOptions.onlyChanged = newOptions.watch; } + newOptions.showSeed = newOptions.showSeed || argv.showSeed; + + const upperBoundSeedValue = 2 ** 31; + + // xoroshiro128plus is used in v8 and is used here (at time of writing) + newOptions.seed = + argv.seed ?? + Math.floor((2 ** 32 - 1) * Math.random() - upperBoundSeedValue); + if ( + newOptions.seed < -upperBoundSeedValue || + newOptions.seed > upperBoundSeedValue - 1 + ) { + throw new ValidationError( + 'Validation Error', + `seed value must be between \`-0x80000000\` and \`0x7fffffff\` inclusive - is ${newOptions.seed}`, + ); + } + if (!newOptions.onlyChanged) { newOptions.onlyChanged = false; } diff --git a/packages/jest-core/src/lib/__tests__/__snapshots__/logDebugMessages.test.ts.snap b/packages/jest-core/src/lib/__tests__/__snapshots__/logDebugMessages.test.ts.snap index b27bc6754017..663431ffd099 100644 --- a/packages/jest-core/src/lib/__tests__/__snapshots__/logDebugMessages.test.ts.snap +++ b/packages/jest-core/src/lib/__tests__/__snapshots__/logDebugMessages.test.ts.snap @@ -95,6 +95,7 @@ exports[`prints the config object 1`] = ` "reporters": [], "rootDir": "/test_root_dir/", "runTestsByPath": false, + "seed": 1234, "silent": false, "skipFilter": false, "snapshotFormat": {}, diff --git a/packages/jest-environment/src/index.ts b/packages/jest-environment/src/index.ts index 793473b1d51c..df50111e37f5 100644 --- a/packages/jest-environment/src/index.ts +++ b/packages/jest-environment/src/index.ts @@ -148,6 +148,11 @@ export interface Jest { * Not available when using legacy fake timers implementation. */ getRealSystemTime(): number; + /** + * Retrieves the seed value. It will be randomly generated for each test run + * or can be manually set via the `--seed` CLI argument. + */ + getSeed(): number; /** * Returns the number of fake timers still left to run. */ diff --git a/packages/jest-repl/src/cli/runtime-cli.ts b/packages/jest-repl/src/cli/runtime-cli.ts index 8e156a9364c4..7cfbb123f197 100644 --- a/packages/jest-repl/src/cli/runtime-cli.ts +++ b/packages/jest-repl/src/cli/runtime-cli.ts @@ -106,6 +106,7 @@ export async function run( sourcesRelatedToTestsInChangedFiles: undefined, }, filePath, + globalConfig, ); for (const path of projectConfig.setupFiles) { diff --git a/packages/jest-reporters/src/DefaultReporter.ts b/packages/jest-reporters/src/DefaultReporter.ts index c8451b3a9468..1c51ed94bfd0 100644 --- a/packages/jest-reporters/src/DefaultReporter.ts +++ b/packages/jest-reporters/src/DefaultReporter.ts @@ -47,7 +47,7 @@ export default class DefaultReporter extends BaseReporter { this._clear = ''; this._out = process.stdout.write.bind(process.stdout); this._err = process.stderr.write.bind(process.stderr); - this._status = new Status(); + this._status = new Status(globalConfig); this._bufferedOutput = new Set(); this.__wrapStdio(process.stdout); this.__wrapStdio(process.stderr); diff --git a/packages/jest-reporters/src/Status.ts b/packages/jest-reporters/src/Status.ts index f0a993f2eb96..f44c960e3ff1 100644 --- a/packages/jest-reporters/src/Status.ts +++ b/packages/jest-reporters/src/Status.ts @@ -85,7 +85,7 @@ export default class Status { private _aggregatedResults?: AggregatedResult; private _showStatus: boolean; - constructor() { + constructor(private _globalConfig: Config.GlobalConfig) { this._cache = null; this._currentTests = new CurrentTestList(); this._currentTestCases = []; @@ -184,6 +184,8 @@ export default class Status { currentTestCases: this._currentTestCases, estimatedTime: this._estimatedTime, roundTime: true, + seed: this._globalConfig.seed, + showSeed: this._globalConfig.showSeed, width, })}`; } diff --git a/packages/jest-reporters/src/SummaryReporter.ts b/packages/jest-reporters/src/SummaryReporter.ts index ea7b8a0b037a..2666812e7837 100644 --- a/packages/jest-reporters/src/SummaryReporter.ts +++ b/packages/jest-reporters/src/SummaryReporter.ts @@ -109,6 +109,8 @@ export default class SummaryReporter extends BaseReporter { if (numTotalTestSuites) { let message = getSummary(aggregatedResults, { estimatedTime: this._estimatedTime, + seed: this._globalConfig.seed, + showSeed: this._globalConfig.showSeed, }); if (!this._globalConfig.silent) { diff --git a/packages/jest-reporters/src/__tests__/__snapshots__/getSummary.test.ts.snap b/packages/jest-reporters/src/__tests__/__snapshots__/getSummary.test.ts.snap new file mode 100644 index 000000000000..00e39eced7a2 --- /dev/null +++ b/packages/jest-reporters/src/__tests__/__snapshots__/getSummary.test.ts.snap @@ -0,0 +1,16 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`getSummary does not print seed value when showSeed is false 1`] = ` +"Test Suites: 0 total +Tests: 0 total +Snapshots: 0 total +Time: 0.01 s" +`; + +exports[`getSummary does print seed value when showSeed is true 1`] = ` +"Seed: 55555 +Test Suites: 0 total +Tests: 0 total +Snapshots: 0 total +Time: 0.01 s" +`; diff --git a/packages/jest-reporters/src/__tests__/getSummary.test.ts b/packages/jest-reporters/src/__tests__/getSummary.test.ts new file mode 100644 index 000000000000..62745ef551bb --- /dev/null +++ b/packages/jest-reporters/src/__tests__/getSummary.test.ts @@ -0,0 +1,44 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +import {makeEmptyAggregatedTestResult} from '@jest/test-result'; +import getSummary from '../getSummary'; + +jest.useFakeTimers().setSystemTime(10); + +describe('getSummary', () => { + test('does not print seed value when showSeed is false', () => { + const summary = getSummary(makeEmptyAggregatedTestResult(), { + estimatedTime: 0, + showSeed: false, + }); + + expect(summary).toMatchSnapshot(); + }); + + test('does print seed value when showSeed is true', () => { + const summary = getSummary(makeEmptyAggregatedTestResult(), { + estimatedTime: 0, + seed: 55555, + showSeed: true, + }); + + expect(summary).toMatchSnapshot(); + }); + + test('throws error is showSeed is true but seed is not present', () => { + expect.assertions(1); + + try { + getSummary(makeEmptyAggregatedTestResult(), { + estimatedTime: 0, + showSeed: true, + }); + } catch (error) { + expect(error).toBeInstanceOf(Error); + } + }); +}); diff --git a/packages/jest-reporters/src/getSummary.ts b/packages/jest-reporters/src/getSummary.ts index dbb42c8d0411..2781932f5ad6 100644 --- a/packages/jest-reporters/src/getSummary.ts +++ b/packages/jest-reporters/src/getSummary.ts @@ -114,6 +114,16 @@ export default function getSummary( const testsTotal = aggregatedResults.numTotalTests; const width = (options && options.width) || 0; + const optionalLines: Array = []; + + if (options?.showSeed === true) { + const {seed} = options; + if (seed === undefined) { + throw new Error('Attempted to display seed but seed value is undefined'); + } + optionalLines.push(`${chalk.bold('Seed: ') + seed}`); + } + const suites = `${ chalk.bold('Test Suites: ') + (suitesFailed ? `${chalk.bold.red(`${suitesFailed} failed`)}, ` : '') + @@ -183,5 +193,6 @@ export default function getSummary( }${snapshotsTotal} total`; const time = renderTime(runTime, estimatedTime, width); - return [suites, tests, snapshots, time].join('\n'); + + return [...optionalLines, suites, tests, snapshots, time].join('\n'); } diff --git a/packages/jest-reporters/src/types.ts b/packages/jest-reporters/src/types.ts index 16567d30fc33..98d131b0061e 100644 --- a/packages/jest-reporters/src/types.ts +++ b/packages/jest-reporters/src/types.ts @@ -60,4 +60,6 @@ export type SummaryOptions = { estimatedTime?: number; roundTime?: boolean; width?: number; + showSeed?: boolean; + seed?: number; }; diff --git a/packages/jest-runner/src/runTest.ts b/packages/jest-runner/src/runTest.ts index cd5d613864ba..0c352b12f05e 100644 --- a/packages/jest-runner/src/runTest.ts +++ b/packages/jest-runner/src/runTest.ts @@ -196,6 +196,7 @@ async function runTestInternal( context.sourcesRelatedToTestsInChangedFiles, }, path, + globalConfig, ); let isTornDown = false; diff --git a/packages/jest-runtime/src/__mocks__/createRuntime.js b/packages/jest-runtime/src/__mocks__/createRuntime.js index bea84f2b8d38..30d1e9479006 100644 --- a/packages/jest-runtime/src/__mocks__/createRuntime.js +++ b/packages/jest-runtime/src/__mocks__/createRuntime.js @@ -55,6 +55,8 @@ module.exports = async function createRuntime(filename, projectConfig) { const moduleNameMapper = setupModuleNameMapper(projectConfig, rootDir); const transform = setupTransform(projectConfig, rootDir, cwd); + const globalConfig = makeGlobalConfig(); + projectConfig = makeProjectConfig({ cacheDirectory: getCacheDirectory(), cwd, @@ -77,7 +79,7 @@ module.exports = async function createRuntime(filename, projectConfig) { } const environment = new NodeEnvironment({ - globalConfig: makeGlobalConfig(), + globalConfig, projectConfig, }); environment.global.console = console; @@ -109,6 +111,7 @@ module.exports = async function createRuntime(filename, projectConfig) { sourcesRelatedToTestsInChangedFiles: undefined, }, filename, + globalConfig, ); for (const path of projectConfig.setupFiles) { diff --git a/packages/jest-runtime/src/index.ts b/packages/jest-runtime/src/index.ts index 5b64adeb2ea2..7be6cf2db53d 100644 --- a/packages/jest-runtime/src/index.ts +++ b/packages/jest-runtime/src/index.ts @@ -155,6 +155,7 @@ const supportsNodeColonModulePrefixInRequire = (() => { export default class Runtime { private readonly _cacheFS: Map; private readonly _config: Config.ProjectConfig; + private readonly _globalConfig?: Config.GlobalConfig; private readonly _coverageOptions: ShouldInstrumentOptions; private _currentlyExecutingModulePath: string; private readonly _environment: JestEnvironment; @@ -213,12 +214,15 @@ export default class Runtime { cacheFS: Map, coverageOptions: ShouldInstrumentOptions, testPath: string, + // TODO: make mandatory in Jest 30 + globalConfig?: Config.GlobalConfig, ) { this._cacheFS = cacheFS; this._config = config; this._coverageOptions = coverageOptions; this._currentlyExecutingModulePath = ''; this._environment = environment; + this._globalConfig = globalConfig; this._explicitShouldMock = new Map(); this._explicitShouldMockModule = new Map(); this._internalModuleRegistry = new Map(); @@ -2123,6 +2127,15 @@ export default class Runtime { ); } }, + getSeed: () => { + // TODO: remove this check in Jest 30 + if (this._globalConfig?.seed === undefined) { + throw new Error( + 'The seed value is not available. Likely you are using older versions of the jest dependencies.', + ); + } + return this._globalConfig.seed; + }, getTimerCount: () => _getFakeTimers().getTimerCount(), isMockFunction: this._moduleMocker.isMockFunction, isolateModules, diff --git a/packages/jest-types/__typetests__/jest.test.ts b/packages/jest-types/__typetests__/jest.test.ts index 4b225aeb8b6e..e6a9cc895e1f 100644 --- a/packages/jest-types/__typetests__/jest.test.ts +++ b/packages/jest-types/__typetests__/jest.test.ts @@ -534,3 +534,6 @@ expectError(jest.retryTimes()); expectType(jest.setTimeout(6000)); expectError(jest.setTimeout()); + +expectType(jest.getSeed()); +expectError(jest.getSeed(123)); diff --git a/packages/jest-types/src/Config.ts b/packages/jest-types/src/Config.ts index e331247ab634..c8cfebd40625 100644 --- a/packages/jest-types/src/Config.ts +++ b/packages/jest-types/src/Config.ts @@ -291,6 +291,7 @@ export type InitialOptions = Partial<{ sandboxInjectedGlobals: Array; setupFiles: Array; setupFilesAfterEnv: Array; + showSeed: boolean; silent: boolean; skipFilter: boolean; skipNodeResolution: boolean; @@ -394,6 +395,8 @@ export type GlobalConfig = { reporters?: Array; runTestsByPath: boolean; rootDir: string; + seed: number; + showSeed?: boolean; shard?: ShardConfig; silent?: boolean; skipFilter: boolean; @@ -537,6 +540,8 @@ export type Argv = Arguments< rootDir: string; roots: Array; runInBand: boolean; + seed: number; + showSeed: boolean; selectProjects: Array; setupFiles: Array; setupFilesAfterEnv: Array; diff --git a/packages/test-utils/src/config.ts b/packages/test-utils/src/config.ts index 92e65d53e91b..adb637b4b9c9 100644 --- a/packages/test-utils/src/config.ts +++ b/packages/test-utils/src/config.ts @@ -47,6 +47,7 @@ const DEFAULT_GLOBAL_CONFIG: Config.GlobalConfig = { reporters: [], rootDir: '/test_root_dir/', runTestsByPath: false, + seed: 1234, silent: false, skipFilter: false, snapshotFormat: {},