Skip to content

Instantly share code, notes, and snippets.

@mdubourg001
Last active January 28, 2026 11:02
Show Gist options
  • Select an option

  • Save mdubourg001/982493993ce6cbefd061772450dfeb8c to your computer and use it in GitHub Desktop.

Select an option

Save mdubourg001/982493993ce6cbefd061772450dfeb8c to your computer and use it in GitHub Desktop.
Vitest tests file execution order reporter (for tests dependencies and shared-state identification)
import {
SerializedError,
TestModule,
TestRunEndReason,
TestSpecification,
} from 'vitest/node';
import { Reporter } from 'vitest/reporters';
interface TestFilesOrderReporterConfig {}
const DEFAULT_CONFIG: TestFilesOrderReporterConfig = {};
/**
* This reporter helps identify test files order dependencies by reporting the order in which test files are executed.
* It also provides guidance on how to bisect the test files to identify potential order dependencies.
*/
export class TestFilesOrderReporter implements Reporter {
private config: TestFilesOrderReporterConfig;
private seed: number | undefined = undefined;
constructor(config: TestFilesOrderReporterConfig = DEFAULT_CONFIG) {
this.config = config;
}
onInit(): void {
console.log(
`This reporter helps identify test files order dependencies by reporting the order in which test files are executed, and helping you bisecting the files that cause the failure.
For predictible results, make sure to run this reporter with the following options:
- --isolate=false
- --sequence.shuffle.files
- --fileParallelism=false
- --bail=1\n`,
);
}
onTestRunStart(specifications: readonly TestSpecification[]) {
this.seed = specifications[0]?.project.globalConfig.sequence.seed;
if (this.seed) {
console.log(`Running tests with seed "${this.seed}"\n\n`);
}
}
onTestRunEnd(
testModules: ReadonlyArray<TestModule>,
unhandledErrors: ReadonlyArray<SerializedError>,
reason: TestRunEndReason,
): void {
const modulesOrder = testModules
.filter((module) => module.task!.result.state !== 'skip')
.map((module) => module.relativeModuleId);
console.log(
`SEED: ${this.seed}\n${reason !== 'passed' ? `FAILING: ${modulesOrder.at(-1)}\n` : ''}
Test files execution order, (excluding skipped):`,
);
console.log(modulesOrder);
if (reason !== 'passed') {
const failingModule = modulesOrder.at(-1);
const firstHalf = modulesOrder.slice(
0,
Math.floor(modulesOrder.length / 2),
);
const secondHalf = modulesOrder.slice(
Math.floor(modulesOrder.length / 2),
);
const firstHalfMessage =
firstHalf.length === 1
? `\nYour potentially order-dependent test file is likely to be: ${firstHalf[0]} ${failingModule}.
You should now investigate what state is being shared between these two test files and causes state pollution. Good luck!`
: `\nTo bisect first half:\n
for i in {1..10}; do pnpm test --isolate=false --sequence.shuffle.files --fileParallelism=false --bail=1 ${firstHalf.join(
' ',
)} ${failingModule} || break; done`;
const secondHalfMessage =
secondHalf.length < 2
? ''
: `\nTo bisect second half:\n
for i in {1..10}; do pnpm test --isolate=false --sequence.shuffle.files --fileParallelism=false --bail=1 ${secondHalf.join(
' ',
)} || break; done`;
console.log(`${firstHalfMessage}\n${secondHalfMessage}`);
} else {
console.log(
`\nNo failing test. Try rerunning.`,
);
}
}
}
import { defineConfig } from 'vitest/config';
import { TestFilesOrderReporter } from './test-files-order-reporter';
export default defineConfig({
// ...
test: {
globals: true,
// ...
reporters: [new TestFilesOrderReporter()],
}
});
@mdubourg001
Copy link
Author

Example of usage:

❯ pnpm test --sequence.shuffle.files --fileParallelism=false --bail=1

This reporter helps identify test files order dependencies by reporting the order in which test files are executed, and helping you bisecting the files that cause the failure.

For predictible results, make sure to run this reporter with the following options:
  - --sequence.shuffle.files
  - --fileParallelism=false
  - --bail=1

Running tests with seed "1769531095369"

Test files execution order, (excluding skipped):
[
  'src/components/presentational/BlocPathForm/__tests__/mergeAndDeduplicateModulePaths.test.ts',
  'src/components/presentational/BlocPathModulePathsList/__tests__/BlocPathModulePathsList.test.tsx',
  'src/components/presentational/ModulePathsList/__tests__/ModulePathsList.test.tsx',
  'src/components/presentational/BlocPathForm/__tests__/getSelectModulePaths.test.ts',
  'src/components/presentational/ModulePathBillingList/ModulePathBillingList.test.tsx',
  'src/components/presentational/ModulePathForm/__tests__/formatMomentValues.test.ts',
  'src/components/presentational/ModulePathExpensesList/ModulePathExpensesList.test.jsx',
  'src/components/presentational/ModulePathForm/__tests__/useValidTrainingModeHelpers.test.ts',
  'src/components/presentational/ModulePathBillingList/BillingForm/BillingForm.test.jsx'
]

To bisect first half:

      for i in {1..10}; do pnpm test --sequence.shuffle.files --fileParallelism=false --bail=1 src/components/presentational/BlocPathForm/__tests__/mergeAndDeduplicateModulePaths.test.ts src/components/presentational/BlocPathModulePathsList/__tests__/BlocPathModulePathsList.test.tsx src/components/presentational/ModulePathsList/__tests__/ModulePathsList.test.tsx src/components/presentational/BlocPathForm/__tests__/getSelectModulePaths.test.ts src/components/presentational/ModulePathBillingList/BillingForm/BillingForm.test.jsx || break; done

To bisect second half:

      for i in {1..10}; do pnpm test --sequence.shuffle.files --fileParallelism=false --bail=1 src/components/presentational/ModulePathBillingList/ModulePathBillingList.test.tsx src/components/presentational/ModulePathForm/__tests__/formatMomentValues.test.ts src/components/presentational/ModulePathExpensesList/ModulePathExpensesList.test.jsx src/components/presentational/ModulePathForm/__tests__/useValidTrainingModeHelpers.test.ts src/components/presentational/ModulePathBillingList/BillingForm/BillingForm.test.jsx || break; done

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment