watchTriggerPatterns config option
watchTriggerPatterns makes dependencies on non-imported files explicit. It was added in version 3.2.0. You declare a regex pattern over file paths and a callback function that returns which tests to rerun when a matching file changes. This solves the problem where Vitest only tracks the import graph and misses tests that depend on files they don't import, such as email templates loaded with fs.readFile, JSON fixtures parsed at runtime, or HTML/CSS pulled in by build steps.
watchTriggerPatterns pattern structure
watchTriggerPatterns accepts an array of objects. Each object has a 'pattern' property containing a regex pattern over file paths, and a 'testsToRun' property containing a callback function. The testsToRun callback receives the changed file path and the result of RegExp.exec against the changed file path. It returns one or more test file paths to rerun as a string or string array, or undefined if no tests should rerun. Paths are resolved against the workspace root and are not interpreted as globs.
watchTriggerPatterns example with template files
This example shows how to rerun tests when template files change: when `src/templates/welcome.html` is edited, it reruns `api/tests/mailers/welcome.test.ts`. The pattern is `/src\/templates\/(.*)\.\(ts|html|txt\)$/`, and testsToRun uses the captured filename from the regex match to construct the test path.
watchTriggerPatterns multiple patterns variations
Multiple watchTriggerPatterns can coexist. One pattern can derive the test path from the directory of the changed file using captured groups. Another pattern can map a single shared fixture to a fixed list of test files by returning an array of test paths from testsToRun. This allows flexible mapping of different types of non-imported file changes to their dependent tests.
expect.poll example with server startup
Example showing expect.poll waiting for a server to be ready: `await expect.poll(() => server.isReady, { timeout: 500, interval: 20 }).toBe(true)`. This polls the server.isReady condition until it becomes true or the 500ms timeout elapses, checking every 20ms.
expect.poll matchers that don't work
Some matchers do not pair with expect.poll: snapshot matchers (which would always succeed under polling), .resolves and .rejects (the condition is already awaited), and toThrow (the value is resolved before the matcher sees it). For these cases, use vi.waitFor instead.
vi.waitFor for retrying work until success
vi.waitFor is used when the wait condition is the work itself succeeding rather than an assertion. It runs the callback at each interval; a thrown error queues another attempt, and the first call that doesn't throw resolves the wait with whatever the callback returned. It accepts options with timeout and interval properties.
vi.waitFor example with database connection
Example showing vi.waitFor waiting for a database connection: `const client = await vi.waitFor(() => connect(DB_URL), { timeout: 5000, interval: 100 })`. This retries the connect call every 100ms until it succeeds (doesn't throw) or 5000ms elapses, then returns the client.
vi.waitUntil example with job results lookup
Example showing vi.waitUntil polling for job results: `const result = await vi.waitUntil(() => jobResults.get('build-42'), { timeout: 5000, interval: 100 })`. This polls the job results every 100ms until a truthy value is returned or 5000ms elapses.
vi.waitUntil for polling with fast failure on errors
vi.waitUntil is used for a value lookup where any thrown error should fail the test on the spot rather than be retried. Each interval calls the callback again. A truthy return resolves the wait; a falsy return waits for the next interval. A thrown error fails the test immediately.
Default timeout and interval for wait helpers
expect.poll, vi.waitFor, and vi.waitUntil all accept options with timeout and interval properties. They default to a 1000ms timeout and 50ms intervals. vi.waitFor and vi.waitUntil also accept a number in place of the options object as shorthand for the timeout.
Wait helpers with fake timers
When vi.useFakeTimers is active, vi.waitFor automatically calls vi.advanceTimersByTime(interval) between attempts. This keeps setTimeout-based code under test reachable without leaking real time into the test.
Choosing between expect.poll, vi.waitFor, and vi.waitUntil
Use expect.poll when the wait is an assertion. Use vi.waitFor when the work might fail until it's ready and you want to retry on thrown errors. Use vi.waitUntil for a lookup that might be falsy and that's fine, but fail fast on thrown errors. expect.poll and vi.waitFor retry on thrown errors; vi.waitUntil does not, it fails fast.
expect.poll for retrying assertions
expect.poll is used when the wait condition is an assertion. The callback returns the value to assert on, the matcher does the comparison, and Vitest retries the whole expression at each interval until the matcher passes. expect.poll makes every assertion asynchronous, so the call must be awaited. It accepts options with timeout and interval properties.
JUnit reporter annotation behavior
The JUnit reporter lists annotations inside the testcase's properties tag. It ignores all attachments and prints only the type and message of annotations.
TAP reporter annotation behavior
The tap and tap-flat reporters print annotations as diagnostic messages on a new line starting with a '#' symbol. They ignore all attachments and print only the type and message.
Test annotations example with multiple types
Example showing test annotations with text message, warning type, file attachment, and markdown content:
```ts
test('hello world', async ({ annotate }) => {
await annotate('this is my test')
if (condition) {
await annotate('this should\'ve errored', 'error')
}
const file = createTestSpecificFile()
await annotate('creates a file', { body: file })
await annotate('creates a file with text', {
contentType: 'text/markdown',
body: 'Hello **markdown**',
bodyEncoding: 'utf-8',
})
})
```
Test annotations via context.annotate API
Vitest supports annotating tests with custom messages and files via the context.annotate API. These annotations are attached to the test case and passed to reporters through the onTestAnnotate hook.
annotate function signature and parameters
The annotate function accepts a message string as the first argument and an optional second argument. The second argument can be a type string ('notice', 'warning', 'error') or an object with properties: body (the annotation content), contentType (e.g. 'text/markdown'), and bodyEncoding (e.g. 'utf-8').
annotate function is async and returns a Promise
The annotate function returns a Promise and must be awaited if you rely on it. However, Vitest will automatically await any non-awaited annotations before the test finishes.
Default reporter annotation behavior
The default reporter prints annotations only if the test has failed. Annotations are shown with their line number, type (notice/warning/error), and message content.
Verbose reporter annotation behavior
The verbose reporter is the only terminal reporter that displays annotations even when the test does not fail, showing line numbers, types, and messages.
HTML reporter annotation behavior
The HTML reporter displays annotations on the line where they were called in the test file, similar to the UI. Annotations called outside test files cannot currently be seen in the UI.
GitHub Actions reporter annotation behavior
The github-actions reporter prints annotations as GitHub notice messages by default. The type can be configured with the second argument as 'notice', 'warning', or 'error'. If the type is none of these values, Vitest displays the message as a notice.
Cannot override non-test fixtures in describe blocks
You cannot override non-test fixtures (worker or file scoped) inside describe blocks. Consider overriding at the top level of the module or by using the injected option.
Concurrent snapshot tests with expect context
When running snapshot tests concurrently with it.concurrent, use the expect API from the test context rather than the global expect, as the global expect cannot track concurrent snapshots.
Non-isolate mode affects worker fixture overrides
In non-isolate mode, overriding a worker fixture will affect the fixture value in all test files running after it was overridden.
skip function signature and usage
The skip function has two signatures: skip(note?: string) which returns never and skips subsequent test execution immediately, and skip(condition: boolean, note?: string) which conditionally skips the test. Since Vitest 3.1, it accepts a boolean parameter to skip the test conditionally.
annotate function for test annotations
The annotate function is available since Vitest 3.2.0 and allows adding test annotations that will be displayed by the reporter. Signature: annotate(message: string, attachment?: TestAttachment) or annotate(message: string, type?: string, attachment?: TestAttachment). Returns a Promise<TestAnnotation>.
signal property AbortSignal behavior
The signal property is an AbortSignal available since Vitest 3.2.0 that is aborted by Vitest in these situations: test times out, user manually cancels with Ctrl+C, vitest.cancelCurrentRun is called programmatically, or another test failed in parallel and the bail flag is set.
bench fixture for benchmarks in tests
The bench fixture available since Vitest 5.0.0 lets you define and run benchmarks inside regular tests. You can measure throughput, compare implementations, and assert relative performance using bench.compare().
onTestFailed and onTestFinished hooks
The onTestFailed and onTestFinished hooks are available in the test context, bound to the current test. These are useful when running tests concurrently and you need special handling for a specific test.
test.extend method overview
Vitest allows extending the test context with custom fixtures using test.extend. It supports two syntaxes: the builder pattern (recommended) and the object syntax (Playwright-compatible). The builder pattern provides automatic type inference while the object syntax requires manual type declarations.
Builder pattern fixture definition
The builder pattern is available since Vitest 4.1.0 and is the recommended way to define fixtures. TypeScript infers the type of each fixture from its return value automatically. Use .extend('name', value) for simple values and .extend('name', async ({ dependency }) => { ... }) for function fixtures. Fixtures can access previously defined fixtures via their first parameter.
onCleanup callback for fixture teardown
The onCleanup callback registers teardown logic that runs after a fixture's scope ends. It can only be called once per fixture. If multiple cleanup operations are needed, combine them into a single cleanup function or split the fixture into multiple smaller fixtures.
Fixture options: auto, scope, injected
The second argument to .extend() accepts options: auto (boolean, fixture runs for every test even if not used), scope ('test' default, 'worker', or 'file'), and injected (boolean, fixture can be overridden via config).
Object syntax for fixtures Playwright-compatible
The object syntax uses a use() callback pattern for cleanup. Code after use() runs for cleanup. You must provide types manually as a generic parameter since TypeScript cannot infer them from the use() callback.
Tuple syntax for fixture options with object syntax
With the object syntax, use a tuple to specify fixture options: [fixtureFunction, { option: value }] or [staticValue, { option: value }].
Fixture initialization is smart
Vitest runner smartly initializes fixtures and injects them into the test context based on usage. Fixtures that are not used by a test are not initialized.
Use object destructuring for fixtures
When using test.extend() with fixtures, always use the object destructuring pattern { database } to access context both in fixture function and test function, not a single context parameter.
Extending extended tests
You can extend an already extended test to add more fixtures using test.extend(). This chains fixture definitions across multiple files.
Mixing builder and object syntax
You can combine both approaches. The builder pattern can be chained after object-based extensions.
Test scope fixture default
By default, fixtures are initialized for each test with 'test' scope. Test-scoped fixtures are created fresh for each test and have access to the built-in test context (task, expect, skip, etc.).
File scope fixtures initialized once per file
File-scoped fixtures with { scope: 'file' } are initialized once per test file and reused by all tests in that file.
Worker scope fixtures initialized once per worker
Worker-scoped fixtures with { scope: 'worker' } are initialized once per worker process. By default every file runs in a separate worker so file and worker scopes work the same way. However, if isolation is disabled, worker-scoped fixtures will be shared across files running in the same worker.
Fixture scope hierarchy access rules
Fixtures can only access other fixtures from the same or higher (longer-lived) scopes. Worker fixtures can only access other worker fixtures. File fixtures can access worker + file fixtures. Test fixtures can access worker + file + test fixtures and the built-in test context.
Built-in test context only in test-scoped fixtures
Only test-scoped fixtures have access to the built-in test context (task, expect, skip, etc.). Worker and file fixtures run outside of any specific test, so test-specific properties are not available to them. Use expect.getState().testPath to access the file path in a file-scoped fixture.
Type-safe scope access with $worker $file $test keys
With the object syntax, use $worker, $file, and $test keys to explicitly declare which fixtures belong to which scope for compile-time type safety similar to the builder pattern.
Injected fixture default value
Since Vitest 3, you can pass { injected: true } in fixture options to provide different values in different projects. If the key is not specified in the project configuration's provide option, the default value will be used.
test.override for fixture value overriding
Available since Vitest 4.1.0, test.override allows overriding fixture values for a specific suite and its children. Returns the test API so calls can be chained. You cannot override a fixture's scope or auto options, and cannot introduce new fixtures with override.
test.override with builder pattern
Use test.override('name', value) or test.override('name', ({ dependency }) => ...) to override fixtures in the builder pattern. It returns the test API for chaining.
test.override with object syntax
With object syntax, use test.override({ fixture1: value1, fixture2: value2 }) to override multiple fixtures at once.
test.override nested scope inheritance
Overrides are inherited by nested suites and can be overwritten again. A describe block can override fixtures for its tests, and nested describe blocks can override them further.
Type-safe hooks with test.extend
When using test.extend, the extended test object provides type-safe hooks that are aware of the extended context. Unlike global hooks, these hooks can access fixtures in their parameters.
Suite-level hooks with fixtures
Available since Vitest 4.1.0, the extended test object provides beforeAll, afterAll, and aroundAll hooks that can access file-scoped and worker-scoped fixtures. These must be called on the test object returned from test.extend() to have access to fixtures.
Suite-level hooks can only access file and worker fixtures
Suite-level hooks (beforeAll, afterAll, aroundAll) can only access file-scoped and worker-scoped fixtures, including auto fixtures. Test-scoped fixtures are not available because they run outside the context of individual tests. Using global hook functions does not have access to custom fixtures.
Builder pattern example with server fixture
Example: const test = baseTest.extend('config', { port: 3000, host: 'localhost' }).extend('server', async ({ config }) => { return `http://${config.host}:${config.port}` })
Built-in expect API bound to test
The expect API can be accessed from the test context and is bound to the current test. This is useful for running snapshot tests concurrently because the global expect cannot track them.
Built-in task property
The task property is a readonly object containing metadata about the test. It includes properties like task.name for the test name.
Test context first argument
The first argument for each test callback is a test context object that provides access to utilities, states, and fixtures.