From f0dd8d842919dbcd4bbd17b67ea25b9a822a6b0e Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 5 Oct 2026 17:02:10 +0900 Subject: [PATCH 01/51] docs: update `page.mark()` nesting behavior (#11484) Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 --- guide/browser/trace-view.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/browser/trace-view.md b/guide/browser/trace-view.md index 1d700576..59c1f97f 100644 --- a/guide/browser/trace-view.md +++ b/guide/browser/trace-view.md @@ -119,7 +119,7 @@ await page.mark('content rendered') await page.getByRole('button', { name: 'Sign in' }).mark('sign in button') ``` -You can also pass a callback to `page.mark()`. Note that grouping is not currently supported — each inner action is recorded individually, and the mark entry appears at the end: +You can also pass a callback to `page.mark()` to group steps. The mark entry spans the callback, and the actions inside it are nested under the mark in the step list: ```ts await page.mark('sign in flow', async () => { From 55a88d677eeac16bb8fd47c133408ceda66c7c2b Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 5 Oct 2026 13:54:03 +0200 Subject: [PATCH 02/51] chore(deps): update all non-major dependencies (#11477) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Vladimir Sheremet --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 7d26ebcf..bca1fa08 100644 --- a/package.json +++ b/package.json @@ -23,8 +23,8 @@ "@iconify-json/carbon": "catalog:", "@iconify-json/logos": "catalog:", "@iconify/vue": "catalog:", - "@shikijs/transformers": "^4.4.3", - "@shikijs/vitepress-twoslash": "^4.4.3", + "@shikijs/transformers": "^4.5.0", + "@shikijs/vitepress-twoslash": "^4.5.0", "@unocss/reset": "catalog:", "@vite-pwa/assets-generator": "^2.0.0", "@vite-pwa/vitepress": "^1.1.0", From 0478de63a6e2fa35a03275b25b0f912430d81d7d Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 5 Oct 2026 21:17:16 +0200 Subject: [PATCH 03/51] docs: align `mock` guide with the current implementation (#11503) --- api/mock.md | 45 ++++++++++++++++++++++++--------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/api/mock.md b/api/mock.md index a7dd8f46..c8ae3c87 100644 --- a/api/mock.md +++ b/api/mock.md @@ -54,7 +54,7 @@ The custom function implementation in the types below is marked with a generic ` ::: ::: warning Class Support {#class-support} -Shorthand methods like `mockReturnValue`, `mockReturnValueOnce`, `mockResolvedValue` and others cannot be used on a mocked class. Class constructors have [unintuitive behaviour](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor) regarding the return value: +The `mockReturnValue`, `mockReturnValueOnce`, `mockResolvedValue`, `mockResolvedValueOnce`, `mockRejectedValue` and `mockRejectedValueOnce` methods cannot be used when the mock is called with the `new` keyword. Class constructors have [unintuitive behaviour](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor) regarding the return value: ```ts {2,7} const CorrectDogClass = vi.fn(class { @@ -74,7 +74,7 @@ Marti instanceof CorrectDogClass // ✅ true Newt instanceof IncorrectDogClass // ❌ false! ``` -Even though the shapes are the same, the _return value_ from the constructor is assigned to `Newt`, which is a plain object, not an instance of a mock. Vitest guards you against this behaviour in shorthand methods (but not in `mockImplementation`!) and throws an error instead. +Even though the shapes are the same, the _return value_ from the constructor is assigned to `Newt`, which is a plain object, not an instance of a mock. Vitest guards you against this behaviour in these methods (but not in `mockImplementation`!) and throws an error instead. If you need to mock constructed instance of a class, consider using the `class` syntax with `mockImplementation` instead: @@ -101,7 +101,7 @@ mock.mockImplementation(class { function getMockImplementation(): T | undefined ``` -Returns the current mock implementation if there is one. +Returns the current mock implementation if there is one. If an implementation was queued with [`mockImplementationOnce`](#mockimplementationonce), it returns the first queued implementation instead. If the mock was created with [`vi.fn`](/api/vi#vi-fn), it will use the provided method as the mock implementation. @@ -230,7 +230,7 @@ myMockFn() // 'original' Can be used with an asynchronous callback. The method has to be awaited to use the original implementation afterward. ```ts -test('async callback', () => { +test('async callback', async () => { const myMockFn = vi.fn(() => 'original') // We await this call since the callback is async @@ -285,7 +285,7 @@ await asyncMock() // throws Error<'Async error'> function mockReset(): Mock ``` -Does what [`mockClear`](#mockClear) does and resets the mock implementation. This also resets all "once" implementations. +Does what [`mockClear`](#mockclear) does and resets the mock implementation. This also resets all "once" implementations. Note that resetting a mock from `vi.fn()` will set the implementation to an empty function that returns `undefined`. Resetting a mock from `vi.fn(impl)` will reset the implementation to `impl`. @@ -315,7 +315,7 @@ To automatically call this method before each test, enable the [`mockReset`](/co ## mockRestore ```ts -function mockRestore(): Mock +function mockRestore(): void ``` Does what [`mockReset`](#mockreset) does and restores the original descriptors of spied-on objects, if the mock was created with [`vi.spyOn`](/api/vi#vi-spyon). @@ -411,7 +411,7 @@ mock() // 43 function mockReturnValueOnce(value: ReturnType): Mock ``` -Accepts a value that will be returned whenever the mock function is called. TypeScript will only accept values that match the return type of the original function. +Accepts a value that will be returned during the next function call. TypeScript will only accept values that match the return type of the original function. If chained, each consecutive call will return the specified value. When the mocked function runs out of implementations, it will invoke the default implementation set with `vi.fn(() => defaultValue)` or `.mockImplementation(() => defaultValue)` if they were called: @@ -527,7 +527,7 @@ interface MockResultReturn { type: 'return' /** * The value that was returned from the function. - * If the function returned a Promise, then this will be a resolved value. + * If the function returned a Promise, then this will be the Promise, not its resolved value. */ value: T } @@ -615,7 +615,7 @@ const settledResults: MockSettledResult>>[] An array containing all values that were resolved or rejected by the function. -If the function returned non-promise values, the `value` will be kept as is, but the `type` will still says `fulfilled` or `rejected`. +If the function returned non-promise values, the `value` will be kept as is, but the `type` will still say `fulfilled` or `rejected`. Until the value is resolved or rejected, the `settledResult` type will be `incomplete`. @@ -664,10 +664,14 @@ fn2.mock.invocationCallOrder === [2] ## mock.contexts ```ts -const contexts: ThisParameterType[] +type MockProcedureContext = T extends new (...args: any[]) => any + ? InstanceType + : ThisParameterType + +const contexts: MockProcedureContext[] ``` -This property is an array of `this` values used during each call to the mock function. +This property is an array of `this` values used during each call to the mock function. If the mock was called with the `new` keyword, the value is the same as in [`mock.instances`](#mock-instances). ```js const fn = vi.fn() @@ -683,22 +687,23 @@ fn.mock.contexts[1] === context ## mock.instances ```ts -const instances: ReturnType[] +const instances: MockProcedureContext[] ``` -This property is an array containing all instances that were created when the mock was called with the `new` keyword. Note that this is the actual context (`this`) of the function, not a return value. - -::: warning -If the mock was instantiated with `new MyClass()`, then `mock.instances` will be an array with one value: +This property is an array with one value for each call to the mock function. If the mock was called with the `new` keyword, the value is the object that `new` returned, or `undefined` if the constructor threw an error. Otherwise, the value is the context (`this`) of the call, the same as in [`mock.contexts`](#mock-contexts). ```js const MyClass = vi.fn() +const context = {} + const a = new MyClass() +MyClass.call(context) MyClass.mock.instances[0] === a +MyClass.mock.instances[1] === context ``` -If you return a value from the constructor, it will not be in the `instances` array, but instead inside `results`: +If the implementation returns an object from the constructor, `mock.instances` stores that object: ```js const Spy = vi.fn(function () { @@ -706,8 +711,6 @@ const Spy = vi.fn(function () { }) const a = new Spy() -Spy.mock.instances[0] !== a -Spy.mock.results[0] === a +Spy.mock.instances[0] === a +Spy.mock.results[0].value === a ``` - -::: From d19cdc932332380fddae7fe415cc974a4c0661fd Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 6 Oct 2026 08:38:21 +0200 Subject: [PATCH 04/51] feat: support `test.ui.theme` option (#11499) --- config/ui.md | 38 ++++++++++++++++++++++++++++++++++++-- 1 file changed, 36 insertions(+), 2 deletions(-) diff --git a/config/ui.md b/config/ui.md index 8e02db39..0c96551a 100644 --- a/config/ui.md +++ b/config/ui.md @@ -5,11 +5,11 @@ outline: deep # ui -- **Type:** `boolean` +- **Type:** `boolean | object` - **Default:** `false` - **CLI:** `--ui`, `--ui=false` -Enable [Vitest UI](/guide/ui). +Enable [Vitest UI](/guide/ui). Setting `ui: true` is the same as `ui: { enabled: true }`. ::: warning This features requires a [`@vitest/ui`](https://npmx.dev/package/@vitest/ui) package to be installed. If you do not have it already, Vitest will install it when you run the test command for the first time. @@ -18,3 +18,37 @@ This features requires a [`@vitest/ui`](https://npmx.dev/package/@vitest/ui) pac ::: danger SECURITY ADVICE Make sure that your UI server is not exposed to the network. Since Vitest 4.1 setting [`api.host`](/config/api) to anything other than `localhost` will disable the buttons to save the code or run any tests for security reasons, effectively making UI a readonly reporter. ::: + +## ui.enabled 5.1.0 {#ui-enabled} + +- **Type:** `boolean` +- **Default:** `false` + +Enable [Vitest UI](/guide/ui). + +## ui.theme 5.1.0 {#ui-theme} + +- **Type:** `'auto' | 'light' | 'dark'` +- **Default:** `'auto'` + +The color theme of Vitest UI and of the [Browser Mode UI](/config/browser/ui). With `'auto'`, the UI follows the system preference and remembers the theme you toggle in the UI. With `'light'` or `'dark'`, the UI opens with this theme on every page load, and the toggle changes it only until the page reloads. + +This option does not enable Vitest UI, so you can set it together with [`browser.ui`](/config/browser/ui): + +```ts +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + ui: { + theme: 'dark', + }, + browser: { + enabled: true, + instances: [ + { browser: 'chromium' }, + ], + } + }, +}) +``` From 7c5bd3df24a10935fb7e6771fe8da8e262ab9c28 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 6 Oct 2026 08:40:32 +0200 Subject: [PATCH 05/51] chore: make ui.theme experimental (#11509) --- config/ui.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/config/ui.md b/config/ui.md index 0c96551a..17a1dec9 100644 --- a/config/ui.md +++ b/config/ui.md @@ -19,14 +19,14 @@ This features requires a [`@vitest/ui`](https://npmx.dev/package/@vitest/ui) pac Make sure that your UI server is not exposed to the network. Since Vitest 4.1 setting [`api.host`](/config/api) to anything other than `localhost` will disable the buttons to save the code or run any tests for security reasons, effectively making UI a readonly reporter. ::: -## ui.enabled 5.1.0 {#ui-enabled} +## ui.enabled 5.0.4 {#ui-enabled} - **Type:** `boolean` - **Default:** `false` Enable [Vitest UI](/guide/ui). -## ui.theme 5.1.0 {#ui-theme} +## ui.theme 5.0.4 {#ui-theme} - **Type:** `'auto' | 'light' | 'dark'` - **Default:** `'auto'` From 2c05a5a8344e0b9fdda2b120c667e0fc26a29a84 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Thu, 8 Oct 2026 12:46:07 +0200 Subject: [PATCH 06/51] feat(experimental): add an option to cache dependencies instead of statically parsing them (#11515) --- config/experimental.md | 43 ++++++++++++++++++++++++++++++++++++++++++ guide/cli-generated.md | 7 +++++++ 2 files changed, 50 insertions(+) diff --git a/config/experimental.md b/config/experimental.md index 76d55048..b2f7010e 100644 --- a/config/experimental.md +++ b/config/experimental.md @@ -375,6 +375,49 @@ export default { } ``` +## experimental.recordDependencies 5.0.4 {#experimental-recorddependencies} + +- **Type:** `boolean` +- **Default:** `false` + +Records every file that a test file loads while it runs and uses these records to select tests for [`--changed`](/guide/cli#changed) and [`vitest related`](/guide/cli#vitest-related). Without this option, Vitest transforms the module graph of every test file before the run to find the affected tests. With it, Vitest reads the records from the previous run instead, so the selection does not transform anything. + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + experimental: { + recordDependencies: true, + }, + }, +}) +``` + +The records are stored in `dependencies.json` next to the [results cache](/config/cache) in Vite's `cacheDir`, so this option has no effect if `cache` is disabled. Every `vitest run` with the option enabled updates the records of the test files it ran. A run that skips some tests of a file, for example with [`-t`](/config/testnamepattern) or `.only`, can miss their dynamic imports, so it only adds files to an existing record and never creates one. A test file without a record, for example a new test file or a file that ran in a browser or [typecheck](/guide/testing-types) pool, always runs. + +A record contains: + +- the test file and every module it imported, including dynamic imports that ran +- [setup files](/config/setupfiles), the [custom environment](/config/environment), the [custom runner](/config/runner), [snapshot serializers](/config/snapshotserializers) and their imports +- mock files from the `__mocks__` folder and modules imported by a mock factory +- files that plugins watch with `this.addWatchFile` +- the snapshot file and every file used by [`toMatchFileSnapshot`](/api/expect#tomatchfilesnapshot) + +A change in a [global setup](/config/globalsetup) file or its imports, in the config file and its imports, or in an `.env` file runs every test of the project. + +Because the records come from a real run, they are exact where the static module graph is not. A module replaced with `vi.mock` and a factory does not load its imports, so they are not recorded. A dynamic import inside a function that the test never calls is not recorded either. + +::: warning +The records describe the files that a test loaded during its last run. If a change that adds an import is committed before the test runs again, a later change in the newly imported file does not select the test, because the commit is no longer part of the `--changed` diff. Run the tests once after you change imports to update the records. + +Vitest records only the files it loads itself. Files read with `fs` (for example, fixtures) and the imports of [externalized](/config/server#server-deps-external) modules are not recorded. + +If [`isolate`](/config/isolate) is disabled, test files that run in the same worker share the module graph. A record can then contain modules that a previous test file loaded dynamically, so `--changed` can select more tests than necessary, but never fewer. + +If [`experimental.viteModuleRunner`](#experimental-vitemodulerunner) is disabled, Vitest records the imports with a resolve hook of [`module.registerHooks`](https://nodejs.org/api/module.html#moduleregisterhooksoptions). Node.js versions without it do not write records, so every test runs. +::: + ## experimental.nodeLoader 4.1.0 {#experimental-nodeloader} - **Type:** `boolean` diff --git a/guide/cli-generated.md b/guide/cli-generated.md index 39e8bfdd..9ec6f1d9 100644 --- a/guide/cli-generated.md +++ b/guide/cli-generated.md @@ -1000,6 +1000,13 @@ Controls whether Vitest will use Node.js Loader API to process in-source or mock Custom provider for detecting changed files. (default: `git`) +### experimental.recordDependencies + +- **CLI:** `--experimental.recordDependencies` +- **Config:** [experimental.recordDependencies](/config/experimental#experimental-recorddependencies) + +Record the modules loaded by every test file in the cache and use the records with `--changed` and `--related` instead of the static module graph. A test file without a record always runs. (default: `false`) + ### experimental.preParse - **CLI:** `--experimental.preParse` From e173ef529fa59bb6dbbc3533bf04b159b631369e Mon Sep 17 00:00:00 2001 From: Vladimir Date: Thu, 8 Oct 2026 12:53:27 +0200 Subject: [PATCH 07/51] fix: respect root in projects (#11521) --- config/root.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/root.md b/config/root.md index 3b4a429c..09762dd8 100644 --- a/config/root.md +++ b/config/root.md @@ -8,4 +8,4 @@ outline: deep - **Type:** `string` - **CLI:** `-r `, `--root=` -Project root +Project root. A relative path is resolved against the current working directory, except in [projects](/guide/projects): a project config file resolves it against its own directory, and an inline project resolves it against the root of the config that declares it. From 6129288c903a147f7cefb04cec42cd90982ed006 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Thu, 8 Oct 2026 13:08:32 +0200 Subject: [PATCH 08/51] refactor: resolve maxWorkers once in config (#11524) --- config/coverage.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/config/coverage.md b/config/coverage.md index ff1a22a1..a4d01ad3 100644 --- a/config/coverage.md +++ b/config/coverage.md @@ -439,7 +439,7 @@ Watermarks for statements, lines, branches and functions. See [istanbul document ## coverage.processingConcurrency - **Type:** `boolean` -- **Default:** `Math.min(20, os.availableParallelism?.() ?? os.cpus().length)` +- **Default:** `Math.min(20, os.availableParallelism())` - **Available for providers:** `'v8' | 'istanbul'` - **CLI:** `--coverage.processingConcurrency=` From d3c2af930aa77a60a44f06376b2496facf945999 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Fri, 9 Oct 2026 14:05:09 +0200 Subject: [PATCH 09/51] fix(browser): run browser instances as workers (#11525) --- config/maxworkers.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/config/maxworkers.md b/config/maxworkers.md index acdc4e67..dda66c93 100644 --- a/config/maxworkers.md +++ b/config/maxworkers.md @@ -58,3 +58,9 @@ vitest --maxWorkers=50% ::: Vitest uses [`os.availableParallelism`](https://nodejs.org/api/os.html#osavailableparallelism) to know the maximum amount of parallelism available. + +## Browser Mode + +In [Browser Mode](/guide/browser/), every browser instance (a [project](/guide/projects) with `browser.enabled`, or an entry in [`browser.instances`](/config/browser/instances)) is a worker, so at most `maxWorkers` instances run at the same time. When another instance needs a slot, an idle headless instance is closed and opens again on its next run. An instance that is not headless stays open for the whole session, so its window is not closed between runs. The same number is the budget of pages shared by the open instances: a single instance runs up to `maxWorkers` test files in parallel in its own pages, and several instances split the budget between them, with at least one page each. + +An instance runs a single test file at a time when the browser is not headless, or when the provider does not support parallelism. From f33a7af8f938b25fed28841bd867a0d4062468f2 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2026 09:22:47 +0200 Subject: [PATCH 10/51] chore(deps): update all non-major dependencies (#11550) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bca1fa08..c662a918 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "vite": "^6.3.5", "vite-plugin-pwa": "^1.3.0", "vitepress": "2.0.0-alpha.20", - "vitepress-plugin-group-icons": "^1.7.6", + "vitepress-plugin-group-icons": "^1.7.7", "vitepress-plugin-llms": "^1.14.0", "vitepress-plugin-tabs": "^0.9.1", "vitest": "workspace:*", From 69558b54ed4a27cd94ada0bcb8e6504c35b5a164 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 12 Oct 2026 20:04:45 +0200 Subject: [PATCH 11/51] chore(deps): update oxc packages (#11551) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Vladimir Sheremet --- api/expect-typeof.md | 1 + 1 file changed, 1 insertion(+) diff --git a/api/expect-typeof.md b/api/expect-typeof.md index 2014f517..1770c49a 100644 --- a/api/expect-typeof.md +++ b/api/expect-typeof.md @@ -34,6 +34,7 @@ expectTypeOf({ a: 1, b: 1 }).not.toEqualTypeOf<{ a: number }>() ::: warning DEPRECATED This matcher has been deprecated since expect-type v1.2.0. Use [`toExtend`](#toextend) instead. ::: + This matcher checks if expect type extends provided type. It is different from `toEqual` and is more similar to [expect's](/api/expect) `toMatchObject()`. With this matcher, you can check if an object “matches” a type. ```ts From f9b6d5117fc619ea0aef30e7136ff9f8bf5efe32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Tue, 13 Oct 2026 22:46:16 +0300 Subject: [PATCH 12/51] docs: fix `test.projects` usage, test descriptions (#11565) --- config/name.md | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/config/name.md b/config/name.md index f4d3fff2..f6ba29ed 100644 --- a/config/name.md +++ b/config/name.md @@ -67,12 +67,16 @@ export default defineConfig({ test: { projects: [ { - name: 'unit', - include: ['./test/*.unit.test.js'], + test: { + name: "unit", + include: ["./test/*.unit.test.js"], + }, }, { - name: 'e2e', - include: ['./test/*.e2e.test.js'], + test: { + name: "e2e", + include: ["./test/*.e2e.test.js"], + }, }, ], }, From bba5b41e2141669b2983ad72614d2646c1539a53 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Thu, 15 Oct 2026 11:16:36 +0200 Subject: [PATCH 13/51] fix: set `skip` in `skipIf`, ignore `undefined` options (#11591) --- api/test.md | 6 +++++- guide/test-tags.md | 7 +++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/api/test.md b/api/test.md index 9e729601..04a5b797 100644 --- a/api/test.md +++ b/api/test.md @@ -75,6 +75,8 @@ test('skipped concurrent test', { skip: true, concurrent: true }, () => { They both work in exactly the same way. To use either one is purely a stylistic choice. +Options set to `undefined` are ignored. They don't override values from the chain, the parent suite, or [tags](/guide/test-tags). + ### timeout - **Type:** `number` @@ -372,11 +374,13 @@ test.skipIf(isDev)('prod only test', () => { }) ``` +Unlike `test.skip`, `test.skipIf` always sets the [`skip`](#skip) option: `true` if the condition is truthy, `false` otherwise. A falsy condition overrides `skip` inherited from [tags](/guide/test-tags), the same as `test(name, { skip: false })`. + ## test.runIf - **Alias:** `it.runIf` -Opposite of [test.skipIf](#test-skipif). +Opposite of [test.skipIf](#test-skipif). It always sets the [`skip`](#skip) option: `false` if the condition is truthy, `true` otherwise. ```ts import { assert, test } from 'vitest' diff --git a/guide/test-tags.md b/guide/test-tags.md index 10b19b80..cf2ea1dd 100644 --- a/guide/test-tags.md +++ b/guide/test-tags.md @@ -138,6 +138,13 @@ test('flaky database test', { tags: ['flaky', 'db'], timeout: 120_000 }) // { timeout: 120_000, retry: 3 } ``` +Options set to `undefined` are ignored, so they don't override the values from tags: + +```ts +test('flaky database test', { tags: ['flaky', 'db'], timeout: undefined }) +// { timeout: 30_000, retry: 3 } +``` + ## Using Tags in Tests You can apply tags to individual tests or entire suites using the `tags` option: From 7b1ba1bf1d91b9f10fe444e739a7e0e9c7dec721 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Thu, 15 Oct 2026 15:23:59 +0200 Subject: [PATCH 14/51] fix(browser): run global setup once with instances (#11596) --- config/browser/instances.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/config/browser/instances.md b/config/browser/instances.md index 446545ea..ea924b39 100644 --- a/config/browser/instances.md +++ b/config/browser/instances.md @@ -38,6 +38,8 @@ export default defineConfig({ For more examples, refer to the ["Multiple Setups" guide](/guide/browser/multiple-setups). ::: +The [`globalSetup`](/config/globalsetup) files of the config that defines `instances` run once for all of them, not once per instance, and receive the project of that config. Values they provide with `project.provide` are available in every instance. Global setup files defined in an instance run only for that instance. + List of available `browser` options: - `browser` (the name of the browser) From 9beee5f46d8c01c749b63bb0238e364ca184e09f Mon Sep 17 00:00:00 2001 From: Vladimir Date: Thu, 15 Oct 2026 16:04:49 +0200 Subject: [PATCH 15/51] docs: explain outer-realm values in vm pools (#11608) --- config/pool.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/config/pool.md b/config/pool.md index 1980e84c..8c6a37de 100644 --- a/config/pool.md +++ b/config/pool.md @@ -47,6 +47,17 @@ catch (err) { } ``` +- Objects and arrays created by Node.js APIs and by the test environment come from outside of the sandbox too. This includes the result of `await response.json()` on a `Response` or a `Request`, `structuredClone()`, `path.parse()` and DOM methods like `element.getAttributeNames()`. Their prototype is not the `Object.prototype` or `Array.prototype` of your tests, so [`toStrictEqual`](/api/expect#tostrictequal) does not consider them equal to the same literal, and libraries that detect plain objects by their prototype treat them as class instances. If your tests depend on these values, run them in the `forks` or `threads` pool, or parse fetch responses inside the sandbox in a [setup file](/config/setupfiles): + +```ts [vitest.setup.ts] +Response.prototype.json = async function () { + return JSON.parse(await this.text()) +} +Request.prototype.json = async function () { + return JSON.parse(await this.text()) +} +``` + - Importing ES modules caches them indefinitely which introduces memory leaks if you have a lot of contexts (test files). There is no API in Node.js that clears that cache. - Accessing globals [takes longer](https://github.com/nodejs/node/issues/31658) in a sandbox environment. From b737fcc5ee709bb98f536ce03ecf53029bf33236 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 19 Oct 2026 17:36:34 +0900 Subject: [PATCH 16/51] fix(browser): change default port to 23315 (#11640) Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 --- config/api.md | 2 +- guide/browser/index.md | 2 +- guide/migration/index.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/config/api.md b/config/api.md index 6bf6ea6e..8c973dbb 100644 --- a/config/api.md +++ b/config/api.md @@ -9,7 +9,7 @@ outline: deep - **Default:** `false` - **CLI:** `--api`, `--api.port`, `--api.host`, `--api.strictPort` -Listen to port and serve API for [the UI](/guide/ui) or [browser server](/guide/browser/). When set to `true`, the default port is `51204` or `63315` if running in Browser Mode. +Listen to port and serve API for [the UI](/guide/ui) or [browser server](/guide/browser/). When set to `true`, the default port is `51204` or `23315` if running in Browser Mode. In Browser Mode the server binds its port on the first browser launch, unless `api` or `ui` is enabled. diff --git a/guide/browser/index.md b/guide/browser/index.md index e2fe7349..ccc3061d 100644 --- a/guide/browser/index.md +++ b/guide/browser/index.md @@ -138,7 +138,7 @@ export default defineConfig({ ``` ::: info -Vitest assigns port `63315` to avoid conflicts with the development server, allowing you to run both in parallel. You can change that with the [`api`](/config/api) option. The port is bound when the first browser is launched, or on startup if `api` or `ui` is enabled. +Vitest assigns port `23315` to avoid conflicts with the development server, allowing you to run both in parallel. You can change that with the [`api`](/config/api) option. The port is bound when the first browser is launched, or on startup if `api` or `ui` is enabled. ::: If you have not used Vite before, make sure you have your framework's plugin installed and specified in the config. Some frameworks might require extra configuration to work - check their Vite related documentation to be sure. diff --git a/guide/migration/index.md b/guide/migration/index.md index cdd4e257..8ea3253b 100644 --- a/guide/migration/index.md +++ b/guide/migration/index.md @@ -531,7 +531,7 @@ If you manually opened the browser preview by copying the Vite server URL or vis ## `browser.api` Is Replaced by the Top-Level `api` -Browser mode now runs on a single Vite server configured by the top-level [`api`](/config/api) option; the default port in browser mode is still `63315`. The `browser.api` option is deprecated and no longer has any effect, so move its value to `api`: +Browser mode now runs on a single Vite server configured by the top-level [`api`](/config/api) option; the default port in browser mode is `23315`. The `browser.api` option is deprecated and no longer has any effect, so move its value to `api`: ```ts [vitest.config.ts] import { defineConfig } from 'vitest/config' From f145e83f86f18494bb9033436b4e8d6aee8f33b3 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 19 Oct 2026 10:41:41 +0200 Subject: [PATCH 17/51] feat(browser): mock modules through an import map (#11534) --- .vitepress/config.ts | 4 ++++ config/browser/importmapmocks.md | 32 ++++++++++++++++++++++++++++++++ guide/cli-generated.md | 7 +++++++ guide/mocking/modules.md | 2 +- 4 files changed, 44 insertions(+), 1 deletion(-) create mode 100644 config/browser/importmapmocks.md diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 6e3ea4d9..dd00153a 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -716,6 +716,10 @@ export default ({ mode }: { mode: string }) => { text: 'browser.trackUnhandledErrors', link: '/config/browser/trackunhandlederrors', }, + { + text: 'browser.importMapMocks', + link: '/config/browser/importmapmocks', + }, { text: 'browser.expect', link: '/config/browser/expect', diff --git a/config/browser/importmapmocks.md b/config/browser/importmapmocks.md new file mode 100644 index 00000000..7498ec95 --- /dev/null +++ b/config/browser/importmapmocks.md @@ -0,0 +1,32 @@ +--- +title: browser.importMapMocks | Config +outline: deep +--- + +# browser.importMapMocks 5.1.0 + +- **Type:** `boolean` +- **Default:** enabled when the browser supports it +- **CLI:** `--browser.importMapMocks`, `--browser.importMapMocks=false` + +Serve module mocks through an [import map](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type/importmap) in the tester document instead of intercepting network requests. + +By default, Vitest checks once per browser whether it accepts an import map after a module has loaded. If it does not, Vitest intercepts requests instead. + +An import map rule cannot be removed from the document. When [`isolate`](/config/isolate) is `false`, all test files run in the same document, so Vitest also intercepts requests by default. + +Set the option to `true` to always use import maps, or to `false` to always intercept requests. + +When a test file calls `vi.mock`, Vitest adds an import map rule that points the module URL to its mocked version: the automocked module, the file in the `__mocks__` folder, or a module that exports every name of the original and reads the values from the factory. The browser loads the mocked module itself, so the provider does not intercept requests (for example, with Playwright's `page.route`) and the mock stays local to the document of the test file. + +Request interception adds a cost to every request in the page, which grows with the number of modules a test file imports. An import map has no per-request cost. + +::: warning +The browser must support multiple import maps per document: Chromium 133+, Safari 18.4+, and Firefox 150+ with the `dom.multiple_import_maps.enabled` preference. The `playwright` provider sets this preference. Other providers cannot, so Firefox falls back to request interception there. + +An import map rule cannot change a module that the document already imported. If a module was imported before `vi.mock` registered it, for example by a setup file that does not mock it, Vitest reports an error instead of silently using the original module. + +An import map rule cannot be removed either. When a test file calls `vi.unmock` with a static path, the setup files do not add a rule for that module, so both the setup files and the test file load the original module. `vi.doUnmock` cannot restore the original module. + +A factory can only provide the exports that the original module declares. Vitest reads the export names from the original module, so a key that the factory adds cannot be imported, and an export that the factory omits is `undefined`. +::: diff --git a/guide/cli-generated.md b/guide/cli-generated.md index 9ec6f1d9..ce05f467 100644 --- a/guide/cli-generated.md +++ b/guide/cli-generated.md @@ -395,6 +395,13 @@ Serve sourcemaps of dependencies to the browser in headless runs, used by devtoo Control if Vitest catches uncaught exceptions so they can be reported (default: `true`) +### browser.importMapMocks + +- **CLI:** `--browser.importMapMocks` +- **Config:** [browser.importMapMocks](/config/browser/importmapmocks) + +Serve module mocks through an import map instead of request interception. By default, enabled when the browser supports it + ### browser.trace - **CLI:** `--browser.trace ` diff --git a/guide/mocking/modules.md b/guide/mocking/modules.md index 61edaca9..f168f3aa 100644 --- a/guide/mocking/modules.md +++ b/guide/mocking/modules.md @@ -326,7 +326,7 @@ If module runner is [disabled](/config/experimental#experimental-vitemodulerunne ### Browser Mode -Vitest uses native ESM in the Browser Mode. This means that we cannot replace the module so easily. Instead, Vitest intercepts the fetch request (via playwright's `page.route` or a Vite plugin API if using `preview` or `webdriverio`) and serves transformed code, if the module was mocked. +Vitest uses native ESM in the Browser Mode. This means that we cannot replace the module so easily. Instead, Vitest intercepts the fetch request (via playwright's `page.route` or a Vite plugin API if using `preview` or `webdriverio`) and serves transformed code, if the module was mocked. With the experimental [`browser.importMapMocks`](/config/browser/importmapmocks) option, mocked modules are served through an import map in the tester document instead, so no request is intercepted. For example, if the module is automocked, Vitest can parse static exports and create a placeholder module: From 299b18138992efad5f0d8bfdfa4fe55e1d4483f6 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 19 Oct 2026 10:43:18 +0200 Subject: [PATCH 18/51] feat: add `--last-failed` to run only failed tests (#11558) --- .vitepress/scripts/cli-generator.ts | 1 + api/advanced/test-specification.md | 4 ++++ api/advanced/vitest.md | 10 ++++++++-- config/cache.md | 4 ++-- guide/cli-generated.md | 6 ++++++ guide/cli.md | 15 +++++++++++++++ 6 files changed, 36 insertions(+), 4 deletions(-) diff --git a/.vitepress/scripts/cli-generator.ts b/.vitepress/scripts/cli-generator.ts index 717b037b..50055c7a 100644 --- a/.vitepress/scripts/cli-generator.ts +++ b/.vitepress/scripts/cli-generator.ts @@ -25,6 +25,7 @@ const skipConfig = new Set([ 'coverage.thresholds.functions', 'coverage.thresholds.lines', 'standalone', + 'lastFailed', 'clearScreen', 'configLoader', 'color', diff --git a/api/advanced/test-specification.md b/api/advanced/test-specification.md index adeeb398..e542769e 100644 --- a/api/advanced/test-specification.md +++ b/api/advanced/test-specification.md @@ -86,6 +86,10 @@ A regexp that matches the name of the test in this module. This value will overr The ids of tasks inside of this specification to run. +## testNames 5.1.0 {#testnames} + +The full names of the tests inside of this specification to run, including the parent suites separated with `>`. + ## testTagsFilter 4.1.0 {#testtagsfilter} The [tags filter](/guide/test-tags#syntax) that a test must pass in order to be included in the run. Multiple filters are treated as `AND`. diff --git a/api/advanced/vitest.md b/api/advanced/vitest.md index 2e781faa..d3dcbee1 100644 --- a/api/advanced/vitest.md +++ b/api/advanced/vitest.md @@ -44,7 +44,7 @@ You can get the latest summary of snapshots via the `vitest.snapshot.summary` pr ## cache -Cache manager that stores the results of the previous test runs. In Vitest itself this is only used by the default sequencer to sort tests. +Cache manager that stores the results of the previous test runs. Vitest uses it to sort tests in the default sequencer and to select tests with [`--last-failed`](/guide/cli#last-failed). ### cache.getTestSpecificationResult 5.0.4 {#cache-gettestspecificationresult} @@ -58,7 +58,7 @@ function getTestSpecificationResult( interface CachedTestFileResult { /** * The file has a known failure. - * Only a complete run of the file clears it. + * A complete run of the file clears it, or a run in which every known failed test passes. */ failed: boolean /** @@ -70,6 +70,12 @@ interface CachedTestFileResult { * It is not set if the file never ran completely. */ lastRun?: number + /** + * Full names of the tests that failed in the last run of the file. + * `--last-failed` runs only these tests. + * It is not set if the failure happened outside of tests, for example in a hook or during collection. + */ + failedTests?: string[] } ``` diff --git a/config/cache.md b/config/cache.md index 95da2519..f0555d89 100644 --- a/config/cache.md +++ b/config/cache.md @@ -9,9 +9,9 @@ outline: deep - **Default:** `true` - **CLI:** `--cache`, `--no-cache` -Store the results of test runs on the file system. Vitest uses them to run failed and longer test files first. +Store the results of test runs on the file system. Vitest uses them to run failed and longer test files first and to select tests with [`--last-failed`](/guide/cli#last-failed). -For every test file, Vitest stores whether the file failed, how long it ran and when it ran last. A run that did not execute the whole file (for example, a run filtered with [`--testNamePattern`](/config/testnamepattern) or cancelled by [`bail`](/config/bail)) can mark the file as failed, but it cannot mark it as passed. +For every test file, Vitest stores whether the file failed, which tests failed, how long it ran and when it ran last. A run that did not execute the whole file (for example, a run filtered with [`--testNamePattern`](/config/testnamepattern) or cancelled by [`bail`](/config/bail)) can mark the file as failed, but it cannot mark it as passed. You can delete the cache by running [`vitest --clearCache`](/guide/cli#clearcache). diff --git a/guide/cli-generated.md b/guide/cli-generated.md index ce05f467..5198d6bd 100644 --- a/guide/cli-generated.md +++ b/guide/cli-generated.md @@ -521,6 +521,12 @@ Ignore any unhandled errors that occur Run tests that are affected by the changed files (default: `false`) +### lastFailed + +- **CLI:** `--lastFailed` + +Run only the tests that failed in the last run (default: `false`) + ### sequence.shuffle.files - **CLI:** `--sequence.shuffle.files` diff --git a/guide/cli.md b/guide/cli.md index 14d6bcc2..d63373e4 100644 --- a/guide/cli.md +++ b/guide/cli.md @@ -271,3 +271,18 @@ Merges every blob report located in the specified folder (`.vitest/blob/` by def ```sh vitest --merge-reports --reporter=junit ``` + +### last-failed + +- **Type:** `boolean` +- **Default:** `false` + +Run only the tests that failed in the last run. Vitest reads the failures from the [results cache](/config/cache) and skips every other test file and test: + +```sh +vitest run --last-failed +``` + +A test file runs completely if it failed outside of a test, for example in a `beforeAll` hook or during collection. Vitest selects the tests by their full names, so a renamed test does not run until the file runs again without the flag. If no test failed in the last run, Vitest prints `No failed tests found` and exits with code 1 unless [`--passWithNoTests`](/config/passwithnotests) is set. + +The flag can be combined with other filters. For example, `vitest run --last-failed -t login` runs only the failed tests whose names match `login`. In watch mode, only the first run is limited to the failed tests. From 8ff9f478c368947d5e7433e292c63d235bdce17a Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 19 Oct 2026 17:43:42 +0900 Subject: [PATCH 19/51] feat(browser): drop experimental label from trace view (#11518) Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 --- config/browser/traceview.md | 2 +- guide/browser/trace-view.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/config/browser/traceview.md b/config/browser/traceview.md index 558eb0ab..73ff528e 100644 --- a/config/browser/traceview.md +++ b/config/browser/traceview.md @@ -3,7 +3,7 @@ title: browser.traceView | Config outline: deep --- -# browser.traceView 5.0.0 +# browser.traceView 5.0.0 - **Type:** `boolean | { enabled?: boolean; recordCanvas?: boolean; inlineImages?: boolean }` - **CLI:** `--browser.traceView` diff --git a/guide/browser/trace-view.md b/guide/browser/trace-view.md index 59c1f97f..e3f6fa44 100644 --- a/guide/browser/trace-view.md +++ b/guide/browser/trace-view.md @@ -1,4 +1,4 @@ -# Trace View 5.0.0 +# Trace View 5.0.0 `browser.traceView` records browser interactions as DOM snapshots and lets you replay them step by step in Vitest's built-in trace viewer. It is useful when the live browser view is not enough: you can inspect earlier tests, failed retries, screenshots, assertions, and user actions after the browser has already moved on. From 9a2a0565ecfce4835be89036ee569e497fb97107 Mon Sep 17 00:00:00 2001 From: Prajwal Janbandhu Date: Mon, 19 Oct 2026 14:15:22 +0530 Subject: [PATCH 20/51] feat: support `Temporal.Instant` and `Temporal.ZonedDateTime` in `vi.setSystemTime()` (#11321) Co-authored-by: Prajwal2246 <213174436+Prajwal2246@users.noreply.github.com> Co-authored-by: Claude Opus 5 (1M context) Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: Codex (GPT-6) --- api/vi.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/api/vi.md b/api/vi.md index 43760c14..953ef15d 100644 --- a/api/vi.md +++ b/api/vi.md @@ -1172,14 +1172,14 @@ await vi.runOnlyPendingTimersAsync() ### vi.setSystemTime ```ts -function setSystemTime(date: string | number | Date): Vitest +function setSystemTime(date: string | number | Date | Temporal.Instant | Temporal.ZonedDateTime): Vitest ``` If fake timers are enabled, this method simulates a user changing the system clock (will affect date related API like `hrtime`, `performance.now` or `new Date()`) - however, it will not fire any timers. If fake timers are not enabled, this method will only mock `Date.*` and `Temporal.Now.*` calls. Useful if you need to test anything that depends on the current date - for example [Luxon](https://github.com/moment/luxon/) calls inside your code. -Accepts the same string and number arguments as the `Date`. +Accepts the same string and number arguments as the `Date`, or a `Temporal.Instant` or `Temporal.ZonedDateTime`. Vitest uses the moment represented by a `ZonedDateTime` as the mocked time and leaves the environment's time zone unchanged. ```ts const date = new Date(1998, 11, 19) @@ -1202,6 +1202,8 @@ To enable mocking timers, you need to call this method. It will wrap all further Mocking `nextTick` is not supported when running Vitest inside `node:child_process` by using `--pool=forks`. NodeJS uses `process.nextTick` internally in `node:child_process` and hangs when it is mocked. Mocking `nextTick` is supported when running Vitest with `--pool=threads`. +The `now` option accepts a `Date`, number, `Temporal.Instant`, or `Temporal.ZonedDateTime`. Vitest uses the moment represented by a `ZonedDateTime` as the mocked time and leaves the environment's time zone unchanged. + The implementation is based internally on [`@sinonjs/fake-timers`](https://github.com/sinonjs/fake-timers). ::: tip From 3accf71e3645859c3872e6e5404c4cf2105a9d91 Mon Sep 17 00:00:00 2001 From: Raul Macarie Date: Mon, 19 Oct 2026 10:47:07 +0200 Subject: [PATCH 21/51] feat(browser): introduce experimental `pointer` API (#10780) --- api/browser/interactivity.md | 52 ++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/api/browser/interactivity.md b/api/browser/interactivity.md index b403cdcd..38585aaa 100644 --- a/api/browser/interactivity.md +++ b/api/browser/interactivity.md @@ -235,6 +235,58 @@ await page.getByRole('tablist').wheel({ direction: 'right' }) This method is intended for testing UI that explicitly listens to `wheel` events (e.g., custom zoom controls, horizontal tab scrolling, canvas interactions). If you need to scroll the page to bring an element into view, rely on the built-in automatic scrolling functionality provided by other `userEvent` methods or [locator actions](/api/browser/locators#methods) instead. ::: +## userEvent.pointer 5.1.0 {#userevent-pointer} + +```ts +function pointer(options: UserEventPointerInput): Promise +``` + +Performs one or more pointer actions. Pass a key string, an action object, or an array of strings and objects. Key strings use [user-event `pointer` syntax](https://testing-library.com/docs/user-event/pointer). For example, `[MouseLeft]` presses and releases the left mouse button, `[MouseLeft>]` keeps it pressed, and `[/MouseLeft]` releases it. + +::: tip +The `pointer` method is a low-level API for composing complex pointer interactions and performing actions that dedicated methods do not support, such as dragging an element to a precise position. + +For one-off actions, prefer the equivalent dedicated method because in most cases they are simpler to use. For example, if you just want to click an element once, use [`userEvent.click`](#userevent-click). +::: + +An action object accepts `keys`, `target`, and `coords`. An object without `keys` only moves the pointer. + +The pointer position persists between `userEvent.pointer` calls on the same `userEvent` instance. This means that if an action omits both `target` and `coords`, it uses the position from the previous action. + +::: warning +When the [Browser UI](/config/browser/ui) is enabled, the test iframe can be scaled to fit the available space. Vitest adjusts pointer coordinates to account for the scaling, but browser pixel rounding can cause the resulting event coordinates to differ by several _iframe pixels_. **Avoid relying on exact coordinates in this mode**. +::: + +```ts +import { page, userEvent } from 'vitest/browser' + +test('drags an element with pointer actions', async () => { + const source = page.getByTestId('source') + const target = page.getByTestId('target') + + await userEvent.pointer([ + { target: source, keys: '[MouseLeft>]' }, + { target, coords: { x: 10, y: 10 }, keys: '[/MouseLeft]' }, + ]) +}) +``` + +::: warning Provider support and differences from user-event +This API is not consistently supported across providers, and its behavior can differ from the `user-event` library. + +- The `playwright` provider does not support touch pointer keys such as `TouchA`. +- The `webdriverio` provider does not support this method at all. +- The `preview` provider simulates events using `@testing-library/user-event`. + +Vitest does not support the `node` and `offset` fields used for text selection and only accepts `x` and `y` values in `coords`. + +Without `target`, `coords` values are considered coordinates relative to the viewport. With `target`, they are offsets from its top-left corner. Unlike user-event, where coordinates paired with a target describe the final pointer position rather than an offset. When `coords` is provided, an omitted axis defaults to `0`. +::: + +References: + +- [testing-library `pointer` API](https://testing-library.com/docs/user-event/pointer) + ## userEvent.fill ```ts From 063d3059e129a7287965de317c4d4f18ef3bec5c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Mon, 19 Oct 2026 10:53:08 +0200 Subject: [PATCH 22/51] fix(deps): update all non-major dependencies (#11637) Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: Vladimir Sheremet --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index c662a918..1ec4a89d 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "vite": "^6.3.5", "vite-plugin-pwa": "^1.3.0", "vitepress": "2.0.0-alpha.20", - "vitepress-plugin-group-icons": "^1.7.7", + "vitepress-plugin-group-icons": "^1.8.1", "vitepress-plugin-llms": "^1.14.0", "vitepress-plugin-tabs": "^0.9.1", "vitest": "workspace:*", From 213155c2c5c5d3728e55da9567d6beab163e07ff Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 19 Oct 2026 17:57:44 +0900 Subject: [PATCH 23/51] feat(browser): capture aria snapshot in trace view (#11492) Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 --- config/browser/traceview.md | 11 ++++++++++- guide/browser/trace-view.md | 4 ++++ guide/cli-generated.md | 7 +++++++ 3 files changed, 21 insertions(+), 1 deletion(-) diff --git a/config/browser/traceview.md b/config/browser/traceview.md index 73ff528e..a186eded 100644 --- a/config/browser/traceview.md +++ b/config/browser/traceview.md @@ -5,7 +5,7 @@ outline: deep # browser.traceView 5.0.0 -- **Type:** `boolean | { enabled?: boolean; recordCanvas?: boolean; inlineImages?: boolean }` +- **Type:** `boolean | { enabled?: boolean; recordCanvas?: boolean; inlineImages?: boolean; ariaSnapshot?: boolean }` - **CLI:** `--browser.traceView` - **Default:** `false` @@ -42,6 +42,7 @@ export default defineConfig({ | `enabled` | `false` | Enables Vitest trace-view artifact collection. | | `inlineImages` | `false` | Inlines loaded `` pixels into snapshots for more portable replay, useful in the HTML reporter. | | `recordCanvas` | `false` | Captures canvas pixels in snapshots. | +| `ariaSnapshot` | `true` | Captures the accessibility tree alongside each snapshot. | ## browser.traceView.enabled {#traceview-enabled} @@ -67,4 +68,12 @@ Inlines loaded `` pixels into snapshots for more portable replay, useful in Captures canvas pixels in snapshots. This enables a weaker replay iframe sandbox because rrweb needs scripts to redraw canvas data. +## browser.traceView.ariaSnapshot 5.1.0 {#traceview-ariasnapshot} + +- **Type:** `boolean` +- **Default:** `true` +- **CLI:** `--browser.traceView.ariaSnapshot` + +Captures the accessibility tree of the page alongside each snapshot. The trace viewer header can then switch from the DOM snapshot to an **Aria** view, which uses the same format as [aria snapshots](/guide/browser/aria-snapshots). + See [Trace View](/guide/browser/trace-view) for full documentation. diff --git a/guide/browser/trace-view.md b/guide/browser/trace-view.md index e3f6fa44..e4863f10 100644 --- a/guide/browser/trace-view.md +++ b/guide/browser/trace-view.md @@ -149,6 +149,10 @@ test('shows button', async () => { Each attempt — retry or repeat — is recorded as a separate trace. When a test has multiple attempts, the viewer opens the most recent one by default. You can switch between attempts in the Report tab. +## Accessibility Tree 5.1.0 {#accessibility-tree} + +Trace view also captures the page's accessibility tree at each step. The trace viewer header shows a view selector to switch the snapshot pane between **DOM** and **Aria**. The Aria view lists the roles, accessible names, and states that role-based locators such as `getByRole()` see, in the same format as [aria snapshots](/guide/browser/aria-snapshots). Set [`traceView.ariaSnapshot`](/config/browser/traceview#traceview-ariasnapshot) to `false` to disable it. + ## Snapshot Fidelity By default, trace view captures the DOM tree, attributes, form values, same-origin readable CSS, element scroll positions, viewport size, and window scroll position. Images and canvas pixels are not inlined by default. diff --git a/guide/cli-generated.md b/guide/cli-generated.md index 5198d6bd..1c6b5dd0 100644 --- a/guide/cli-generated.md +++ b/guide/cli-generated.md @@ -430,6 +430,13 @@ Capture canvas pixels in trace-view snapshots (default: `false`) Inline loaded image pixels in trace-view snapshots (default: `false`) +### browser.traceView.ariaSnapshot + +- **CLI:** `--browser.traceView.ariaSnapshot` +- **Config:** [browser.traceView.ariaSnapshot](/config/browser/traceview#traceview-ariasnapshot) + +Capture the accessibility tree in trace-view snapshots (default: `true`) + ### browser.locators.exact - **CLI:** `--browser.locators.exact` From 1ec0fdc7ec7c6b1c2cc8e9e09c1380f523c28d97 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 19 Oct 2026 11:03:38 +0200 Subject: [PATCH 24/51] feat: introduce `--since-last-run` flag (#11516) --- .vitepress/config.ts | 4 ++ .vitepress/scripts/cli-generator.ts | 2 +- config/experimental.md | 54 ++++++++++++++++++++--- guide/cli-generated.md | 2 +- guide/cli.md | 21 +++++++++ guide/recipes/affected-tests.md | 66 +++++++++++++++++++++++++++++ 6 files changed, 141 insertions(+), 8 deletions(-) create mode 100644 guide/recipes/affected-tests.md diff --git a/.vitepress/config.ts b/.vitepress/config.ts index dd00153a..c5021809 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -1096,6 +1096,10 @@ export default ({ mode }: { mode: string }) => { text: 'Parallel and Sequential Test Files', link: '/guide/recipes/parallel-sequential', }, + { + text: 'Running Only Affected Tests', + link: '/guide/recipes/affected-tests', + }, ], }, { diff --git a/.vitepress/scripts/cli-generator.ts b/.vitepress/scripts/cli-generator.ts index 50055c7a..b0931fb6 100644 --- a/.vitepress/scripts/cli-generator.ts +++ b/.vitepress/scripts/cli-generator.ts @@ -9,7 +9,7 @@ const cliTablePath = resolve(docsDir, './guide/cli-generated.md') const nonNullable = (value: T): value is NonNullable => value !== null && value !== undefined -const skipCli = new Set(['mergeReports', 'shard']) +const skipCli = new Set(['mergeReports', 'shard', 'sinceLastRun']) const skipConfig = new Set([ 'config', diff --git a/config/experimental.md b/config/experimental.md index b2f7010e..d2ecfc65 100644 --- a/config/experimental.md +++ b/config/experimental.md @@ -316,7 +316,7 @@ If you are running tests in Deno, TypeScript files are processed by the runtime ## experimental.vcsProvider 4.1.1 {#experimental-vcsprovider} -- **Type:** `VCSProvider | string` +- **Type:** `'git' | 'mtime' | VCSProvider | string` ```ts interface VCSProvider { @@ -326,14 +326,55 @@ interface VCSProvider { interface VCSProviderOptions { root: string changedSince?: string | boolean + /** Resolves the dependencies of the test files of the run. */ + resolver: ModulesResolver +} + +interface ModulesResolver { + /** + * Every file whose change can affect the test files of the run. + */ + getDependencies(): Promise +} + +interface ModuleDependency { + /** Absolute path of the file. */ + file: string + /** + * Start time of the oldest run that recorded the file, in milliseconds. + * Not set when the dependency comes from the static module graph. + */ + recordedAt?: number } ``` - **Default:** `'git'` -Custom provider for detecting changed files. Used with the [`--changed`](/guide/cli#changed) flag to determine which files have been modified. +Provider for detecting changed files. Used with the [`--changed`](/guide/cli#changed) flag to determine which files have been modified. + +Vitest has two built-in providers: + +- `'git'` returns the files that differ from the `--changed` reference, plus staged and untracked files. +- `'mtime'` 5.1.0 returns the recorded dependencies whose modification time is newer than the run that recorded them, and deleted dependencies. It needs [`experimental.recordDependencies`](#experimental-recorddependencies): a dependency without a record is always reported as changed, so every test runs until the records exist. It does not need Git and ignores the `--changed` reference. A `--changed` run updates the records of the tests it ran, so the next `--changed` run selects only the tests whose dependencies changed since, and the test files that failed in their last run. + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + experimental: { + recordDependencies: true, + vcsProvider: 'mtime', + }, + }, +}) +``` + +::: warning +The `mtime` provider compares file times with the clock of the machine that ran the tests. A checkout or a copy that sets file times in the past is not detected. +::: -By default, Vitest uses Git to detect changed files. You can provide a custom implementation of the `VCSProvider` interface to use a different version control system: +You can provide a custom implementation of the `VCSProvider` interface to use a different version control system. `options.resolver.getDependencies()` returns every file the test files of the run depend on, so a provider can limit its work to these files: ```ts [vitest.config.ts] import { defineConfig } from 'vitest/config' @@ -342,8 +383,9 @@ export default defineConfig({ test: { experimental: { vcsProvider: { - async findChangedFiles({ root, changedSince }) { - // return paths of changed files + async findChangedFiles({ root, changedSince, resolver }) { + const dependencies = await resolver.getDependencies() + // return the paths of the changed files return [] }, }, @@ -375,7 +417,7 @@ export default { } ``` -## experimental.recordDependencies 5.0.4 {#experimental-recorddependencies} +## experimental.recordDependencies 5.1.0 {#experimental-recorddependencies} - **Type:** `boolean` - **Default:** `false` diff --git a/guide/cli-generated.md b/guide/cli-generated.md index 1c6b5dd0..799a387e 100644 --- a/guide/cli-generated.md +++ b/guide/cli-generated.md @@ -1018,7 +1018,7 @@ Controls whether Vitest will use Node.js Loader API to process in-source or mock - **CLI:** `--experimental.vcsProvider ` - **Config:** [experimental.vcsProvider](/config/experimental#experimental-vcsprovider) -Custom provider for detecting changed files. (default: `git`) +Provider for detecting changed files: `git`, `mtime` (requires `experimental.recordDependencies`) or a path to a custom provider. (default: `git`) ### experimental.recordDependencies diff --git a/guide/cli.md b/guide/cli.md index d63373e4..f9b80c99 100644 --- a/guide/cli.md +++ b/guide/cli.md @@ -272,6 +272,27 @@ Merges every blob report located in the specified folder (`.vitest/blob/` by def vitest --merge-reports --reporter=junit ``` +### since-last-run 5.1.0 {#since-last-run} + +- **Type:** `boolean` +- **Default:** `false` + +Run only the tests whose dependencies were modified since the last run of the test. A test is selected when one of the files it loaded during its last run has a newer modification time than that run, or was deleted. A test that never ran with this flag has no record and always runs. A test file that failed in its last run also runs, until it passes, because the cause of a failure can be outside the recorded files, for example an environment variable or a server that was down. + +```sh +vitest run --since-last-run +``` + +The flag enables [`changed`](/config/changed), [`experimental.recordDependencies`](/config/experimental#experimental-recorddependencies) and the `mtime` [provider](/config/experimental#experimental-vcsprovider). Every run with this flag updates the records of the tests it ran, so the next run selects only the tests affected by changes made in between. To run every test again, run Vitest without the flag, or delete the cache with [`--clearCache`](#clearcache). + +The flag requires [`cache`](/config/cache). Vitest throws an error if the cache is disabled. + +::: warning +The modification time comes from the file system. A `git checkout` or an unpacked archive that sets old modification times is not detected. Files that a test did not load during its last run, for example a new `.env` file or a new `__mocks__` file, are not detected either. See [`experimental.recordDependencies`](/config/experimental#experimental-recorddependencies) for the full list of recorded files. +::: + +See [Running Only Affected Tests](/guide/recipes/affected-tests) for a comparison with `--changed`. + ### last-failed - **Type:** `boolean` diff --git a/guide/recipes/affected-tests.md b/guide/recipes/affected-tests.md new file mode 100644 index 00000000..ac7973e5 --- /dev/null +++ b/guide/recipes/affected-tests.md @@ -0,0 +1,66 @@ +--- +title: Running Only Affected Tests | Recipes +--- + +# Running Only Affected Tests + +A full run of a large suite is the right check before a merge, but it is slow feedback for a small change. If you edited one module, only the tests that load that module can change their result. Vitest has two flags that select those tests. They differ in one question: changed compared to what? + +- [`--changed`](/config/changed) compares the working tree with a Git reference. +- [`--since-last-run`](/guide/cli#since-last-run) compares every file a test loaded with the time of the test's last run. + +## `--changed`: what differs from a Git reference + +```sh +vitest run --changed # uncommitted changes, staged and unstaged +vitest run --changed HEAD~1 # the last commit and uncommitted changes +vitest run --changed origin/main # everything on this branch +``` + +Vitest asks Git for the changed files, then finds the test files that import them, directly or through other modules. A test runs when any file in its import graph is in the list. + +The reference is the baseline, so the same command gives the same selection until the baseline moves. This fits checks that are defined in terms of Git: a pull request against `origin/main`, or a pre-push hook that covers the commits you are about to push. It requires a Git repository, and a change that is committed and merged stops being "changed", even if the tests never ran against it on your machine. + +## `--since-last-run`: what changed since the test last ran + +```sh +vitest run --since-last-run +``` + +The first run with `--since-last-run` runs every test and records the files each test loaded, including dynamic imports, mocks, setup files and snapshot files. Every later run compares the modification time of those files with the time of the run that recorded them. A test runs again when one of its files is newer or was deleted, or when the test has no record yet. A test file that failed in its last run also runs again, until it passes, even if the fix was outside the recorded files. + +Each run updates the records of the tests it ran, so the baseline moves with you: after a run, only the next edit selects tests again. Git is not involved, so the flag works in a fresh container, in a worktree with an unrelated history, or in a directory that is not a repository. A new test file, or one that was never recorded, always runs. + +The records live in the [cache](/config/cache). `vitest run --clearCache` deletes them, and the next `--since-last-run` run is a full run again. + +## Which one to pick + +| You want to run the tests affected by… | Use | +| ---------------------------------------------------------------------- | --------------------------------- | +| a branch, before opening or updating a pull request | `--changed origin/main` | +| the commits you are about to push | `--changed ` | +| your uncommitted edits, in a Git repository | `--changed` or `--since-last-run` | +| your edits since you last ran the tests, committed or not | `--since-last-run` | +| edits in a directory without Git, or with a history you do not control | `--since-last-run` | + +`--changed` is the better fit when the baseline is shared with other people: the reference is the same on every machine and in CI. `--since-last-run` is the better fit when the baseline is your own last run: it does not matter whether you committed in between, or whether Git is there at all. + +## Agents + +AI coding agents run tests after almost every edit. They often work in a container or a temporary worktree, commit in the middle of a task, and start a new process for every command. A Git baseline fits none of that: `--changed` is empty right after a commit, and it needs a repository. `--since-last-run` tracks what the agent actually ran, so each `vitest run --since-last-run` covers exactly the edits since the previous one, and the first command in a fresh environment is a full run that builds the records. + +Tell the agent about it in the instructions file of your project: + +```md [AGENTS.md] +- After a change, run `vitest run --since-last-run` to test only what the change affects. +- Before you finish, run `vitest run` once for the whole suite. +``` + +Two things the records cannot see: a file that no test loaded during the last run (a new `.env` file, a new `__mocks__` file), and modification times set in the past by a checkout or an unpacked archive. The full run at the end covers both. + +## See also + +- [`changed`](/config/changed) +- [`--since-last-run`](/guide/cli#since-last-run) +- [`experimental.recordDependencies`](/config/experimental#experimental-recorddependencies) +- [Test Filtering](/guide/filtering) From 43f262e88ea49ca437ed53b6310e0c763d110291 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 19 Oct 2026 23:39:49 +0900 Subject: [PATCH 25/51] feat: expose per-retry/repeat attempt result (#11232) Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: Codex (GPT-6) Co-authored-by: Claude Opus 5.5 (1M context) --- api/advanced/runner.md | 31 ++++++++++++++++++++++ api/advanced/test-case.md | 54 ++++++++++++++++++++++++++++++++++++++- 2 files changed, 84 insertions(+), 1 deletion(-) diff --git a/api/advanced/runner.md b/api/advanced/runner.md index f4f6561a..d3c6fd69 100644 --- a/api/advanced/runner.md +++ b/api/advanced/runner.md @@ -268,6 +268,37 @@ export interface TaskResult { * The zero-based index of the current repeat. */ repeatCount?: number + /** + * Individual results for every retry and repeat attempt. + */ + attempts?: TaskResultAttempt[] +} + +export interface TaskResultAttempt { + /** + * The state after applying the test's expected failure option. + */ + state: 'pass' | 'fail' | 'skip' + /** + * Errors produced by this attempt. + */ + errors?: TestError[] + /** + * How long in milliseconds the attempt took to run. + */ + duration: number + /** + * Time in milliseconds when the attempt started running. + */ + startTime: number + /** + * The zero-based retry index within the repeat. + */ + retryIndex: number + /** + * The zero-based repeat index. + */ + repeatIndex: number } ``` diff --git a/api/advanced/test-case.md b/api/advanced/test-case.md index e6d6a824..c07e47fb 100644 --- a/api/advanced/test-case.md +++ b/api/advanced/test-case.md @@ -228,7 +228,7 @@ interface TestResultPassed { ``` ::: warning -Note that the test with `passed` state can still have errors attached - this can happen if `retry` was triggered at least once. +Note that the test with `passed` state can still have errors attached - this can happen if `retry` was triggered at least once. Use [`attempts()`](#attempts) to see which run produced each error. ::: ## diagnostic @@ -278,6 +278,58 @@ interface TestDiagnostic { `diagnostic()` will return `undefined` if the test was not scheduled to run yet. ::: +## attempts 5.1.0 {#attempts} + +```ts +function attempts(): ReadonlyArray +``` + +Results of every run of the test, in execution order. A test runs more than once when [`retry`](/config/retry) or [`repeats`](/api/test#repeats) is configured, and each run produces one entry. Unlike `result()`, which only reports the final outcome and merges errors from all runs, each attempt keeps its own state, errors and timing. + +```ts +interface TestAttempt { + /** + * The state of this attempt. `fails` tests are already inverted, + * so an expected failure is reported as `passed`. + */ + readonly state: 'passed' | 'failed' | 'skipped' + /** + * Errors thrown during this attempt only. + */ + readonly errors: ReadonlyArray | undefined + /** + * How long in milliseconds the attempt took to run. + */ + readonly duration: number + /** + * Time in milliseconds when the attempt started. + */ + readonly startTime: number + /** + * The zero-based retry index within the current repeat. + */ + readonly retryIndex: number + /** + * The zero-based repeat index. + */ + readonly repeatIndex: number +} +``` + +For example, a test with `retry: 2` that passes on its third run returns: + +```ts +[ + { state: 'failed', retryIndex: 0, repeatIndex: 0, errors: [/* ... */] }, + { state: 'failed', retryIndex: 1, repeatIndex: 0, errors: [/* ... */] }, + { state: 'passed', retryIndex: 2, repeatIndex: 0, errors: undefined }, +] +``` + +::: info +`attempts()` returns an empty array if the test has not run yet. +::: + ## annotations ```ts From bdcede2e3aae3bc10d9e8aba61ed692d397e2942 Mon Sep 17 00:00:00 2001 From: Julien Wajsberg Date: Mon, 19 Oct 2026 16:42:10 +0200 Subject: [PATCH 26/51] fix(browser): advance fake timers in actions and expect.element (#11242) Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 --- api/browser/assertions.md | 2 +- api/browser/locators.md | 4 ++++ api/expect.md | 2 ++ api/vi.md | 6 ++++-- guide/mocking/timers.md | 15 +++++++++++++++ 5 files changed, 26 insertions(+), 3 deletions(-) diff --git a/api/browser/assertions.md b/api/browser/assertions.md index bc938d5d..46952bce 100644 --- a/api/browser/assertions.md +++ b/api/browser/assertions.md @@ -55,7 +55,7 @@ interface ExpectPollOptions { ``` ::: tip -Like [`expect.poll`](/api/expect#poll), `expect.element` retries DOM assertions until they pass or the timeout is reached. When it receives a locator, Vitest resolves it with [`locator.findElement()`](/api/browser/locators#findelement) before running the DOM assertion. The `timeout` option applies to the whole retry operation. The `interval` option controls how often failed DOM assertions are retried, but locator resolution uses `findElement`'s own increasing retry intervals. +Like [`expect.poll`](/api/expect#poll), `expect.element` retries DOM assertions until they pass or the timeout is reached. When it receives a locator, Vitest resolves it with [`locator.findElement()`](/api/browser/locators#findelement) before running the DOM assertion. The `timeout` option applies to the whole retry operation. The `interval` option controls how often failed DOM assertions are retried, but locator resolution uses `findElement`'s own increasing retry intervals. If [fake timers](/api/vi#vi-usefaketimers) are enabled, `expect.element` advances them while it retries. See [Automatic Advancing](/guide/mocking/timers#automatic-advancing) for details. `toMatchTextContent` and all other assertions are still available on a regular `expect` without a built-in retry-ability mechanism: diff --git a/api/browser/locators.md b/api/browser/locators.md index c26275ba..07c337a1 100644 --- a/api/browser/locators.md +++ b/api/browser/locators.md @@ -620,6 +620,10 @@ This options narrows down the selector to only match elements that do not contai All methods are asynchronous and must be awaited. Since Vitest 3, tests will fail if a method is not awaited. +::: tip +If [fake timers](/api/vi#vi-usefaketimers) are enabled, these methods advance them while they run. See [Automatic Advancing](/guide/mocking/timers#automatic-advancing) for details. +::: + ### click ```ts diff --git a/api/expect.md b/api/expect.md index 91971aed..407f3081 100644 --- a/api/expect.md +++ b/api/expect.md @@ -118,6 +118,8 @@ The callback receives an `AbortSignal` that is aborted when the poll timeout is If an error is thrown inside the `expect.poll` callback, Vitest will retry again until the timeout runs out. +If `vi.useFakeTimers` is used, `expect.poll` automatically calls `vi.advanceTimersByTime(interval)` after every failed attempt. See [Automatic Advancing](/guide/mocking/timers#automatic-advancing) for details. + ```ts import { expect, test } from 'vitest' diff --git a/api/vi.md b/api/vi.md index 953ef15d..17462444 100644 --- a/api/vi.md +++ b/api/vi.md @@ -1231,7 +1231,7 @@ Controls how fake timers are advanced. - `nextTimerAsync`: Timers will be advanced automatically to the next available timer after each macrotask. - `interval`: Timers are advanced automatically by a specified interval. -When `mode` is `'interval'`, you can also provide an `interval` in milliseconds. +When `mode` is `'interval'`, you can also provide an `interval` in milliseconds. See [Tick Modes](/guide/mocking/timers#tick-modes) for when to use each mode. **Example:** @@ -1346,7 +1346,7 @@ test('Element exists in a DOM', async () => { }) ``` -If `vi.useFakeTimers` is used, `vi.waitFor` automatically calls `vi.advanceTimersByTime(interval)` in every check callback. +If `vi.useFakeTimers` is used, `vi.waitFor` automatically calls `vi.advanceTimersByTime(interval)` in every check callback. See [Automatic Advancing](/guide/mocking/timers#automatic-advancing) for details. ### vi.waitUntil {#vi-waituntil} @@ -1378,6 +1378,8 @@ test('Element render correctly', async () => { }) ``` +If `vi.useFakeTimers` is used, `vi.waitUntil` automatically calls `vi.advanceTimersByTime(interval)` in every check callback. See [Automatic Advancing](/guide/mocking/timers#automatic-advancing) for details. + ### vi.hoisted {#vi-hoisted} ```ts diff --git a/guide/mocking/timers.md b/guide/mocking/timers.md index 29a33c88..599ce192 100644 --- a/guide/mocking/timers.md +++ b/guide/mocking/timers.md @@ -46,3 +46,18 @@ describe('delayed execution', () => { }) }) ``` + +## Automatic Advancing {#automatic-advancing} + +Some Vitest APIs wait for a condition to be met. When fake timers are enabled, these APIs advance them while they run: + +- [`vi.waitFor`](/api/vi#vi-waitfor) and [`vi.waitUntil`](/api/vi#vi-waituntil) advance timers by `interval` before every check. +- [`expect.poll`](/api/expect#poll) advances timers by `interval` after every failed attempt. +- In Browser Mode, [`expect.element`](/api/browser/assertions) advances timers while it retries, and [locator](/api/browser/locators#methods) and [`userEvent`](/api/browser/interactivity) methods that take an element advance them while they run, even when the element already exists. The amount depends on how long the action takes in real time, so it can vary between runs. Methods that don't take an element, such as `userEvent.keyboard` and `userEvent.tab`, don't advance timers. + +## Tick Modes {#tick-modes} + +By default, fake timers only move when you advance them or when one of the APIs above runs. [`vi.setTimerTickMode`](/api/vi#vi-settimertickmode) makes them advance on their own: + +- `interval` advances fake time by a fixed step on a real-time interval, so it stays roughly in step with real time. It suits code where some state only lasts for a limited time. +- `nextTimerAsync` runs the next pending timer after each macrotask, so chained timers fire almost back to back. It suits code that eventually settles, but state that only lasts for a limited time can disappear before an assertion or action sees it. From aff1977612f47e9d46ee595d1f6bde168066ad1a Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 19 Oct 2026 23:42:45 +0900 Subject: [PATCH 27/51] feat: support fake timers for `node:timers` and `node:timers/promise` (#10899) Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: OpenCode Co-authored-by: Codex (GPT-6) --- config/faketimers.md | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/config/faketimers.md b/config/faketimers.md index 14c4544e..fc8c4ef1 100644 --- a/config/faketimers.md +++ b/config/faketimers.md @@ -7,7 +7,7 @@ outline: deep - **Type:** `FakeTimerConfig` -Options that Vitest will pass down to [`@sinon/fake-timers`](https://npmx.dev/package/@sinonjs/fake-timers) when using [`vi.useFakeTimers()`](/api/vi#vi-usefaketimers). +Options that configure fake timers when using [`vi.useFakeTimers()`](/api/vi#vi-usefaketimers). Vitest's fake timer implementation is based on [`@sinon/fake-timers`](https://npmx.dev/package/@sinonjs/fake-timers), with additional options and behavior tailored to Vitest. ## fakeTimers.now @@ -65,3 +65,10 @@ Relevant only when using with `shouldAdvanceTime: true`. increment mocked time b - **Default:** `true` Tells fake timers to clear "native" (i.e. not fake) timers by delegating to their respective handlers. When disabled, it can lead to potentially unexpected behavior if timers existed prior to starting fake timers session. + +## fakeTimers.fakeNodeBuiltins 5.0.0 {#faketimers-fakenodebuiltins} + +- **Type:** `boolean` +- **Default:** `false` + +Mock timer APIs imported from `node:timers` and `node:timers/promises`. From 3760e1c1c748b0497108f0d945bfc6e2be7f8b43 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 20 Oct 2026 16:48:34 +0900 Subject: [PATCH 28/51] feat(browser): show trace steps timeline on terminal on test failure (#11594) Co-authored-by: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 --- config/browser/traceview.md | 2 +- guide/browser/snippets/trace-failure.ansi | 24 +++++++++++++++++++++++ guide/browser/trace-view.md | 13 ++++++++++-- 3 files changed, 36 insertions(+), 3 deletions(-) create mode 100644 guide/browser/snippets/trace-failure.ansi diff --git a/config/browser/traceview.md b/config/browser/traceview.md index a186eded..fb8b38b1 100644 --- a/config/browser/traceview.md +++ b/config/browser/traceview.md @@ -9,7 +9,7 @@ outline: deep - **CLI:** `--browser.traceView` - **Default:** `false` -Enable trace-view collection for browser tests. Vitest captures DOM snapshots for browser interactions and can show them in the browser UI, Vitest UI, or HTML reporter when those surfaces are enabled — no external tools required. +Enable [trace view](/guide/browser/trace-view) collection for browser tests. Vitest captures DOM snapshots for browser interactions and can show them in the browser UI, Vitest UI, or HTML reporter when those surfaces are enabled — no external tools required. When a test fails, the terminal output also includes a [timeline of its recorded steps](/guide/browser/trace-view#failure-output). ```ts export default defineConfig({ diff --git a/guide/browser/snippets/trace-failure.ansi b/guide/browser/snippets/trace-failure.ansi new file mode 100644 index 00000000..eebc87e5 --- /dev/null +++ b/guide/browser/snippets/trace-failure.ansi @@ -0,0 +1,24 @@ + FAIL src/sign-in.test.ts > signs in +Error: expect(element).toHaveTextContent() + +Expected element to have text content: + Welcome +Received: + Sign in + + ❯ src/sign-in.test.ts:11:51 +  9| }) +  10| await page.getByRole('button', { name: 'Sign in' }).click() +  11| await expect.element(page.getByRole('heading')).toHaveTextContent('Welcome') +  | ^ +  12| }) + +Caused by: Error: Matcher did not succeed in time. + +Trace: + step locator start duration location + fill form -  +1ms 58ms src/sign-in.test.ts:6:14 + ├── fill getByLabel('Email')  +2ms 31ms src/sign-in.test.ts:7:40 + └── fill getByLabel('Password')  +34ms 24ms src/sign-in.test.ts:8:43 + click getByRole('button', { name: 'Sign in' }) +61ms 18ms src/sign-in.test.ts:10:55 + toHaveTextContent [ERROR] getByRole('heading')  +80ms 1003ms src/sign-in.test.ts:11:16 diff --git a/guide/browser/trace-view.md b/guide/browser/trace-view.md index e4863f10..bbe0a3ee 100644 --- a/guide/browser/trace-view.md +++ b/guide/browser/trace-view.md @@ -2,7 +2,7 @@ `browser.traceView` records browser interactions as DOM snapshots and lets you replay them step by step in Vitest's built-in trace viewer. It is useful when the live browser view is not enough: you can inspect earlier tests, failed retries, screenshots, assertions, and user actions after the browser has already moved on. -Trace view is additive to the current browser testing workflow. Enabling it does not force a single debugging mode. You can use it with the normal local browser UI, with a headless browser and Vitest UI, or with the HTML reporter in CI. +Trace view is additive to the current browser testing workflow. Enabling it does not force a single debugging mode. You can use it with the normal local browser UI, with a headless browser and Vitest UI, or with the HTML reporter in CI. When a test fails, the terminal output also includes a text timeline of its recorded steps. ::: tip Trace view, browser UI, and HTML reports @@ -44,7 +44,7 @@ vitest --browser.traceView ::: -When `browser.traceView` is enabled, tests with recorded traces can be opened in the trace viewer from the [browser UI](/config/browser/ui), [Vitest UI](/guide/ui), and [HTML reporter](/guide/reporters#html-reporter). +When `browser.traceView` is enabled, tests with recorded traces can be opened in the trace viewer from the [browser UI](/config/browser/ui), [Vitest UI](/guide/ui), and [HTML reporter](/guide/reporters#html-reporter). Failed tests also print their recorded steps in the terminal, as described in [Failure Output](#failure-output). With the HTML reporter enabled, the [Vitest VS Code extension](https://github.com/vitest-dev/vscode#trace-view) adds an **Open Trace View** action to tests in the Testing view and editor gutter. @@ -81,6 +81,7 @@ TODO: The browser UI / Vitest UI / browser driver combinations are not specific | Debug locally with a headless browser | `vitest --browser.traceView --browser.headless --ui` | The browser runs headless, while Vitest UI shows recorded trace steps and snapshots. | | Debug locally with a visible browser window and Vitest UI | `vitest --browser.traceView --browser.headless=false --browser.ui=false --ui` | Vitest UI shows recorded trace steps and snapshots, while tests run in a separate headed browser window. | | Generate a static report for CI or run mode | `vitest run --browser.traceView --reporter=html` | The HTML report includes the trace viewer for recorded tests. | +| See failure steps in CI logs or from a coding agent | `vitest run --browser.traceView` | The failure summary in the terminal includes a timeline of the steps that led to each failure. | ## Relation to Playwright Traces @@ -107,6 +108,14 @@ Each entry captures the DOM state at that point, along with timing information, In Vitest UI, trace entries are streamed as the test runs, so you can inspect recorded steps before the test finishes. Long-running actions, `expect.element(...)` assertions, and callback `page.mark()` entries appear as in-progress steps first, then update with their final status and duration. +## Failure Output + +When a test fails, the error in the terminal is followed by a timeline of its recorded steps: + +<<< ./snippets/trace-failure.ansi [Error report] + +These are the same steps the trace viewer lists, without DOM snapshots. Only the last 10 steps are printed, so open the trace viewer to see the full trace. + ## Custom Trace Entries You can insert your own named entries with `page.mark()` and `locator.mark()`: From 08257662c256bb8df4dd8a6f6b308691be23a56c Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 21 Oct 2026 08:26:33 +0200 Subject: [PATCH 29/51] chore: address code scanning alerts (#11671) --- .vitepress/config.ts | 9 --------- 1 file changed, 9 deletions(-) diff --git a/.vitepress/config.ts b/.vitepress/config.ts index c5021809..8b8518c8 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -144,15 +144,6 @@ export default ({ mode }: { mode: string }) => { search: { provider: 'local', - /* provider: 'algolia', - options: { - appId: 'ZTF29HGJ69', - apiKey: '9c3ced6fed60d2670bb36ab7e8bed8bc', - indexName: 'vitest', - // searchParameters: { - // facetFilters: ['tags:en'], - // }, - }, */ }, // banner: { From 37439f12df0c44efeb00c141915ba6718f2681a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Thu, 22 Oct 2026 04:12:35 +0300 Subject: [PATCH 30/51] docs(coverage): update istanbuljs mentions (#11685) --- guide/coverage.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guide/coverage.md b/guide/coverage.md index 01fb218d..17a03a2d 100644 --- a/guide/coverage.md +++ b/guide/coverage.md @@ -84,7 +84,7 @@ import Box from '../.vitepress/components/Box.vue' ## Istanbul Provider -[Istanbul code coverage tooling](https://istanbul.js.org/) has existed since 2012 and is very well battle-tested. This provider works on any JavaScript runtime as coverage tracking works by transforming your source code to add instrumentation logic. In practice, the code Vitest will end up running looks something like this: +[Istanbul code coverage tooling](https://istanbul.js.org/) has existed since 2012 and is very well battle-tested. Vitest uses its own [`@vitest/istanbuljs`](https://github.com/vitest-dev/istanbuljs) that is a fork of the original [`istanbuljs/istanbuljs`](https://github.com/istanbuljs/istanbuljs) project. This provider works on any JavaScript runtime as coverage tracking works by transforming your source code to add instrumentation logic. In practice, the code Vitest will end up running looks something like this: ```js // Simplified example of branch and function coverage counters @@ -123,7 +123,7 @@ globalThis.__VITEST_COVERAGE__[filename] = coverage // [!code ++]
Test file - Pre‑instrumentation with Babel + Pre‑instrumentation on Javascript side Run file From 802c9800ea8d03b8e4093e44d416f600976c2a4a Mon Sep 17 00:00:00 2001 From: Erick Serra Date: Thu, 22 Oct 2026 18:28:24 +0100 Subject: [PATCH 31/51] feat(coverage): v8 to support `ignoreNode` option (#11688) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Ari Perkkiö --- config/coverage.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/config/coverage.md b/config/coverage.md index a4d01ad3..698355dc 100644 --- a/config/coverage.md +++ b/config/coverage.md @@ -407,6 +407,35 @@ Sets thresholds to 100 for files matching the glob pattern. Set to array of class method names to ignore for coverage. See [istanbul documentation](https://github.com/istanbuljs/nyc#ignoring-methods) for more information. +## coverage.ignoreNode 5.1.0 {#coverage-ignorenode} + +- **Type:** `(node: Node, type: 'function' | 'statement' | 'branch') => boolean | 'ignore-this-and-nested-nodes' | void` +- **Available for providers:** `'v8'` + +Filter to ignore code from coverage based on its AST node. Vitest calls it for each node of the transformed code, together with the kind of coverage the node counts for. Return `true` to ignore the node, or `'ignore-this-and-nested-nodes'` to also ignore all nodes inside it. + +This is useful for code that doesn't exist in the source file and can't carry an [ignore hint](/guide/coverage#ignoring-code), like code that compilers or other plugins generate. Since the nodes come from the code after Vite's transforms, the filter sees exactly what the plugins generated. + +Vitest's built-in filters run first, so nodes that Vitest already ignores, like its own transform artifacts, are never passed to this function. + +```ts +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + coverage: { + provider: 'v8', + ignoreNode: (node, type) => + type === 'statement' + && node.type === 'ExpressionStatement' + && node.expression.type === 'CallExpression' + && node.expression.callee.type === 'Identifier' + && node.expression.callee.name === '__generatedHelper', + }, + }, +}) +``` + ## coverage.watermarks - **Type:** From 4f5231b672dcd87ff56c42d0a8f759e9e284034e Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 26 Oct 2026 11:56:36 +0900 Subject: [PATCH 32/51] fix(mocker): don't change the original prototype chain (#11737) Co-authored-by: Max Schwenk --- api/vi.md | 4 ++++ guide/mocking/modules.md | 4 ++++ 2 files changed, 8 insertions(+) diff --git a/api/vi.md b/api/vi.md index 17462444..5c50cb9b 100644 --- a/api/vi.md +++ b/api/vi.md @@ -527,6 +527,10 @@ expect(spied.simple).toHaveBeenCalled() expect(spied.simple.mock.results[0]).toEqual({ type: 'return', value: 'value' }) ``` +::: warning +With `spy: true`, `vi.mockObject` doesn't copy objects. It returns the same `original` object and replaces its methods, including the methods of nested objects and class prototypes, with spies in place. +::: + ### vi.isMockFunction ```ts diff --git a/guide/mocking/modules.md b/guide/mocking/modules.md index f168f3aa..a8a98208 100644 --- a/guide/mocking/modules.md +++ b/guide/mocking/modules.md @@ -188,6 +188,10 @@ expect(answer()).toBe(42) expect(answer).toHaveBeenCalled() ``` +::: warning +With `spy: true`, Vitest doesn't copy exported objects and class prototypes. Instead, it replaces their methods with spies in place. Node.js built-ins and [externalized](/config/server#server-deps-external) dependencies are evaluated only once, so other code, `vi.importActual` and `require` see these spies too. For example, `vi.mock(import('node:events'), { spy: true })` turns `EventEmitter.prototype.emit` into a spy for every event emitter in the worker. +::: + One nice thing that mocked modules support is sharing the state between the instance and its prototype. Consider this module: ```ts [answer.js] From 93a53b5bfd23bbdc85dd10e1323bd7bbc7e642f8 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 26 Oct 2026 11:58:40 +0900 Subject: [PATCH 33/51] fix(mocker): use root __mocks__ for linked packages (fix #7708) (#11739) --- api/vi.md | 22 +++++++++++++++++----- config/deps.md | 20 +++++++++++++++----- config/server.md | 6 +++--- 3 files changed, 35 insertions(+), 13 deletions(-) diff --git a/api/vi.md b/api/vi.md index 5c50cb9b..cb091998 100644 --- a/api/vi.md +++ b/api/vi.md @@ -139,15 +139,22 @@ vi.mock('./path/to/module.js', () => { ::: -If there is a `__mocks__` folder alongside a file that you are mocking, and the factory is not provided, Vitest will try to find a file with the same name in the `__mocks__` subfolder and use it as an actual module. If you are mocking a dependency, Vitest will try to find a `__mocks__` folder in the [root](/config/root) of the project (default is `process.cwd()`). You can tell Vitest where the dependencies are located through the [`deps.moduleDirectories`](/config/deps#deps-moduledirectories) config option. +If the factory is not provided, Vitest looks for a file with the same name in a `__mocks__` folder and uses it instead of the original module: + +- If the path is a package name, like `axios` or `@company/utils/format`, Vitest looks in the `__mocks__` folder in the [root](/config/root) of the project (default is `process.cwd()`) for a file with this path and any extension, for example, `__mocks__/@company/utils/format.ts`. This also applies to a package that is linked from outside of `node_modules`, like a workspace package in a monorepo. +- If the module resolves to a file outside of `node_modules`, Vitest looks in the `__mocks__` folder next to that file for a file with the same name and extension, for example, `src/__mocks__/increment.js` for `src/increment.js`. For a package name, Vitest does this only if the root `__mocks__` folder doesn't have the file. + +Vitest treats files in other [`deps.moduleDirectories`](/config/deps#deps-moduledirectories) the same as files in `node_modules`. For example, you have this file structure: ``` - __mocks__ + - @company + - utils.js - axios.js - src - __mocks__ + - __mocks__ - increment.js - increment.js - tests @@ -162,13 +169,18 @@ import { vi } from 'vitest' // axios is a default export from `__mocks__/axios.js` import axios from 'axios' +// format is a named export from `__mocks__/@company/utils.js`, +// even if `@company/utils` is a workspace package +import { format } from '@company/utils' + // increment is a named export from `src/__mocks__/increment.js` -import { increment } from '../increment.js' +import { increment } from '../src/increment.js' vi.mock('axios') -vi.mock('../increment.js') +vi.mock('@company/utils') +vi.mock('../src/increment.js') -axios.get(`/apples/${increment(1)}`) +axios.get(`/apples/${format(increment(1))}`) ``` ::: warning diff --git a/config/deps.md b/config/deps.md index dad21d83..220f7579 100644 --- a/config/deps.md +++ b/config/deps.md @@ -113,11 +113,13 @@ By default, Vitest assumes you are using a bundler to bypass this and will not f - **Type:** `string[]` - **Default:** `['node_modules']` -A list of directories that should be treated as module directories. This config option affects the behavior of [`vi.mock`](/api/vi#vi-mock): when no factory is provided and the path of what you are mocking matches one of the `moduleDirectories` values, Vitest will try to resolve the mock by looking for a `__mocks__` folder in the [root](/config/root) of the project. +A list of directories that contain dependencies. Vitest adds a slash before and after each value and checks if the path of a file includes it: `'vendor'` matches every file inside a `vendor` folder, and an absolute path matches only the files inside that folder. Vitest always includes `node_modules`. -This option will also affect if a file should be treated as a module when externalizing dependencies. By default, Vitest imports external modules with native Node.js bypassing Vite transformation step. +Vitest treats a file inside one of these directories as a dependency: -Setting this option will _override_ the default, if you wish to still search `node_modules` for packages include it along with any other options: +- [`vi.mock`](/api/vi#vi-mock) without a factory looks for the mock only in the `__mocks__` folder in the [root](/config/root) of the project. +- Vitest [externalizes](/config/server#server-deps-external) JavaScript files that Node.js can import directly, for example, a `.js` file in a package with `"type": "module"`. Vitest imports these files with native Node.js, Vite plugins don't transform them, and `vi.mock` doesn't apply to the modules that they import. +- Vitest prefixes string values of [`server.deps.inline`](/config/server#server-deps-inline) and [`server.deps.external`](/config/server#server-deps-external) with these directories. ```ts import { defineConfig } from 'vitest/config' @@ -125,8 +127,16 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { deps: { - moduleDirectories: ['node_modules', path.resolve('../../packages')], - } + moduleDirectories: ['vendor'], + }, }, }) ``` + +::: tip +You don't need this option to mock a workspace package in a monorepo. If you call `vi.mock` with a package name, Vitest looks in the root `__mocks__` folder even if the package resolves outside of `node_modules`. +::: + +::: warning +Don't add a directory that contains the source files of your project. For example, if the project is in `packages/app`, the value `packages` makes Vitest treat the files in `packages/app` as dependencies too. +::: diff --git a/config/server.md b/config/server.md index e7c3623e..2f16741b 100644 --- a/config/server.md +++ b/config/server.md @@ -20,7 +20,7 @@ Normally, Vitest should do this automatically. ### server.deps.external - **Type:** `(string | RegExp)[]` -- **Default:** files inside [`moduleDirectories`](/config/deps#moduledirectories) +- **Default:** files inside [`moduleDirectories`](/config/deps#deps-moduledirectories) Specifies modules that should not be transformed by Vite and should instead be processed directly by the engine. These modules are imported via native dynamic `import` and bypass both transformation and resolution phases. @@ -43,7 +43,7 @@ External modules and their dependencies are not present in the module graph and Typically, packages under `node_modules` are externalized. ::: tip -If a string is provided, it is first normalized by prefixing the `/node_modules/` or other [`moduleDirectories`](/config/deps#moduledirectories) segments (for example, `'react'` becomes `/node_modules/react/`), and the resulting string is then matched against the full file path. For example, package `@company/some-name` located inside `packages/some-name` should be specified as `some-name`, and `packages` should be included in `deps.moduleDirectories`. +If a string is provided, it is first normalized by prefixing the `/node_modules/` or other [`moduleDirectories`](/config/deps#deps-moduledirectories) segments (for example, `'react'` becomes `/node_modules/react/`), and the resulting string is then matched against the full file path. For example, package `@company/some-name` located inside `packages/some-name` should be specified as `some-name`, and `packages` should be included in `deps.moduleDirectories`. If a `RegExp` is provided, it is matched against the full file path. ::: @@ -58,7 +58,7 @@ Specifies modules that should be transformed and resolved by Vite. These modules Typically, your source files are inlined. ::: tip -If a string is provided, it is first normalized by prefixing the `/node_modules/` or other [`moduleDirectories`](/config/deps#moduledirectories) segments (for example, `'react'` becomes `/node_modules/react/`), and the resulting string is then matched against the full file path. For example, package `@company/some-name` located inside `packages/some-name` should be specified as `some-name`, and `packages` should be included in `deps.moduleDirectories`. +If a string is provided, it is first normalized by prefixing the `/node_modules/` or other [`moduleDirectories`](/config/deps#deps-moduledirectories) segments (for example, `'react'` becomes `/node_modules/react/`), and the resulting string is then matched against the full file path. For example, package `@company/some-name` located inside `packages/some-name` should be specified as `some-name`, and `packages` should be included in `deps.moduleDirectories`. If a `RegExp` is provided, it is matched against the full file path. ::: From e045f771612c410dbfdf49bf3e894923462fa14f Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Date: Mon, 26 Oct 2026 16:11:56 +0900 Subject: [PATCH 34/51] feat(browser): add failure-only trace view mode (#11612) Co-authored-by: Claude Opus 5.5 --- config/browser/traceview.md | 45 ++++++++++++++++++++++++++++--------- guide/cli-generated.md | 9 +++++++- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/config/browser/traceview.md b/config/browser/traceview.md index fb8b38b1..b5bd7bd8 100644 --- a/config/browser/traceview.md +++ b/config/browser/traceview.md @@ -5,7 +5,7 @@ outline: deep # browser.traceView 5.0.0 -- **Type:** `boolean | { enabled?: boolean; recordCanvas?: boolean; inlineImages?: boolean; ariaSnapshot?: boolean }` +- **Type:** `boolean | 'off' | 'on' | 'failure-only' | { mode?: 'off' | 'on' | 'failure-only'; recordCanvas?: boolean; inlineImages?: boolean; ariaSnapshot?: boolean }` - **CLI:** `--browser.traceView` - **Default:** `false` @@ -21,6 +21,18 @@ export default defineConfig({ }) ``` +To keep traces only for failed tests, use the `'failure-only'` mode: + +```ts +export default defineConfig({ + test: { + browser: { + traceView: 'failure-only', + }, + }, +}) +``` + Use the object form to enable additional snapshot fidelity options: ```ts @@ -28,7 +40,7 @@ export default defineConfig({ test: { browser: { traceView: { - enabled: true, + mode: 'on', inlineImages: true, recordCanvas: true, }, @@ -37,20 +49,33 @@ export default defineConfig({ }) ``` -| Option | Default | Description | -| -------------- | ------- | --------------------------------------------------------------------------------------------------- | -| `enabled` | `false` | Enables Vitest trace-view artifact collection. | -| `inlineImages` | `false` | Inlines loaded `` pixels into snapshots for more portable replay, useful in the HTML reporter. | -| `recordCanvas` | `false` | Captures canvas pixels in snapshots. | -| `ariaSnapshot` | `true` | Captures the accessibility tree alongside each snapshot. | +| Option | Default | Description | +| -------------- | ------- | ---------------------------------------------------------------------------------------------------- | +| `mode` | `'off'` | How trace view records tests: `'on'` (every test), `'failure-only'` (only failed tests), or `'off'`. | +| `inlineImages` | `false` | Inlines loaded `` pixels into snapshots for more portable replay, useful in the HTML reporter. | +| `recordCanvas` | `false` | Captures canvas pixels in snapshots. | +| `ariaSnapshot` | `true` | Captures the accessibility tree alongside each snapshot. | +| `enabled` | `false` | Deprecated. Use `mode` instead. | -## browser.traceView.enabled {#traceview-enabled} +## browser.traceView.enabled {#traceview-enabled} - **Type:** `boolean` - **Default:** `false` - **CLI:** `--browser.traceView.enabled` -Enables Vitest trace-view artifact collection. +Use [`mode`](#traceview-mode) instead. `true` is the same as `mode: 'on'`. + +## browser.traceView.mode 5.1.0 {#traceview-mode} + +- **Type:** `'off' | 'on' | 'failure-only'` +- **Default:** `'off'` +- **CLI:** `--browser.traceView.mode ` + +How trace view records tests. Takes precedence over the deprecated `enabled`. + +- `'on'`: every test gets a trace with a snapshot at every step, and Vitest UI shows steps live as tests run. +- `'failure-only'`: only failed tests get a trace, with the full step timeline and a snapshot at the end. +- `'off'`: no traces. ## browser.traceView.inlineImages {#traceview-inlineimages} diff --git a/guide/cli-generated.md b/guide/cli-generated.md index 799a387e..bd5c84c6 100644 --- a/guide/cli-generated.md +++ b/guide/cli-generated.md @@ -414,7 +414,14 @@ Enable trace view mode. Supported: "on", "off", "on-first-retry", "on-all-retrie - **CLI:** `--browser.traceView.enabled` - **Config:** [browser.traceView.enabled](/config/browser/traceview#traceview-enabled) -Enable Vitest trace-view collection for browser tests (default: `false`) +Deprecated, use `--browser.traceView.mode` instead. Enable Vitest trace-view collection for browser tests (default: `false`) + +### browser.traceView.mode + +- **CLI:** `--browser.traceView.mode ` +- **Config:** [browser.traceView.mode](/config/browser/traceview#traceview-mode) + +How trace view records tests. Supported: "on" (every test), "failure-only" (only failed tests), "off" (default: `"off"`) ### browser.traceView.recordCanvas From 702ad48ff30b9b7a6f43b6c329958e5abcc805c1 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:13:50 +0900 Subject: [PATCH 35/51] docs(browser): explain `--ui`, `browser.ui`, and `browser.headless` in the Browser Mode guide (#11769) Co-authored-by: Claude Opus 5.5 (1M context) --- config/browser/ui.md | 4 ++-- guide/browser/index.md | 22 +++++++++++++++++++++- guide/browser/trace-view.md | 13 +------------ 3 files changed, 24 insertions(+), 15 deletions(-) diff --git a/config/browser/ui.md b/config/browser/ui.md index 4b437c96..b99be2dc 100644 --- a/config/browser/ui.md +++ b/config/browser/ui.md @@ -6,7 +6,7 @@ outline: deep # browser.ui - **Type:** `boolean` -- **Default:** `!isCI` +- **Default:** `!browser.headless && !isCI` - **CLI:** `--browser.ui=false` -Should Vitest UI be injected into the page. By default, injects UI iframe during development. +Should the browser UI be injected into the page. By default, injects UI iframe during development. See [Headless Mode and UI](/guide/browser/#headless-mode-and-ui) for how this option relates to [`browser.headless`](/config/browser/headless) and the top-level [`--ui`](/guide/ui) flag. diff --git a/guide/browser/index.md b/guide/browser/index.md index ccc3061d..b52296af 100644 --- a/guide/browser/index.md +++ b/guide/browser/index.md @@ -361,7 +361,7 @@ For an alternative debugging model that captures DOM snapshots for every test in Headless mode is another option available in the browser mode. In headless mode, the browser runs in the background without a user interface, which makes it useful for running automated tests. The headless option in Vitest can be set to a boolean value to enable or disable headless mode. -When using headless mode, Vitest won't open the UI automatically. If you want to continue using the UI but have tests run headlessly, you can install the [`@vitest/ui`](/guide/ui) package and pass the `--ui` flag when running Vitest. +When using headless mode, Vitest won't open the browser UI automatically. If you want to continue using a UI but have tests run headlessly, you can install the [`@vitest/ui`](/guide/ui) package and pass the `--ui` flag when running Vitest. See [Headless Mode and UI](#headless-mode-and-ui) for what each option controls and how their defaults interact. Here's an example configuration enabling headless mode: @@ -392,6 +392,26 @@ In this case, Vitest will run in headless mode using the Chrome browser. Headless mode is not available by default. You need to use either [`playwright`](https://npmx.dev/package/playwright) or [`webdriverio`](https://npmx.dev/package/webdriverio) providers to enable this feature. ::: +### Headless Mode and UI {#headless-mode-and-ui} + +Three options decide what you see during a browser mode run: + +- [`browser.headless`](/config/browser/headless) controls whether the browser that runs the tests has a visible window. +- [`browser.ui`](/config/browser/ui) controls whether the browser UI is rendered inside that browser, with the test iframe in the center. +- The top-level `--ui` flag controls whether [Vitest UI](/guide/ui) is served by Vitest and opened in your default browser, separately from the browser that runs the tests. + +`browser.headless` defaults to `true` in CI, and `browser.ui` defaults to `true` when the browser is headed and Vitest is not running in CI. The following table lists common setups: + +| Goal | Settings | Result | +| ------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------ | +| Develop locally | defaults | The browser window shows the browser UI. | +| Watch tests in a plain browser window | `--browser.ui=false` | The browser window shows only the test iframe at its configured viewport size, without the browser UI. | +| Run in CI | defaults | No window is shown, and results are reported in the terminal. | +| Use Vitest UI with a headless browser | `--ui --browser.headless` | Vitest UI shows the results, and tests run in a headless browser. | +| Use Vitest UI with a visible browser window | `--ui --browser.ui=false` | Vitest UI shows the results, and tests run in a separate browser window without the browser UI. | + +Passing `--ui` alone keeps the browser UI in the headed browser window, so set `--browser.ui=false` to use only Vitest UI. + ## Examples By default, you don't need any external packages to work with the Browser Mode: diff --git a/guide/browser/trace-view.md b/guide/browser/trace-view.md index bbe0a3ee..47fd848e 100644 --- a/guide/browser/trace-view.md +++ b/guide/browser/trace-view.md @@ -62,18 +62,7 @@ Selecting a step also opens its source location in the Editor tab when that loca ## Common Setups - - -`browser.traceView` records traces. The browser mode, UI, and reporter options determine where you inspect them. +`browser.traceView` records traces, and the browser mode, UI, and reporter options decide where you inspect them. The following table lists common setups, and [Headless Mode and UI](/guide/browser/#headless-mode-and-ui) explains what `--ui`, `browser.ui`, and `browser.headless` each control. | Goal | Configuration | Result | | --------------------------------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | From 4cf2461192197b2d040850ff44fadb5096e436f4 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:15:17 +0900 Subject: [PATCH 36/51] feat(browser): enable failure-only trace view by default (#11766) Co-authored-by: Claude Opus 5.5 --- config/browser/traceview.md | 32 +++++++++++--------------------- guide/browser/trace-view.md | 18 +++++++++--------- guide/cli-generated.md | 4 ++-- 3 files changed, 22 insertions(+), 32 deletions(-) diff --git a/config/browser/traceview.md b/config/browser/traceview.md index b5bd7bd8..6713334b 100644 --- a/config/browser/traceview.md +++ b/config/browser/traceview.md @@ -7,27 +7,17 @@ outline: deep - **Type:** `boolean | 'off' | 'on' | 'failure-only' | { mode?: 'off' | 'on' | 'failure-only'; recordCanvas?: boolean; inlineImages?: boolean; ariaSnapshot?: boolean }` - **CLI:** `--browser.traceView` -- **Default:** `false` +- **Default:** `'failure-only'` Enable [trace view](/guide/browser/trace-view) collection for browser tests. Vitest captures DOM snapshots for browser interactions and can show them in the browser UI, Vitest UI, or HTML reporter when those surfaces are enabled — no external tools required. When a test fails, the terminal output also includes a [timeline of its recorded steps](/guide/browser/trace-view#failure-output). -```ts -export default defineConfig({ - test: { - browser: { - traceView: true, - }, - }, -}) -``` - -To keep traces only for failed tests, use the `'failure-only'` mode: +By default, trace view keeps traces only for failed tests. Set it to `true` (the same as [`mode: 'on'`](#traceview-mode)) to record every test, or `false` to turn it off: ```ts export default defineConfig({ test: { browser: { - traceView: 'failure-only', + traceView: true, }, }, }) @@ -49,13 +39,13 @@ export default defineConfig({ }) ``` -| Option | Default | Description | -| -------------- | ------- | ---------------------------------------------------------------------------------------------------- | -| `mode` | `'off'` | How trace view records tests: `'on'` (every test), `'failure-only'` (only failed tests), or `'off'`. | -| `inlineImages` | `false` | Inlines loaded `` pixels into snapshots for more portable replay, useful in the HTML reporter. | -| `recordCanvas` | `false` | Captures canvas pixels in snapshots. | -| `ariaSnapshot` | `true` | Captures the accessibility tree alongside each snapshot. | -| `enabled` | `false` | Deprecated. Use `mode` instead. | +| Option | Default | Description | +| -------------- | ---------------- | ---------------------------------------------------------------------------------------------------- | +| `mode` | `'failure-only'` | How trace view records tests: `'on'` (every test), `'failure-only'` (only failed tests), or `'off'`. | +| `inlineImages` | `false` | Inlines loaded `` pixels into snapshots for more portable replay, useful in the HTML reporter. | +| `recordCanvas` | `false` | Captures canvas pixels in snapshots. | +| `ariaSnapshot` | `true` | Captures the accessibility tree alongside each snapshot. | +| `enabled` | `false` | Deprecated. Use `mode` instead. | ## browser.traceView.enabled {#traceview-enabled} @@ -68,7 +58,7 @@ Use [`mode`](#traceview-mode) instead. `true` is the same as `mode: 'on'`. ## browser.traceView.mode 5.1.0 {#traceview-mode} - **Type:** `'off' | 'on' | 'failure-only'` -- **Default:** `'off'` +- **Default:** `'failure-only'` - **CLI:** `--browser.traceView.mode ` How trace view records tests. Takes precedence over the deprecated `enabled`. diff --git a/guide/browser/trace-view.md b/guide/browser/trace-view.md index 47fd848e..cc359a4f 100644 --- a/guide/browser/trace-view.md +++ b/guide/browser/trace-view.md @@ -2,13 +2,13 @@ `browser.traceView` records browser interactions as DOM snapshots and lets you replay them step by step in Vitest's built-in trace viewer. It is useful when the live browser view is not enough: you can inspect earlier tests, failed retries, screenshots, assertions, and user actions after the browser has already moved on. -Trace view is additive to the current browser testing workflow. Enabling it does not force a single debugging mode. You can use it with the normal local browser UI, with a headless browser and Vitest UI, or with the HTML reporter in CI. When a test fails, the terminal output also includes a text timeline of its recorded steps. +By default, Vitest records traces only for failed tests, so every browser test failure prints a [timeline of its recorded steps](#failure-output) in the terminal. The trace keeps a DOM snapshot of the page when the test failed, which you can open in the browser UI, Vitest UI, or HTML reporter. To snapshot every step, set [`browser.traceView.mode`](/config/browser/traceview#traceview-mode) to `'on'`. Trace view works with your existing debugging setup, whether that is the normal local browser UI, a headless browser with Vitest UI, or the HTML reporter in CI. ::: tip Trace view, browser UI, and HTML reports The normal local browser mode opens the [browser UI](/config/browser/ui), where tests run in a visible iframe. This is useful while developing, but the iframe only shows the current browser state. When another test runs, the previous rendered state is gone. -`browser.traceView` keeps a replayable record for each test. In local browser UI mode, the trace viewer appears alongside the existing live view so you can keep using the browser UI while also inspecting recorded steps. +`browser.traceView` keeps a replayable record for each recorded test. In local browser UI mode, the trace viewer appears alongside the existing live view so you can keep using the browser UI while also inspecting recorded steps. For static output, add the [HTML reporter](/guide/reporters#html-reporter). The same trace viewer can then be opened from the generated report, which is useful for run-mode and CI failures. @@ -22,7 +22,7 @@ This page now documents Vitest's built-in `browser.traceView` feature. The previ ## Quick Start -Enable trace view with the [`browser.traceView`](/config/browser/traceview) option: +Failed tests are recorded without any configuration. To record passing tests too, set the [`browser.traceView`](/config/browser/traceview) option to `true`, or set it to `false` to turn trace view off: ::: code-group @@ -44,7 +44,7 @@ vitest --browser.traceView ::: -When `browser.traceView` is enabled, tests with recorded traces can be opened in the trace viewer from the [browser UI](/config/browser/ui), [Vitest UI](/guide/ui), and [HTML reporter](/guide/reporters#html-reporter). Failed tests also print their recorded steps in the terminal, as described in [Failure Output](#failure-output). +Tests with recorded traces can be opened in the trace viewer from the [browser UI](/config/browser/ui), [Vitest UI](/guide/ui), and [HTML reporter](/guide/reporters#html-reporter). Failed tests also print their recorded steps in the terminal, as described in [Failure Output](#failure-output). With the HTML reporter enabled, the [Vitest VS Code extension](https://github.com/vitest-dev/vscode#trace-view) adds an **Open Trace View** action to tests in the Testing view and editor gutter. @@ -66,11 +66,11 @@ Selecting a step also opens its source location in the Editor tab when that loca | Goal | Configuration | Result | | --------------------------------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -| Add trace replay to the normal local browser UI | `vitest --browser.traceView` | Uses the default local headed browser UI and adds trace replay for recorded tests. | +| Add trace replay to the normal local browser UI | `vitest --browser.traceView` | Uses the default local headed browser UI and adds trace replay for every test. | | Debug locally with a headless browser | `vitest --browser.traceView --browser.headless --ui` | The browser runs headless, while Vitest UI shows recorded trace steps and snapshots. | | Debug locally with a visible browser window and Vitest UI | `vitest --browser.traceView --browser.headless=false --browser.ui=false --ui` | Vitest UI shows recorded trace steps and snapshots, while tests run in a separate headed browser window. | -| Generate a static report for CI or run mode | `vitest run --browser.traceView --reporter=html` | The HTML report includes the trace viewer for recorded tests. | -| See failure steps in CI logs or from a coding agent | `vitest run --browser.traceView` | The failure summary in the terminal includes a timeline of the steps that led to each failure. | +| See failure steps in CI logs or from a coding agent | `vitest run` | The failure summary in the terminal includes a timeline of the steps that led to each failure. | +| Inspect failures in CI or run mode | `vitest run --reporter=html` | The HTML report shows the full step list with the page when the test failed. | ## Relation to Playwright Traces @@ -103,7 +103,7 @@ When a test fails, the error in the terminal is followed by a timeline of its re <<< ./snippets/trace-failure.ansi [Error report] -These are the same steps the trace viewer lists, without DOM snapshots. Only the last 10 steps are printed, so open the trace viewer to see the full trace. +These are the last 10 steps the trace viewer lists, without DOM snapshots. To inspect the full step list and the page when the test failed, add the [HTML reporter](/guide/reporters#html-reporter). Set [`browser.traceView.mode`](/config/browser/traceview#traceview-mode) to `'on'` to snapshot every step. ## Custom Trace Entries @@ -166,7 +166,7 @@ export default defineConfig({ test: { browser: { traceView: { - enabled: true, + mode: 'on', inlineImages: true, recordCanvas: true, }, diff --git a/guide/cli-generated.md b/guide/cli-generated.md index bd5c84c6..ac101eb1 100644 --- a/guide/cli-generated.md +++ b/guide/cli-generated.md @@ -414,14 +414,14 @@ Enable trace view mode. Supported: "on", "off", "on-first-retry", "on-all-retrie - **CLI:** `--browser.traceView.enabled` - **Config:** [browser.traceView.enabled](/config/browser/traceview#traceview-enabled) -Deprecated, use `--browser.traceView.mode` instead. Enable Vitest trace-view collection for browser tests (default: `false`) +Deprecated, use `--browser.traceView.mode` instead. `true` is the same as `--browser.traceView.mode=on` ### browser.traceView.mode - **CLI:** `--browser.traceView.mode ` - **Config:** [browser.traceView.mode](/config/browser/traceview#traceview-mode) -How trace view records tests. Supported: "on" (every test), "failure-only" (only failed tests), "off" (default: `"off"`) +How trace view records tests. Supported: "on" (every test), "failure-only" (only failed tests), "off" (default: `"failure-only"`) ### browser.traceView.recordCanvas From e9b363e31ed659b9927f1225e6a7120c1092b5d0 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Date: Wed, 28 Oct 2026 15:29:08 +0900 Subject: [PATCH 37/51] fix: prefix expect.poll message to all errors (#11756) Co-authored-by: Claude Opus 5.5 (1M context) --- api/expect.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/api/expect.md b/api/expect.md index 407f3081..9919cdf9 100644 --- a/api/expect.md +++ b/api/expect.md @@ -118,6 +118,13 @@ The callback receives an `AbortSignal` that is aborted when the poll timeout is If an error is thrown inside the `expect.poll` callback, Vitest will retry again until the timeout runs out. +The `message` option describes what the poll waits for. If the poll fails, Vitest adds the message to the start of the error. + +```ts +await expect.poll(() => queue.count(), { message: 'queue drained' }).toBe(0) +// AssertionError: queue drained: expected 3 to be 0 // Object.is equality +``` + If `vi.useFakeTimers` is used, `expect.poll` automatically calls `vi.advanceTimersByTime(interval)` after every failed attempt. See [Automatic Advancing](/guide/mocking/timers#automatic-advancing) for details. ```ts From 5f1d4cd984a13ec6911a589e179e4d59d9e58cfb Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa <4232207+hi-ogawa@users.noreply.github.com> Date: Wed, 28 Oct 2026 16:23:17 +0900 Subject: [PATCH 38/51] docs(browser): rework trace view guide and config (#11767) Co-authored-by: Claude Opus 5.5 --- config/browser/traceview.md | 44 +++++------- guide/browser/trace-view.md | 134 ++++++++++++++++-------------------- 2 files changed, 77 insertions(+), 101 deletions(-) diff --git a/config/browser/traceview.md b/config/browser/traceview.md index 6713334b..808dafe6 100644 --- a/config/browser/traceview.md +++ b/config/browser/traceview.md @@ -9,9 +9,9 @@ outline: deep - **CLI:** `--browser.traceView` - **Default:** `'failure-only'` -Enable [trace view](/guide/browser/trace-view) collection for browser tests. Vitest captures DOM snapshots for browser interactions and can show them in the browser UI, Vitest UI, or HTML reporter when those surfaces are enabled — no external tools required. When a test fails, the terminal output also includes a [timeline of its recorded steps](/guide/browser/trace-view#failure-output). +Records the actions, assertions, and marks of browser tests with DOM snapshots, so you can replay them in the [trace viewer](/guide/browser/trace-view). -By default, trace view keeps traces only for failed tests. Set it to `true` (the same as [`mode: 'on'`](#traceview-mode)) to record every test, or `false` to turn it off: +Set it to a [mode](#traceview-mode) such as `'failure-only'`, or use `true` and `false` as shorthands for `'on'` and `'off'`: ```ts export default defineConfig({ @@ -23,7 +23,7 @@ export default defineConfig({ }) ``` -Use the object form to enable additional snapshot fidelity options: +To combine a mode with the options below, use an object: ```ts export default defineConfig({ @@ -32,40 +32,23 @@ export default defineConfig({ traceView: { mode: 'on', inlineImages: true, - recordCanvas: true, }, }, }, }) ``` -| Option | Default | Description | -| -------------- | ---------------- | ---------------------------------------------------------------------------------------------------- | -| `mode` | `'failure-only'` | How trace view records tests: `'on'` (every test), `'failure-only'` (only failed tests), or `'off'`. | -| `inlineImages` | `false` | Inlines loaded `` pixels into snapshots for more portable replay, useful in the HTML reporter. | -| `recordCanvas` | `false` | Captures canvas pixels in snapshots. | -| `ariaSnapshot` | `true` | Captures the accessibility tree alongside each snapshot. | -| `enabled` | `false` | Deprecated. Use `mode` instead. | - -## browser.traceView.enabled {#traceview-enabled} - -- **Type:** `boolean` -- **Default:** `false` -- **CLI:** `--browser.traceView.enabled` - -Use [`mode`](#traceview-mode) instead. `true` is the same as `mode: 'on'`. - ## browser.traceView.mode 5.1.0 {#traceview-mode} - **Type:** `'off' | 'on' | 'failure-only'` - **Default:** `'failure-only'` - **CLI:** `--browser.traceView.mode ` -How trace view records tests. Takes precedence over the deprecated `enabled`. +Sets which tests trace view records and what each trace keeps: -- `'on'`: every test gets a trace with a snapshot at every step, and Vitest UI shows steps live as tests run. -- `'failure-only'`: only failed tests get a trace, with the full step timeline and a snapshot at the end. -- `'off'`: no traces. +- `'failure-only'` records only failed tests. Each trace keeps every step and a snapshot of the page when the test failed. +- `'on'` records every test with a snapshot at each step, and Vitest UI shows the steps live as tests run. +- `'off'` records nothing. ## browser.traceView.inlineImages {#traceview-inlineimages} @@ -73,7 +56,7 @@ How trace view records tests. Takes precedence over the deprecated `enabled`. - **Default:** `false` - **CLI:** `--browser.traceView.inlineImages` -Inlines loaded `` pixels into snapshots for more portable replay, useful in the HTML reporter. +Inlines loaded `` pixels into snapshots, so the HTML report renders them without loading the original URLs. See [What Snapshots Capture](/guide/browser/trace-view#what-snapshots-capture) for details. ## browser.traceView.recordCanvas {#traceview-recordcanvas} @@ -81,7 +64,7 @@ Inlines loaded `` pixels into snapshots for more portable replay, useful in - **Default:** `false` - **CLI:** `--browser.traceView.recordCanvas` -Captures canvas pixels in snapshots. This enables a weaker replay iframe sandbox because rrweb needs scripts to redraw canvas data. +Captures canvas pixels in snapshots. The trace viewer then allows scripts in its replay iframe for these traces. See the canvas replay sandbox warning in [What Snapshots Capture](/guide/browser/trace-view#what-snapshots-capture). ## browser.traceView.ariaSnapshot 5.1.0 {#traceview-ariasnapshot} @@ -89,6 +72,11 @@ Captures canvas pixels in snapshots. This enables a weaker replay iframe sandbox - **Default:** `true` - **CLI:** `--browser.traceView.ariaSnapshot` -Captures the accessibility tree of the page alongside each snapshot. The trace viewer header can then switch from the DOM snapshot to an **Aria** view, which uses the same format as [aria snapshots](/guide/browser/aria-snapshots). +Captures the accessibility tree of the page alongside each snapshot, so you can switch the trace viewer's snapshot pane between **DOM** and **Aria** views. The Aria view uses the same format as [aria snapshots](/guide/browser/aria-snapshots). + +## browser.traceView.enabled {#traceview-enabled} + +- **Type:** `boolean` +- **CLI:** `--browser.traceView.enabled` -See [Trace View](/guide/browser/trace-view) for full documentation. +Use [`mode`](#traceview-mode) instead. `true` is the same as `mode: 'on'`, and `false` is the same as `mode: 'off'`. When both are set, `mode` wins. diff --git a/guide/browser/trace-view.md b/guide/browser/trace-view.md index cc359a4f..1fc58e4d 100644 --- a/guide/browser/trace-view.md +++ b/guide/browser/trace-view.md @@ -1,28 +1,22 @@ # Trace View 5.0.0 -`browser.traceView` records browser interactions as DOM snapshots and lets you replay them step by step in Vitest's built-in trace viewer. It is useful when the live browser view is not enough: you can inspect earlier tests, failed retries, screenshots, assertions, and user actions after the browser has already moved on. +Trace view records the steps of a browser test, such as actions, assertions, and marks, together with DOM snapshots of the page. After the test finishes, you can replay them in Vitest's trace viewer to see what happened and what the page looked like. -By default, Vitest records traces only for failed tests, so every browser test failure prints a [timeline of its recorded steps](#failure-output) in the terminal. The trace keeps a DOM snapshot of the page when the test failed, which you can open in the browser UI, Vitest UI, or HTML reporter. To snapshot every step, set [`browser.traceView.mode`](/config/browser/traceview#traceview-mode) to `'on'`. Trace view works with your existing debugging setup, whether that is the normal local browser UI, a headless browser with Vitest UI, or the HTML reporter in CI. - -::: tip Trace view, browser UI, and HTML reports - -The normal local browser mode opens the [browser UI](/config/browser/ui), where tests run in a visible iframe. This is useful while developing, but the iframe only shows the current browser state. When another test runs, the previous rendered state is gone. - -`browser.traceView` keeps a replayable record for each recorded test. In local browser UI mode, the trace viewer appears alongside the existing live view so you can keep using the browser UI while also inspecting recorded steps. - -For static output, add the [HTML reporter](/guide/reporters#html-reporter). The same trace viewer can then be opened from the generated report, which is useful for run-mode and CI failures. +By default, Vitest records only failed tests, prints their steps in the terminal, and keeps a snapshot of the page when each test failed. +::: tip +For Playwright traces recorded with [`browser.trace`](/config/browser/trace), see [Playwright Traces](./playwright-traces). ::: -::: details Looking for Playwright traces? +## Recording Modes -This page now documents Vitest's built-in `browser.traceView` feature. The previous `browser.trace` guide for Playwright traces moved to [Playwright Traces](./playwright-traces). +[`browser.traceView`](/config/browser/traceview) decides which tests Vitest records: -::: - -## Quick Start +- `'failure-only'` records only failed tests. This is the default. Each trace keeps every step and a snapshot of the page when the test failed. +- `true` or `'on'` records every test with a snapshot at each step. Vitest UI also streams steps as the test runs, so you can inspect them before the test finishes. Long-running actions, `expect.element(...)` assertions, and callback `page.mark()` entries first appear as in-progress steps, then update with their final status and duration. +- `false` or `'off'` turns trace view off. -Failed tests are recorded without any configuration. To record passing tests too, set the [`browser.traceView`](/config/browser/traceview) option to `true`, or set it to `false` to turn trace view off: +For example, to record every test: ::: code-group @@ -44,66 +38,52 @@ vitest --browser.traceView ::: -Tests with recorded traces can be opened in the trace viewer from the [browser UI](/config/browser/ui), [Vitest UI](/guide/ui), and [HTML reporter](/guide/reporters#html-reporter). Failed tests also print their recorded steps in the terminal, as described in [Failure Output](#failure-output). +## Failure Output -With the HTML reporter enabled, the [Vitest VS Code extension](https://github.com/vitest-dev/vscode#trace-view) adds an **Open Trace View** action to tests in the Testing view and editor gutter. +When a test fails, the error in the terminal is followed by a timeline of its recorded steps: -The viewer has two resizable panes: +<<< ./snippets/trace-failure.ansi [Error report] -- **Step list** (left) — every recorded action, assertion, mark, and lifecycle entry, with name, timing, selector, and source location. Failed actions and assertions are highlighted in red. -- **DOM snapshot** (right) — a reconstruction of the page at the selected step. The interacted element is highlighted in blue. +The timeline lists the same steps as the trace viewer, truncated for long tests and without DOM snapshots. The trace viewer shows every step and the page when the test failed. -Selecting a step also opens its source location in the Editor tab when that location is available. +## Opening Traces -Vitest UI trace viewer showing step list and DOM snapshot -Vitest UI trace viewer showing step list and DOM snapshot +The trace viewer is available wherever you already look at test results: -Example replay uses [Vuetify's](https://github.com/vuetifyjs/vuetify) `VDateInput` component. +| Where | Command | What you see | +| ----------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------ | +| [Browser UI](/config/browser/ui) | `vitest` | The trace viewer next to the live view, which only shows the current page | +| [Vitest UI](/guide/ui) | `vitest --ui --browser.headless` | The trace viewer in each test's report, with tests running in a headless browser | +| [HTML reporter](/guide/reporters#html-reporter) | `vitest run --reporter=html` | The trace viewer in the generated report, which suits CI | +| Terminal | `vitest run` | The step timeline of each failure, as described in [Failure Output](#failure-output) | -## Common Setups +To use Vitest UI with a visible browser window instead, see [Headless Mode and UI](/guide/browser/#headless-mode-and-ui). -`browser.traceView` records traces, and the browser mode, UI, and reporter options decide where you inspect them. The following table lists common setups, and [Headless Mode and UI](/guide/browser/#headless-mode-and-ui) explains what `--ui`, `browser.ui`, and `browser.headless` each control. +With the HTML reporter enabled, the [Vitest VS Code extension](https://github.com/vitest-dev/vscode#trace-view) adds an **Open Trace View** action to tests in the Testing view and editor gutter. -| Goal | Configuration | Result | -| --------------------------------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -| Add trace replay to the normal local browser UI | `vitest --browser.traceView` | Uses the default local headed browser UI and adds trace replay for every test. | -| Debug locally with a headless browser | `vitest --browser.traceView --browser.headless --ui` | The browser runs headless, while Vitest UI shows recorded trace steps and snapshots. | -| Debug locally with a visible browser window and Vitest UI | `vitest --browser.traceView --browser.headless=false --browser.ui=false --ui` | Vitest UI shows recorded trace steps and snapshots, while tests run in a separate headed browser window. | -| See failure steps in CI logs or from a coding agent | `vitest run` | The failure summary in the terminal includes a timeline of the steps that led to each failure. | -| Inspect failures in CI or run mode | `vitest run --reporter=html` | The HTML report shows the full step list with the page when the test failed. | +## Trace Viewer -## Relation to Playwright Traces +The viewer has two resizable panes: -`browser.traceView` and [`browser.trace`](/config/browser/trace) are independent features: +- **Step list** (left) shows every recorded action, assertion, mark, and lifecycle entry, with its name, timing, selector, and source location. Failed actions and assertions are highlighted in red. +- **DOM snapshot** (right) shows the page at the selected step, with the interacted element highlighted in blue. When only failed tests are recorded, the snapshot is taken at the end of the test. -| | `browser.traceView` | `browser.trace` | -| ---------------------- | -------------------------------------------------------- | ---------------------------------------------- | -| Provider support | All providers (playwright, webdriverio, preview) | Playwright only | -| Viewer | Browser UI / Vitest UI / HTML reporter | Playwright Trace Viewer / trace.playwright.dev | -| Format | [rrweb](https://github.com/rrweb-io/rrweb) DOM snapshots | Playwright `.trace.zip` | -| Requires external tool | No | Yes (`npx playwright show-trace`) | +Selecting a step also opens its source location in the Editor tab when that location is available. + +Vitest UI trace viewer showing step list and DOM snapshot +Vitest UI trace viewer showing step list and DOM snapshot -You can enable both at the same time. See [Playwright Traces](./playwright-traces) for the `browser.trace` workflow. +Example replay uses [Vuetify's](https://github.com/vuetifyjs/vuetify) `VDateInput` component. ## Recorded Steps -Trace entries are recorded automatically for: +Vitest records a step for: - `expect.element(...)` assertions -- Interactive actions like `click`, `dblClick`, `tripleClick`, `fill`, `clear`, `type`, `hover`, `selectOptions`, `upload`, `dragAndDrop`, `tab`, `keyboard`, `wheel`, and screenshots -- Test runner lifecycle event (e.g. `vitest:onAfterRetryTask` is recorded after each test and retry run) - -Each entry captures the DOM state at that point, along with timing information, the selector, and the source location that triggered it. +- Interactive actions such as `click`, `dblClick`, `tripleClick`, `fill`, `clear`, `type`, `hover`, `selectOptions`, `upload`, `dragAndDrop`, `tab`, `keyboard`, `wheel`, and screenshots +- Test runner lifecycle events, such as `vitest:onAfterRetryTask` after each test and retry run -In Vitest UI, trace entries are streamed as the test runs, so you can inspect recorded steps before the test finishes. Long-running actions, `expect.element(...)` assertions, and callback `page.mark()` entries appear as in-progress steps first, then update with their final status and duration. - -## Failure Output - -When a test fails, the error in the terminal is followed by a timeline of its recorded steps: - -<<< ./snippets/trace-failure.ansi [Error report] - -These are the last 10 steps the trace viewer lists, without DOM snapshots. To inspect the full step list and the page when the test failed, add the [HTML reporter](/guide/reporters#html-reporter). Set [`browser.traceView.mode`](/config/browser/traceview#traceview-mode) to `'on'` to snapshot every step. +Each step records its timing, selector, and the source location that triggered it. ## Custom Trace Entries @@ -145,19 +125,19 @@ test('shows button', async () => { ## Retries and Repeats -Each attempt — retry or repeat — is recorded as a separate trace. When a test has multiple attempts, the viewer opens the most recent one by default. You can switch between attempts in the Report tab. +Each retry or repeat of a test is recorded as a separate trace. The viewer opens the most recent attempt, and you can switch between attempts in the Report tab. ## Accessibility Tree 5.1.0 {#accessibility-tree} -Trace view also captures the page's accessibility tree at each step. The trace viewer header shows a view selector to switch the snapshot pane between **DOM** and **Aria**. The Aria view lists the roles, accessible names, and states that role-based locators such as `getByRole()` see, in the same format as [aria snapshots](/guide/browser/aria-snapshots). Set [`traceView.ariaSnapshot`](/config/browser/traceview#traceview-ariasnapshot) to `false` to disable it. +Trace view also captures the page's accessibility tree with each snapshot. The view selector in the trace viewer header switches the snapshot pane between **DOM** and **Aria**. The Aria view lists the roles, accessible names, and states that role-based locators such as `getByRole()` see, in the same format as [aria snapshots](/guide/browser/aria-snapshots). Set [`traceView.ariaSnapshot`](/config/browser/traceview#traceview-ariasnapshot) to `false` to disable it. -## Snapshot Fidelity +## What Snapshots Capture -By default, trace view captures the DOM tree, attributes, form values, same-origin readable CSS, element scroll positions, viewport size, and window scroll position. Images and canvas pixels are not inlined by default. +A snapshot captures the DOM tree, attributes, form values, same-origin readable CSS, element scroll positions, viewport size, and window scroll position. Iframe content is not captured, so both same-origin and cross-origin iframes replay as empty frames. -Stylesheets are captured through the browser's CSSOM. Readable `