This skill equips you to configure, run, debug, and review a Vitest suite: the runner's own surface — its config file, its vi API, its pools and reporters, its coverage providers, and its Browser Mode — plus the operational rules an agent needs to drive it without hanging a session or burning a context window.
It is the runner-specific layer. A tool-agnostic unit-testing capability owns what to assert, how to name a spec, what makes a fixture good, and whether a behavior deserves a unit test at all; those questions have the same answers whatever runner executes them. This skill owns the mechanism underneath: which option, which file, which vi call, which flag. Where a rule here touches judgment — which dependency is worth mocking, how much of a payload to pin, whether a snapshot earns its place — it points at the tool-agnostic owner rather than answering it.
Two neighbours are disclaimed explicitly. An end-to-end capability owns user journeys, the locator fallback hierarchy, server lifecycle, and scenario coverage even when the runner underneath is Vitest; this skill owns the runner configuration that suite runs on. A component-development capability owns test-hook conventions such as data-testid and testID; this skill owns the Browser Mode machinery that queries them. A project on a different runner should reach for that runner's own capability, not this one.
Version posture is lookup-first. Rules are verified against Vitest 4.1.10 (Node >= 20, Vite >= 6) and name the release they were checked against. Vitest 4 moved or removed roughly two dozen configuration options and silently ignores the old names rather than erroring, so a v3-era answer copied off the open web fails invisibly — the suite runs, the option does nothing. Wherever a surface is known to have moved, consult the installed version's own documentation at https://vitest.dev rather than recalling an option name; each reference below names the upstream page it was verified against.
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be interpreted as described in RFC 2119.
Running Vitest as an Agent
See running-as-an-agent.md for:
vitest runor--no-watchfor every non-interactive run; barevitestenters watch mode and never exits- a project
testscript defined asvitestorvitest --watch, which hands that hang to anyone who runs it - narrowing with a path,
-t,file.test.ts:42,--project,--changed, or--related - the
minimalreporter's token cost argument, and the customreportersarray that disables its auto-selection - reading the reported diff and stack frame instead of re-running the suite to see the failure again
- third-party Vitest MCP servers, and why adding one needs the human's approval
Version Discipline
See version-discipline.md for:
- Vitest 4 ignoring an unrecognized
testkey instead of erroring, so a v3-era option runs green and does nothing workspace→projects,maxThreads/maxForks→maxWorkers,singleThread→maxWorkers: 1+isolate: falsepoolOptions.*flattened to the top level, anddeps.external/inline→server.deps.*coverage.all,environmentMatchGlobs,poolMatchGlobs,minWorkers, and thebasicreporter, all removedgetMockName()now returning"vi.fn()", which rewrites every snapshot containing a mock- which features need 4.1.0, 4.1.1, 4.1.3, or 4.1.4 — tags, the fixture builder, custom snapshot matchers, ARIA snapshots
Configuration
See configuration.md for:
defineConfigimported fromvitest/config, not fromvite, which does not type thetestblock- v4's default exclusions shrinking to
node_modulesand.git, sodistis now scanned unless excluded globals: falseand explicit imports as the posture, and thevitest/globalstypes entryglobals: truethen needsclearMocks,mockReset,restoreMocks,unstubEnvs,unstubGlobalsas config rather than a remembered hooktestTimeoutat 5,000 ms, and raising the narrowest scope — per-test orhookTimeout— instead of the suitedangerouslyIgnoreUnhandledErrorsdisabling a real check, and theexperimentalblock as a separate risk class
Test Projects
See test-projects.md for:
projectsas the one mechanism, replacingworkspace,poolMatchGlobs, andenvironmentMatchGlobstogether- glob, config path, and inline entries,
!negation, and thepackages/!(business)bracket idiom - the
vitest.config.*/vitest.<name>.config.*naming rule a file must match to be picked up at all - projects not inheriting the root config —
extends: trueormergeConfig, or shared setup files are simply absent coverage,reporters, andresolveSnapshotPathbeing process-wide and therefore illegal in a project- splitting a
nodeproject from a browser one, or unit from integration with different isolation
Test Environment
See environments.md for:
node,jsdom,happy-dom,edge-runtime, and DOM startup cost paid per file- the
// @vitest-environment jsdomdocblock for a handful of files rather than the whole suite - a dependency importing CSS or an asset failing under a DOM shim until the whole chain is in
server.deps.inline viteEnvironmentas v4's replacement fortransformModein a custom environment- "passes under jsdom, breaks in the browser" as the signal to move to Browser Mode rather than stub further
Run Lifecycle
See run-lifecycle.md for:
globalSetuprunning in the main process with a different global scope, so onlyprovide/injectcrosses itsetupFilesrunning in the worker, which is why mock registration andexpect.extendbelong there- setup files re-running under
isolate: falsewhile the modules they import stay cached globalSetupnot re-running in watch mode, so a seeded database keeps the previous run's stateforceRerunTriggersandwatchTriggerPatternsfor a non-imported file that should invalidate a run
Test API
See test-api.md for:
- test options in the second argument; the v3 third-argument form is removed and silently unread
skip,skipIf,runIf,only,todo,fails,concurrent, andsequential's deprecationtest.eachspreading an array case whiletest.fordoes not, andforsupplying the test contextaroundEach/aroundAlland therunTestcall without which nothing inside runsonTestFinished/onTestFailedas the per-test cleanup that survives concurrencyretry's error predicate, for absorbing one failure mode rather than every error the test can raise
Fixtures and Test Context
See fixtures-and-context.md for:
context.expectbeing required for a snapshot inside a concurrent test- the 4.1
test.extendbuilder inferring types, andonCleanupbeing callable only once per fixture - test, file, and worker scope, and a fixture reaching only its own scope or a longer-lived one
- only destructured fixtures being initialized, so
{ database }andcontext.databaseare not equivalent test.overridefor a suite's value, andinjectedfixtures fed fromconfig.provideper project
Assertions and Async Tests
See assertions-and-async.md for:
toEqualandtoStrictEqualdisagreeing exactly on a present-undefinedkey and a sparse arrayexpect.objectContaining,expect.stringMatching,expect.closeTo, andexpect.schemaMatchingfor Standard Schema.resolves/.rejectsfailing the test in v4 when unawaited, where v3 passed silentlyvi.waitForvsvi.waitUntilvsexpect.poll, andexpect.pollrejecting snapshots,.rejects, andtoThrowexpect.hasAssertions()for a test whose assertions sit inside a callbackexpect.extend'sMatcherResult, and why branchingpassonisNotcancels out
Function Mocks and Spies
See function-mocks.md for:
- a spy seeing only calls made after it is installed, so import-time work is invisible to it
mockClearkeeping the implementation,mockResetrestoring the original,mockRestorerestoring the descriptorclearAllMocks/resetAllMocks/restoreAllMocksnot reaching automocked modules at allmockThrow/mockThrowOnce,withImplementation, and theOncevariants for a call sequenceinvocationCallOrderstarting at 1 in v4, and preferring the mock matchers over readingmock.callsvi.defineHelperso a shared assertion helper reports the caller's line
Module Mocking
See module-mocking.md for:
vi.mockhoisting above imports, leaving a module-levelconstundefined when the factory runsvi.hoistedfor a value the factory closes over, andimportOriginalneeding anawait- automocking emptying arrays and returning
undefinedfrom methods, versus{ spy: true }keeping real behavior __mocks__staying inert until avi.mockcall names the module- a call between two functions in the same file being unmockable, and the extract-or-inject refactor that fixes it
server.deps.inlinewhenvi.mockagainst an externalized dependency has no effect
Timers and Ambient State
See timers-and-ambient-state.md for:
- a fake clock left installed leaking into later files in the same worker
- the
*Asyncadvance variants being required whenever a scheduled callback awaits runAllTimersthrowing atloopLimiton code that reschedules itselfvi.setSystemTimefreezing a drifting snapshot, andTZstill governing formatted outputvi.stubGlobal/vi.stubEnvpersisting across tests unlessunstubGlobals/unstubEnvsare setmemfsthrough__mocks__/fs.cjsand__mocks__/fs/promises.cjs, mocking both specifiers
Network Mocking
See network-mocking.md for:
- intercepting with MSW rather than replacing
fetch, so the request path under test stays real server.listen()/resetHandlers()/close()acrossbeforeAll,afterEach, andafterAllonUnhandledRequest: "error"as what stops an unmatched request reaching the real network- why an end-to-end test deliberately wants the real dependency instead
Snapshots
See snapshots.md for:
- inline for a diff-reviewable value,
.snapfor larger output, file snapshots for markup, ARIA for structure - errors rendering as
[Error: message]rather than the bare message -uacceptingnew,all, andnone, and--update=newleaving existing snapshots alone- CI refusing to write a snapshot, so a missing one fails rather than being created
- an obsolete snapshot failing CI, which catches a deleted test whose stored output stayed
Coverage
See coverage.md for:
@vitest/coverage-v8and@vitest/coverage-istanbulneeding explicit installation in v4- only imported files being reported unless
coverage.includenames the source globs autoUpdaterewriting a threshold after a run, so a drop is recorded rather than rejected- ignore hints needing
-- @preserveor esbuild strips them before the provider sees them DEBUG=vitest:coveragewhen collection itself is what is slow
Performance and Parallelism
See performance-and-parallelism.md for:
- files across workers versus tests within a file, and which axis a given slowdown sits on
forksas the compatible default, andthreadsproducing worker-termination and segfault failuresisolate: falseas the biggest win, what module-level state survives it, andvmThreadsforbidding it- concurrency only helping tests that wait, since synchronous tests still share one thread
- reading the transform, collect, setup, and environment timings before changing any setting
- sharding splitting files rather than cases, so one slow file cannot be sharded
Filtering and Tags
See filtering-and-tags.md for:
- a positional path filter,
-t, andfile.test.ts:10needing the full filename allowOnlyat its default failing a CI run that contains.only- declaring tags in config before use, with
timeout,retry, andpriorityper tag --tags-filterexpressions withand/or/not, wildcards, andnot>and>orprecedence@module-tagapplying to a whole file rather than one test- filters applying per file, so Vitest still loads every file to find matches
Reporters and CI Output
See reporters-and-ci.md for:
minimal, aliasedagent, printing only failures and their errors- its auto-selection on
std-envAI-agent detection or anAI_AGENTvariable - any custom
reportersarray skipping that detection, andgithub-actionsneeding listing for the same reason blobwith--shard, merged by--merge-reports, as the only correct way to report a sharded runoutputFileas a path or a per-reporter mapcontext.annotateseverities, and the default reporter printing them only for failed tests
Browser Mode
See browser-mode.md for:
- CSS layout, computed style, and real event dispatch as the bug class a DOM shim cannot produce
- v4 providers being objects from
@vitest/browser-playwright,-webdriverio, or-preview, not strings - imports moving to
vitest/browserfrom the removed@vitest/browser/context await expect.element(...)as the retrying assertion, where the synchronousexpectdoes notuserEventfromvitest/browserdriving real input, distinct from Testing Library's synthetic eventsalert/confirmblocking the thread, and sealed ESM namespaces needing{ spy: true }to observe an export
Visual Regression
See visual-regression.md for:
toMatchScreenshotwriting a reference on first run and failing, into a committed__screenshots__/allowedMismatchedPixelRatioversusallowedMismatchedPixels, where the stricter limit wins- stable-screenshot detection reshooting until two frames agree
- fonts, GPU, and headless mode making a local screenshot differ from CI's on an unchanged page
toMatchAriaSnapshotasserting the accessibility tree instead, and surviving a font bump
Type and In-Source Tests
See types-and-in-source.md for:
*.test-d.tswithexpectTypeOf/assertType, run only under--typecheck- type tests being analyzed rather than executed, so a
test.eachname is never evaluated import.meta.vitestwithincludeSource, and"vitest/importMeta"in the tsconfig types- the production-build
define: { "import.meta.vitest": "undefined" }without which those tests ship to users - the documentation scoping in-source tests to small utilities, not components or e2e
Debugging and Failure Modes
See debugging-and-failure-modes.md for:
--inspect-brk --no-file-parallelism --test-timeout=0together, and what each flag removes--isolate falsekeeping a debugger attached across watch reruns- the
hanging-processreporter and--detect-async-leaksfor a run that will not exit Failed to Terminate Workerfrom Node'sfetchunderthreads, fixed byforks- segfaults from native modules under
threads, fixed the same way Cannot find modulefrom an unresolved path alias, andssr.resolve.conditionsfor ignored export conditions
Suite Hygiene
See suite-hygiene.md for:
expect-expect,no-focused-tests, andvalid-expectfrom@vitest/eslint-pluginallowOnlyand obsolete-snapshot failures as the two guards against an abandoned editrestoreMocks/unstubEnvs/unstubGlobalsmoving hygiene out of every author's memory--detect-async-leaksfor a handle that outlives the test that opened it- whether CI invokes
vitest runrather than a form that can enter watch mode, and whether a changed config key still exists in the installed version
Extending Vitest
See extending-vitest.md for:
- the removed v3 reporter hooks (
onCollected,onTaskUpdate,onFinished) producing silence, not an error viteEnvironmentin a custom environment, and the rewritten v4 pool interfacestartVitest,createVitest, andvitest.state.getTestModules()fromvitest/nodeexperimental.openTelemetryneeding ansdkPathwhose module default-exports an unstarted SDK- the per-worker startup cost tracing adds unless the run is not isolated