Vitest doctor cut our CI test time from 263s to 115s
Context:
- Environment: SPA React application, 615 test files / 1813 tests, happy-dom
- Dependencies:
- Vitest version:
"vitest": "5.0.0" - Vitest 5 requires Vite >= 6.4.0 and Node.js >= 22.12.0
- Vitest version:
Problem:
The duration breakdown tells you setup or environment dominates, not whether a different pool would fix it. You find that out by running the suite under each one and comparing.
Solution:
Vitest 5 adds vitest doctor.
npx vitest doctor
Output:
Results (min of 3 runs each)
baseline (pool: forks · isolate: true) 4.08s
pool: 'threads' 3.64s (-11%)
pool: 'vmThreads' 1.33s (-67%)
isolate: false 1.28s (-69%)
Recommendation: pool: 'vmThreads' (-67%)
// vitest.config.ts
export default defineConfig({
test: {
pool: 'vmThreads',
},
})
Real numbers on a 615-file suite
Link to heading: Real numbers on a 615-file suiteDoctor recommended pool: 'vmThreads' over the forks we had been running. Local, 615 test files / 1813 tests:
| Pool | Duration |
|---|---|
forks (baseline) | 293.59s |
vmThreads | 45s |
On CI the same switch, measured on the latest happy-dom environment:
| Run | Duration | Breakdown |
|---|---|---|
| Before | 263.36s | setup 65%, tests 14%, import 8%, transform 7%, environment 5% |
| After | 114.74s | setup 40%, tests 26%, transform 16%, worker 13%, import 4%, environment 1% |
That is -56% on CI.
The custom environment caveat
Link to heading: The custom environment caveatIf your project uses a custom environment, say a thin wrapper around happy-dom that adds a BroadcastChannel polyfill for MSW 2.x, doctor skips the vmThreads and vmForks candidates without reporting why. That was issue #11192, fixed since.
If you hit it, point your config at the built-in environment, run doctor, then switch back:
// vitest.config.ts
export default defineConfig({
test: {
environment: 'happy-dom', // instead of './custom-happy-dom-environment.ts'
},
})
Before you switch pools
Link to heading: Before you switch poolsvmThreads does not give each test file a fresh process:
- code relying on process-level globals or native modules can break
- memory leaks accumulate across files instead of dying with the process
- a suite that leaks between files will start failing in new ways. detectAsyncLeaks finds those before you commit to the switch
Do not merge a doctor recommendation without a full CI run. A fast green run can still hide new flakiness.
Sources: