From bf7524bae55d3da5cca67e22d376836be5816fb9 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sat, 25 Apr 2026 20:01:43 +0900 Subject: [PATCH 01/11] fix: global `sequence.concurrent: true` with top-level `test(..., { concurrent: false })` + depreacte `sequential` test API and options (#10194) Co-authored-by: Codex Co-authored-by: Vladimir --- api/describe.md | 6 +++++- api/test.md | 10 +++++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/api/describe.md b/api/describe.md index 5444a50b..2d37a4c2 100644 --- a/api/describe.md +++ b/api/describe.md @@ -230,10 +230,14 @@ describe.concurrent('suite', () => { }) ``` -## describe.sequential +## describe.sequential {#describe-sequential} - **Alias:** `suite.sequential` +::: warning DEPRECATED +Use [`concurrent: false`](/api/test#concurrent) instead when you need to override inherited or configured concurrency. +::: + `describe.sequential` in a suite marks every test as sequential. This is useful if you want to run tests in sequence within `describe.concurrent` or with the `--sequence.concurrent` command option. ```ts diff --git a/api/test.md b/api/test.md index 18af801f..7618fa19 100644 --- a/api/test.md +++ b/api/test.md @@ -223,6 +223,10 @@ Whether this test run concurrently with other concurrent tests in the suite. - **Default:** `true` - **Alias:** [`test.sequential`](#test-sequential) +::: warning DEPRECATED +Use [`concurrent: false`](#concurrent) instead when you need to override inherited or configured concurrency. +::: + Whether tests run sequentially. When both `concurrent` and `sequential` are specified, `concurrent` takes precedence. ### skip @@ -453,10 +457,14 @@ test.concurrent('test 2', async ({ expect }) => { Note that if tests are synchronous, Vitest will still run them sequentially. -## test.sequential +## test.sequential {#test-sequential} - **Alias:** `it.sequential` +::: warning DEPRECATED +Use [`concurrent: false`](#concurrent) instead when you need to override inherited or configured concurrency. +::: + `test.sequential` marks a test as sequential. This is useful if you want to run tests in sequence within `describe.concurrent` or with the `--sequence.concurrent` command option. ```ts From 8241fd0e8b125d7f5b05779dd840bd7d9a3e8986 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Sun, 26 Apr 2026 16:14:52 +0900 Subject: [PATCH 02/11] docs: document v5 migration changes for #9609 and #10170 (#10200) Co-authored-by: Codex --- guide/migration.md | 48 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) diff --git a/guide/migration.md b/guide/migration.md index 82963564..9047317d 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -7,6 +7,54 @@ outline: deep [Migrating to Vitest 3.0](https://v3.vitest.dev/guide/migration) | [Migrating to Vitest 2.0](https://v2.vitest.dev/guide/migration) +## Migrating to Vitest 5.0 {#vitest-5} + +::: warning Work in progress +Vitest 5.0 is currently in beta. This section tracks breaking changes as they are merged and may change before the stable release. +::: + +### String Values in `$` Test Titles Are No Longer Quoted + +When interpolating string values in `test.each`, `test.for`, `describe.each`, or `describe.for` titles with the `$` syntax, Vitest no longer wraps those string values in quotes. + +This affects generated task names in reporter output, snapshots, and any tooling that matches tests by their generated title. + +```ts +test.for([{ name: 'Alice' }])('I am $name', () => {}) +// Vitest 4 → I am 'Alice' +// Vitest 5 → I am Alice +``` + +If you need quotes in the generated title, add them to the title template: + +```ts +test.for([{ name: 'Alice' }])('I am "$name"', () => {}) +// → I am "Alice" +``` + +### `chaiConfig.truncateThreshold` No Longer Controls Test Title Value Truncation + +Vitest now formats interpolated task title values with its display formatter based on `@vitest/pretty-format`, instead of Chai/loupe formatting. + +Most output should stay similar, but generated titles or assertion output involving formatted values may have small formatting differences. + +If you used `chaiConfig.truncateThreshold` to control truncation in `test.each`, `test.for`, `describe.each`, or `describe.for` titles, use `taskTitleValueFormatTruncate` instead: + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + chaiConfig: { // [!code --] + truncateThreshold: 120, // [!code --] + }, // [!code --] + taskTitleValueFormatTruncate: 120, // [!code ++] + }, +}) +``` + +`chaiConfig.truncateThreshold` still controls truncation in assertion error messages. + ## Migrating to Vitest 4.0 {#vitest-4} ::: warning Prerequisites From 853e1c2700435082498bf9ef06434d2bf2bbb2d3 Mon Sep 17 00:00:00 2001 From: "Md.Sadiq" Date: Sun, 26 Apr 2026 20:00:59 +0530 Subject: [PATCH 03/11] fix!: default `attachmentsDir` from `.vitest-attachements/` to `.vitest/attachments/` (#10186) --- config/attachmentsdir.md | 2 +- guide/browser/visual-regression-testing.md | 4 ++-- guide/cli-generated.md | 2 +- guide/improving-performance.md | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/config/attachmentsdir.md b/config/attachmentsdir.md index 993fa9f8..14867d75 100644 --- a/config/attachmentsdir.md +++ b/config/attachmentsdir.md @@ -6,6 +6,6 @@ outline: deep # attachmentsDir - **Type:** `string` -- **Default:** `'.vitest-attachments'` +- **Default:** `'.vitest/attachments'` Directory path for storing attachments created by [`context.annotate`](/guide/test-context#annotate) relative to the project root. diff --git a/guide/browser/visual-regression-testing.md b/guide/browser/visual-regression-testing.md index ad088764..75a877bd 100644 --- a/guide/browser/visual-regression-testing.md +++ b/guide/browser/visual-regression-testing.md @@ -302,10 +302,10 @@ Reference screenshot: tests/__screenshots__/button.test.ts/button-chromium-darwin.png Actual screenshot: - tests/.vitest-attachments/button.test.ts/button-chromium-darwin-actual.png + tests/.vitest/attachments/button.test.ts/button-chromium-darwin-actual.png Diff image: - tests/.vitest-attachments/button.test.ts/button-chromium-darwin-diff.png + tests/.vitest/attachments/button.test.ts/button-chromium-darwin-diff.png ``` ### Understanding the diff image diff --git a/guide/cli-generated.md b/guide/cli-generated.md index 4fc81a51..abdd2dad 100644 --- a/guide/cli-generated.md +++ b/guide/cli-generated.md @@ -865,7 +865,7 @@ Collect test and suite locations in the `location` property - **CLI:** `--attachmentsDir ` - **Config:** [attachmentsDir](/config/attachmentsdir) -The directory where attachments from `context.annotate` are stored in (default: `.vitest-attachments`) +The directory where attachments from `context.annotate` are stored in (default: `.vitest/attachments`) ### run diff --git a/guide/improving-performance.md b/guide/improving-performance.md index efbbc804..baca0c6c 100644 --- a/guide/improving-performance.md +++ b/guide/improving-performance.md @@ -178,7 +178,7 @@ jobs: uses: actions/upload-artifact@v4 with: name: blob-attachments-${{ matrix.shardIndex }} - path: .vitest-attachments/** + path: .vitest/** include-hidden-files: true retention-days: 1 @@ -209,7 +209,7 @@ jobs: - name: Download attachments from GitHub Actions Artifacts uses: actions/download-artifact@v4 with: - path: .vitest-attachments + path: .vitest pattern: blob-attachments-* merge-multiple: true From f28efd4c52f1568690be75bab14296f8c4df1350 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 27 Apr 2026 16:58:47 +0900 Subject: [PATCH 04/11] refactor!: remove `sequential` test/suite options in favor of `concurrent` (#10198) Co-authored-by: Codex --- api/describe.md | 37 +++++++++++++---------------------- api/test.md | 46 ++++++-------------------------------------- guide/migration.md | 41 ++++++++++----------------------------- guide/parallelism.md | 13 +++++++++++++ 4 files changed, 42 insertions(+), 95 deletions(-) diff --git a/api/describe.md b/api/describe.md index 2d37a4c2..f1a95f9e 100644 --- a/api/describe.md +++ b/api/describe.md @@ -208,6 +208,19 @@ describe.concurrent('suite', () => { }) ``` +Set `concurrent` to `false` to opt out of concurrency inherited from a parent suite or [`sequence.concurrent`](/config/sequence#sequence-concurrent): + +```ts +describe.concurrent('suite', () => { + test('concurrent test', async () => { /* ... */ }) + + describe('sequential suite', { concurrent: false }, () => { + test('sequential test 1', async () => { /* ... */ }) + test('sequential test 2', async () => { /* ... */ }) + }) +}) +``` + `.skip`, `.only`, and `.todo` works with concurrent suites. All the following combinations are valid: ```ts @@ -230,30 +243,6 @@ describe.concurrent('suite', () => { }) ``` -## describe.sequential {#describe-sequential} - -- **Alias:** `suite.sequential` - -::: warning DEPRECATED -Use [`concurrent: false`](/api/test#concurrent) instead when you need to override inherited or configured concurrency. -::: - -`describe.sequential` in a suite marks every test as sequential. This is useful if you want to run tests in sequence within `describe.concurrent` or with the `--sequence.concurrent` command option. - -```ts -import { describe, test } from 'vitest' - -describe.concurrent('suite', () => { - test('concurrent test 1', async () => { /* ... */ }) - test('concurrent test 2', async () => { /* ... */ }) - - describe.sequential('', () => { - test('sequential test 1', async () => { /* ... */ }) - test('sequential test 2', async () => { /* ... */ }) - }) -}) -``` - ## describe.shuffle - **Alias:** `suite.shuffle` diff --git a/api/test.md b/api/test.md index 7618fa19..670f4006 100644 --- a/api/test.md +++ b/api/test.md @@ -217,17 +217,13 @@ Prefer using non-nested meta, if possible. Whether this test run concurrently with other concurrent tests in the suite. -### sequential +Set `concurrent` to `false` to opt out of concurrency inherited from [`describe.concurrent`](/api/describe#describe-concurrent) or [`sequence.concurrent`](/config/sequence#sequence-concurrent): -- **Type:** `boolean` -- **Default:** `true` -- **Alias:** [`test.sequential`](#test-sequential) - -::: warning DEPRECATED -Use [`concurrent: false`](#concurrent) instead when you need to override inherited or configured concurrency. -::: - -Whether tests run sequentially. When both `concurrent` and `sequential` are specified, `concurrent` takes precedence. +```ts +test('runs sequentially', { concurrent: false }, async () => { + // ... +}) +``` ### skip @@ -457,36 +453,6 @@ test.concurrent('test 2', async ({ expect }) => { Note that if tests are synchronous, Vitest will still run them sequentially. -## test.sequential {#test-sequential} - -- **Alias:** `it.sequential` - -::: warning DEPRECATED -Use [`concurrent: false`](#concurrent) instead when you need to override inherited or configured concurrency. -::: - -`test.sequential` marks a test as sequential. This is useful if you want to run tests in sequence within `describe.concurrent` or with the `--sequence.concurrent` command option. - -```ts -import { describe, test } from 'vitest' - -// with config option { sequence: { concurrent: true } } -test('concurrent test 1', async () => { /* ... */ }) -test('concurrent test 2', async () => { /* ... */ }) - -test.sequential('sequential test 1', async () => { /* ... */ }) -test.sequential('sequential test 2', async () => { /* ... */ }) - -// within concurrent suite -describe.concurrent('suite', () => { - test('concurrent test 1', async () => { /* ... */ }) - test('concurrent test 2', async () => { /* ... */ }) - - test.sequential('sequential test 1', async () => { /* ... */ }) - test.sequential('sequential test 2', async () => { /* ... */ }) -}) -``` - ## test.todo - **Alias:** `it.todo` diff --git a/guide/migration.md b/guide/migration.md index 9047317d..788cf0ab 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -13,48 +13,27 @@ outline: deep Vitest 5.0 is currently in beta. This section tracks breaking changes as they are merged and may change before the stable release. ::: -### String Values in `$` Test Titles Are No Longer Quoted +### Removed `test.sequential`, `describe.sequential`, and `sequential` Options -When interpolating string values in `test.each`, `test.for`, `describe.each`, or `describe.for` titles with the `$` syntax, Vitest no longer wraps those string values in quotes. - -This affects generated task names in reporter output, snapshots, and any tooling that matches tests by their generated title. +Vitest 5.0 removes the deprecated `test.sequential`, `describe.sequential`, and `sequential` test options. Use `concurrent: false` when you need a test or suite to opt out of inherited or globally configured concurrency. ```ts -test.for([{ name: 'Alice' }])('I am $name', () => {}) -// Vitest 4 → I am 'Alice' -// Vitest 5 → I am Alice +test.sequential('example', async () => { /* ... */ }) // [!code --] +test('example', { concurrent: false }, async () => { /* ... */ }) // [!code ++] ``` -If you need quotes in the generated title, add them to the title template: - ```ts -test.for([{ name: 'Alice' }])('I am "$name"', () => {}) -// → I am "Alice" +describe.sequential('suite', () => { /* ... */ }) // [!code --] +describe('suite', { concurrent: false }, () => { /* ... */ }) // [!code ++] ``` -### `chaiConfig.truncateThreshold` No Longer Controls Test Title Value Truncation - -Vitest now formats interpolated task title values with its display formatter based on `@vitest/pretty-format`, instead of Chai/loupe formatting. - -Most output should stay similar, but generated titles or assertion output involving formatted values may have small formatting differences. - -If you used `chaiConfig.truncateThreshold` to control truncation in `test.each`, `test.for`, `describe.each`, or `describe.for` titles, use `taskTitleValueFormatTruncate` instead: - -```ts [vitest.config.ts] -import { defineConfig } from 'vitest/config' +The same replacement applies to option objects: -export default defineConfig({ - test: { - chaiConfig: { // [!code --] - truncateThreshold: 120, // [!code --] - }, // [!code --] - taskTitleValueFormatTruncate: 120, // [!code ++] - }, -}) +```ts +test('example', { sequential: true }, async () => { /* ... */ }) // [!code --] +test('example', { concurrent: false }, async () => { /* ... */ }) // [!code ++] ``` -`chaiConfig.truncateThreshold` still controls truncation in assertion error messages. - ## Migrating to Vitest 4.0 {#vitest-4} ::: warning Prerequisites diff --git a/guide/parallelism.md b/guide/parallelism.md index 4c694c3b..c273eeef 100644 --- a/guide/parallelism.md +++ b/guide/parallelism.md @@ -80,6 +80,19 @@ describe.concurrent('user API', () => { If you want *all* tests in your project to run concurrently by default, set [`sequence.concurrent`](/config/sequence#sequence-concurrent) to `true` in your config. +You can opt individual tests or suites out of inherited concurrency with `concurrent: false`: + +```ts +test('uses a shared resource', { concurrent: false }, async () => { + // ... +}) + +describe('shared resource suite', { concurrent: false }, () => { + test('step 1', async () => { /* ... */ }) + test('step 2', async () => { /* ... */ }) +}) +``` + ### Hooks with Concurrent Tests When tests run concurrently, lifecycle hooks behave differently. `beforeAll` and `afterAll` still run once for the group, but `beforeEach` and `afterEach` run for each test — potentially at the same time, since the tests themselves overlap. From 7daeb00de738d4b2e1ecc9375e687613db5d7ae4 Mon Sep 17 00:00:00 2001 From: Raul Macarie Date: Mon, 27 Apr 2026 14:06:51 +0200 Subject: [PATCH 05/11] feat(browser): provide project reference in `ToMatchScreenshotResolvePath` (#10138) Co-authored-by: Vladimir --- config/browser/expect.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/config/browser/expect.md b/config/browser/expect.md index dff6d64b..45c3a21a 100644 --- a/config/browser/expect.md +++ b/config/browser/expect.md @@ -118,6 +118,10 @@ receives an object with the following properties: The value provided to [`attachmentsDir`](/config/attachmentsdir), if none is provided, its default value. +- `project: TestProject` 4.1.6 + + The [`TestProject`](/api/advanced/test-project) the test belongs to. + For example, to group screenshots by browser: ```ts From 00c87ce19fbcada49c3d015f977279bbaa391224 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Mon, 27 Apr 2026 15:10:47 +0300 Subject: [PATCH 06/11] feat(coverage): v8 to track `node:child_process` and `node:worker_threads` contexts (#9976) --- config/coverage.md | 11 +++++++++++ guide/cli-generated.md | 7 +++++++ 2 files changed, 18 insertions(+) diff --git a/config/coverage.md b/config/coverage.md index d7456831..8f1cfb42 100644 --- a/config/coverage.md +++ b/config/coverage.md @@ -458,3 +458,14 @@ Note that setting this option does not change where coverage HTML report is gene - **CLI:** `--coverage.changed`, `--coverage.changed=` Collect coverage only for files changed since a specified commit or branch. When set to `true`, it uses staged and unstaged changes. + +## coverage.autoAttachSubprocess 5.0.0 {#coverage-autoattachsubprocess} + +- **Type:** `boolean` +- **Default:** `false` +- **Available for providers:** `'v8'` +- **CLI:** `--coverage.autoAttachSubprocess` + +Track coverage of the `node:child_process` and `node:worker_threads` spawned during test run. + +Note that this option has some performance overhead as its using [`NODE_V8_COVERAGE`](https://nodejs.org/api/cli.html#node-v8-coveragedir) internally. This triggers Node to write lots of unnecessary files on file system. diff --git a/guide/cli-generated.md b/guide/cli-generated.md index abdd2dad..db20e272 100644 --- a/guide/cli-generated.md +++ b/guide/cli-generated.md @@ -299,6 +299,13 @@ Apply exclusions again after coverage has been remapped to original sources. (de Directory of HTML coverage output to be served in UI mode and HTML reporter. +### coverage.autoAttachSubprocess + +- **CLI:** `--coverage.autoAttachSubprocess` +- **Config:** [coverage.autoAttachSubprocess](/config/coverage#coverage-autoattachsubprocess) + +Track coverage of the `node:child_process` and `node:worker_threads` spawned during test run. Supported only by `v8` provider. (default: false) + ### mode - **CLI:** `--mode ` From 6c3732e5999b7dc50edc6ebad00ffe151f18bda0 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 27 Apr 2026 15:37:28 +0200 Subject: [PATCH 07/11] refactor: inline snapshot package (#10210) --- guide/snapshot.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guide/snapshot.md b/guide/snapshot.md index 371a2a7b..e99faccd 100644 --- a/guide/snapshot.md +++ b/guide/snapshot.md @@ -344,7 +344,7 @@ Custom serializers control how values are _rendered_ into snapshot strings, but A domain adapter implements four methods and is generic over two types — `Captured` (what the value actually is) and `Expected` (what the stored snapshot parses into): ```ts -import type { DomainMatchResult, DomainSnapshotAdapter } from '@vitest/snapshot' +import type { DomainMatchResult, DomainSnapshotAdapter } from 'vitest' const myAdapter: DomainSnapshotAdapter = { name: 'my-domain', @@ -420,7 +420,7 @@ expect(value).toMatchMyDomainInlineSnapshot(`key=value`) A minimal adapter that stores objects as `key=value` lines, with regex pattern and subset key match support ([full source](https://github.com/vitest-dev/vitest/blob/main/test/snapshots/test/fixtures/domain/basic.ts)): ```ts [kv-adapter.ts] -import type { DomainMatchResult, DomainSnapshotAdapter } from '@vitest/snapshot' +import type { DomainMatchResult, DomainSnapshotAdapter } from 'vitest' type KVCaptured = Record type KVExpected = Record From d9595487e961ee3eff6602c95c364f530c2cff88 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Tue, 28 Apr 2026 17:40:04 +0900 Subject: [PATCH 08/11] feat: expose default reporters through `configDefaults.reporters` (#10219) Co-authored-by: Claude Opus 4.7 (1M context) --- config/reporters.md | 8 ++++---- guide/reporters.md | 46 ++++++++++++++++++++++++++------------------- guide/ui.md | 2 +- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/config/reporters.md b/config/reporters.md index 2098d8f6..989ac0e2 100644 --- a/config/reporters.md +++ b/config/reporters.md @@ -14,7 +14,7 @@ interface UserConfig { type ConfigReporter = string | Reporter | [string, object?] ``` -- **Default:** [`'default'`](/guide/reporters#default-reporter) (or [['default'](/guide/reporters#default-reporter), ['github-actions'](/guide/reporters#github-actions-reporter)] when `process.env.GITHUB_ACTIONS === 'true'`) +- **Default:** [`'default'`](/guide/reporters#default-reporter). See [Default Reporters](/guide/reporters#default-reporters) for environment-specific behavior. - **CLI:** - `--reporter=tap` for a single reporter - `--reporter=verbose --reporter=github-actions` for multiple reporters @@ -49,14 +49,14 @@ Note that the [coverage](/guide/coverage) feature uses a different [`coverage.re ::: code-group ```js [vitest.config.js] -import { defineConfig } from 'vitest/config' +import { configDefaults, defineConfig } from 'vitest/config' export default defineConfig({ test: { reporters: [ - 'default', + ...configDefaults.reporters, // conditional reporter - process.env.CI ? 'github-actions' : {}, + ...(process.env.CI ? ['html'] : []), // custom reporter from npm package // options are passed down as a tuple [ diff --git a/guide/reporters.md b/guide/reporters.md index 34307954..e423ef3d 100644 --- a/guide/reporters.md +++ b/guide/reporters.md @@ -5,7 +5,7 @@ outline: deep # Reporters -Vitest provides several built-in reporters to display test output in different formats, as well as the ability to use custom reporters. You can select different reporters either by using the `--reporter` command line option, or by including a `reporters` property in your [configuration file](/config/reporters). If no reporter is specified, Vitest will use the `default` reporter as described below. +Vitest provides several built-in reporters to display test output in different formats, as well as the ability to use custom reporters. You can select different reporters either by using the `--reporter` command line option, or by including a `reporters` property in your [configuration file](/config/reporters). If no reporter is specified, Vitest [auto-selects reporters](#default-configuration) based on the environment. Using reporters via command line: @@ -38,6 +38,26 @@ export default defineConfig({ }) ``` +## Default Configuration + +When `reporters` is not configured, Vitest uses the following reporters: + +- [`default`](#default-reporter) in normal terminal runs +- [`minimal`](#minimal-reporter) when Vitest detects an AI coding agent +- [`github-actions`](#github-actions-reporter) is added when `process.env.GITHUB_ACTIONS === 'true'` + +If you configure your own reporters, the configured list replaces the default list. To add a reporter while keeping Vitest's defaults, extend `configDefaults.reporters`: + +```ts +import { configDefaults, defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + reporters: ['json', ...configDefaults.reporters], + }, +}) +``` + ## Reporter Output By default, Vitest's reporters will print their output to the terminal. When using the `json`, `html` or `junit` reporters, you can instead write your tests' output to a file by including an `outputFile` [configuration option](/config/outputfile) either in your Vite configuration file or via CLI. @@ -66,9 +86,11 @@ npx vitest --reporter=json --reporter=default ``` ```ts +import { configDefaults, defineConfig } from 'vitest/config' + export default defineConfig({ test: { - reporters: ['json', 'default'], + reporters: ['json', ...configDefaults.reporters], outputFile: './test-output.json' }, }) @@ -96,11 +118,7 @@ This example will write separate JSON and XML reports as well as printing a verb ### Default Reporter -By default (i.e. if no reporter is specified), Vitest will display summary of running tests and their status at the bottom. Once a suite passes, its status will be reported on top of the summary. - -::: tip -When Vitest detects it is running inside an AI coding agent, the [`minimal`](#minimal-reporter) reporter is used instead to reduce output and minimize token usage. You can override this by explicitly configuring the [`reporters`](/config/reporters) option. -::: +The `default` reporter displays summary of running tests and their status at the bottom. Once a suite passes, its status will be reported on top of the summary. You can disable the summary by configuring the reporter: @@ -550,21 +568,11 @@ export default defineConfig({ ### GitHub Actions Reporter {#github-actions-reporter} Output [workflow commands](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-error-message) -to provide annotations for test failures. This reporter is automatically enabled when the `reporters` option is not configured and `process.env.GITHUB_ACTIONS === 'true'` (on GitHub Actions environment). +to provide annotations for test failures. This reporter is [enabled automatically](#default-configuration) when `process.env.GITHUB_ACTIONS === 'true'` (on GitHub Actions environment). GitHub Actions GitHub Actions -If you configure reporters, you need to explicitly add `github-actions`. - -```ts -export default defineConfig({ - test: { - reporters: process.env.GITHUB_ACTIONS === 'true' ? ['dot', 'github-actions'] : ['dot'], - }, -}) -``` - You can customize the file paths that are printed in [GitHub's annotation command format](https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/workflow-commands-for-github-actions) by using the `onWritePath` option. This is useful when running Vitest in a containerized environment, such as Docker, where the file paths may not match the paths in the GitHub Actions environment. ```ts @@ -662,7 +670,7 @@ export default defineConfig({ Outputs a minimal report containing only failed tests and their error messages. Console logs from passing tests and the summary section are also suppressed. ::: tip Agent Reporter -This reporter is well optimized for AI coding assistants and LLM-based workflows to reduce token usage. It is automatically enabled when no `reporters` option is configured and Vitest detects it is running inside an AI coding agent. If you configure custom reporters, you can explicitly add `minimal` or `agent`: +This reporter is well optimized for AI coding assistants and LLM-based workflows to reduce token usage. It is [enabled automatically](#default-configuration) when Vitest detects it is running inside an AI coding agent. :::code-group ```bash [CLI] diff --git a/guide/ui.md b/guide/ui.md index 78030856..d6c15437 100644 --- a/guide/ui.md +++ b/guide/ui.md @@ -40,7 +40,7 @@ export default defineConfig({ You can check your coverage report in Vitest UI: see [Vitest UI Coverage](/guide/coverage#vitest-ui) for more details. ::: warning -If you still want to see how your tests are running in real time in the terminal, don't forget to add `default` reporter to `reporters` option: `['default', 'html']`. +If you still want to see how your tests are running in real time in the terminal, add `configDefaults.reporters` to the `reporters` option: `['html', ...configDefaults.reporters]`. ::: ::: tip From a623a2f04649c0cf08aaced982073b2d85df276f Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 28 Apr 2026 16:11:22 +0200 Subject: [PATCH 09/11] fix!: represent locator as an object instead of a string (#10212) --- api/browser/locators.md | 59 ++++++++++++++++++++++++++++++++++++++--- guide/migration.md | 22 +++++++++++++++ 2 files changed, 77 insertions(+), 4 deletions(-) diff --git a/api/browser/locators.md b/api/browser/locators.md index a4da5a8c..e4ae66f9 100644 --- a/api/browser/locators.md +++ b/api/browser/locators.md @@ -1051,19 +1051,70 @@ Internally, this method calls `.elements` and wraps every element using [`page.e - [See `locator.elements()`](#elements) +### serialize + +```ts +function serialize(): SerializedLocator +``` + +Returns a JSON-serializable representation of the locator. The returned object has two fields: + +- [`selector`](#selector): the provider-specific selector string used to query the element at runtime. +- `locator`: a human-readable description of the locator (e.g. `getByRole('button')`), used for error messages and tracing. Equivalent to calling [`asLocator()`](#aslocator). + +This is primarily intended for forwarding a locator to a [browser command](/api/browser/commands), which runs in Node and cannot receive a live `Locator` instance: + +```ts +import { commands, page } from 'vitest/browser' + +await commands.myCommand(page.getByRole('button').serialize()) +``` + +::: tip +Vitest automatically serializes any `Locator` argument passed to a command, so calling `serialize()` explicitly is rarely necessary. You can also use `JSON.stringify(locator)` (it calls [`toJSON`](#tojson) internally), which produces the same result. +::: + +### toJSON + +```ts +function toJSON(): SerializedLocator +``` + +Alias of [`serialize`](#serialize). Defined so that `JSON.stringify(locator)` and structured-clone-based transports return a `SerializedLocator` object. + +### asLocator + +```ts +function asLocator(): string +``` + +Returns a human-readable description of the locator using the JavaScript locator syntax (e.g. `getByRole('button', { name: 'Submit' })`). This is the same string exposed as the `locator` field of [`serialize()`](#serialize) and is used in error messages and traces. + +```ts +import { page } from 'vitest/browser' + +const button = page.getByRole('button', { name: 'Submit' }) +button.asLocator() // "getByRole('button', { name: 'Submit' })" +``` + +::: tip +Use [`selector`](#selector) when you need the provider-specific string to forward to a [browser command](/api/browser/commands). Use `asLocator()` only for diagnostic output. The returned string is not meant to be re-used to query elements. +::: + ## Properties ### selector -The `selector` is a string that will be used to locate the element by the browser provider. Playwright will use a `playwright` locator syntax while `preview` and `webdriverio` will use CSS. +The `selector` is a string that will be used to locate the element by the browser provider. Playwright will use a `playwright` locator syntax, and `preview` and `webdriverio` will use CSS. ::: danger You should not use this string in your test code. The `selector` string should only be used when working with the Commands API: ```ts [commands.ts] import type { BrowserCommand } from 'vitest/node' +import type { SerializedLocator } from '@vitest/browser' -const test: BrowserCommand = function test(context, selector) { +const test: BrowserCommand = function test(context, { selector }) { // playwright await context.iframe.locator(selector).click() // webdriverio @@ -1076,8 +1127,8 @@ import { test } from 'vitest' import { commands, page } from 'vitest/browser' test('works correctly', async () => { - await commands.test(page.getByText('Hello').selector) // ✅ - // vitest will automatically unwrap it to a string + await commands.test(page.getByText('Hello').serialize()) // ✅ + // vitest will automatically unwrap it to a SerializedLocator await commands.test(page.getByText('Hello')) // ✅ }) ``` diff --git a/guide/migration.md b/guide/migration.md index 788cf0ab..8d1dd57b 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -34,6 +34,28 @@ test('example', { sequential: true }, async () => { /* ... */ }) // [!code --] test('example', { concurrent: false }, async () => { /* ... */ }) // [!code ++] ``` +### Locators in Commands are Serialized as Objects + +Locators forwarded to [browser commands](/api/browser/commands) are now serialized as a `SerializedLocator` object instead of a bare selector string. The object exposes two fields: + +- `selector`: the provider-specific selector string (the same value commands previously received). +- `locator`: a human-readable representation of the locator (e.g. `getByRole('button')`), used for error messages and tracing. + +Update any custom commands that accept a locator to destructure `selector` from the new object: + +```ts +import type { SerializedLocator } from '@vitest/browser' +import type { BrowserCommandContext } from 'vitest/node' + +export async function customClick( + context: BrowserCommandContext, + selector: string, // [!code --] + { selector }: SerializedLocator, // [!code ++] +) { + await context.page.locator(selector).click() +} +``` + ## Migrating to Vitest 4.0 {#vitest-4} ::: warning Prerequisites From 56e853e8219086186dd3fff53af3a2c8a793e8cd Mon Sep 17 00:00:00 2001 From: Vladimir Date: Wed, 29 Apr 2026 07:20:43 +0200 Subject: [PATCH 10/11] fix!: remove deprecated entry points (#10222) --- api/advanced/runner.md | 2 +- guide/migration.md | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/api/advanced/runner.md b/api/advanced/runner.md index fb8dd451..fa41cbc8 100644 --- a/api/advanced/runner.md +++ b/api/advanced/runner.md @@ -142,7 +142,7 @@ If you don't have a custom runner or didn't define `runTest` method, Vitest will ::: ::: tip -Snapshot support and some other features depend on the runner. If you don't want to lose it, you can extend your runner from `VitestTestRunner` imported from `vitest/runners`. It also exposes `NodeBenchmarkRunner`, if you want to extend benchmark functionality. +Snapshot support and some other features depend on the runner. If you don't want to lose it, you can extend your runner from `TestRunner` imported from `vitest`. It also exposes `NodeBenchmarkRunner`, if you want to extend benchmark functionality. ::: ## Tasks diff --git a/guide/migration.md b/guide/migration.md index 8d1dd57b..45b19e63 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -56,6 +56,19 @@ export async function customClick( } ``` +### Removed Deprecated Entrypoints + +Several entry points were marked as deprecated in Vitest 4.1. This release removes them entirely. + +- `vitest/coverage`: use `vitest/node` instead +- `vitest/reporters`: use `vitest/node` instead +- `vitest/environments`: use `vitest/runtime` instead +- `vitest/snapshot`: use `vitest/runtime` instead +- `vitest/runners`: use `TestRunner` from `vitest` instead +- `vitest/suite`: use static methods on `TestRunner` from vitest instead (for example, `TestRunner.getCurrentTest()`) +- `vitest/mocker` is removed completely, use `@vitest/mocker` package directly (this was published by accident at one point and never removed) +- `vitest/internal/module-runner` is removed + ## Migrating to Vitest 4.0 {#vitest-4} ::: warning Prerequisites From ad185a835a130e727cdfb4ac909f05238364dc6a Mon Sep 17 00:00:00 2001 From: neumaennl Date: Wed, 29 Apr 2026 10:03:21 +0200 Subject: [PATCH 11/11] feat(junit-reporter): add jest-junit-compatible naming options (#10189) Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: neumaennl <7926739+neumaennl@users.noreply.github.com> Co-authored-by: Martin Neumann Co-authored-by: Copilot --- guide/reporters.md | 63 ++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 58 insertions(+), 5 deletions(-) diff --git a/guide/reporters.md b/guide/reporters.md index e423ef3d..4b800ec4 100644 --- a/guide/reporters.md +++ b/guide/reporters.md @@ -349,17 +349,70 @@ AssertionError: expected 5 to be 4 // Object.is equality ``` -The outputted XML contains nested `testsuites` and `testcase` tags. These can also be customized via reporter options `suiteName` and `classnameTemplate`. `classnameTemplate` can either be a template string or a function. +The output XML contains nested `testsuites` → `testsuite` → `testcase` tags. You can customize the reporter's behaviour with the following options: + +| Option | Description | Default | +|---|---|---| +| `suiteName` | `name` attribute of `` | `"vitest tests"` | +| `suiteNameTemplate` | Template for the `name` attribute of ``. Accepts a string with placeholders or a function. | Relative file path | +| `classnameTemplate` | Template for the `classname` attribute of ``. Accepts a string with placeholders or a function. | Relative file path | +| `titleTemplate` | Template for the `name` attribute of ``. Accepts a string with placeholders or a function. | Full test title with ancestor hierarchy | +| `ancestorSeparator` | Separator used when joining ancestor describe block names in the `{classname}` placeholder and in the default test title. | `" > "` | +| `addFileAttribute` | Add a `file` attribute to each ``. | `false` | +| `includeConsoleOutput` | Include `` / `` console output. | `true` | +| `stackTrace` | Include stack traces in `` elements. | `true` | + +The following placeholders are available for `suiteNameTemplate`: +- `{title}` – name of the first top-level `describe` block; falls back to the file basename when there is no top-level `describe` +- `{filename}` – relative file path from the root (e.g. `src/foo.test.ts`) +- `{filepath}` – absolute file path +- `{basename}` – file name without directory (e.g. `foo.test.ts`) +- `{displayName}` – Vitest project name + +The following placeholders are available for `classnameTemplate` and `titleTemplate`: +- `{classname}` – ancestor `describe` block names joined by `ancestorSeparator` (e.g. `outer > inner`) +- `{title}` – leaf test title (the string passed to `it`/`test`) +- `{suitename}` – top-level `describe` block name, empty string when the test has no enclosing `describe` +- `{filename}` – relative file path from the root +- `{filepath}` – absolute file path +- `{basename}` – file name without directory +- `{displayName}` – Vitest project name -The supported placeholders for the `classnameTemplate` option are: -- filename -- filepath +::: tip +`{filename}` follows Vitest's convention and resolves to the **relative path** from the project root (e.g. `src/foo.test.ts`). This differs from jest-junit where `{filename}` is the bare file name. Use `{basename}` to get only the file name. +::: + +```ts +export default defineConfig({ + test: { + reporters: [ + ['junit', { + suiteName: 'My Test Suite', + // Use the first top-level describe block name as the testsuite name + suiteNameTemplate: '{title}', + // classname = ancestor describe chain + classnameTemplate: '{classname}', + // name = leaf test title only (jest-junit-compatible) + titleTemplate: '{title}', + ancestorSeparator: ' > ', + }] + ] + }, +}) +``` + +Function-based templates receive all available variables and can return any string: ```ts export default defineConfig({ test: { reporters: [ - ['junit', { suiteName: 'custom suite name', classnameTemplate: 'filename:{filename} - filepath:{filepath}' }] + ['junit', { + classnameTemplate: ({ classname, filename }) => + classname ? `${filename}::${classname}` : filename, + titleTemplate: ({ suitename, title }) => + suitename ? `[${suitename}] ${title}` : title, + }] ] }, })