From b03beabf6efd4475be8ca9589d97e32628df989e Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Mon, 25 May 2026 15:15:57 +0900 Subject: [PATCH 1/9] docs: add custom browser command example to show how to validate input (#10443) Co-authored-by: Codex --- api/browser/commands.md | 52 ++++++++++++++++++++++++++++++++++++-- config/browser/commands.md | 6 +++++ 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/api/browser/commands.md b/api/browser/commands.md index 6306ae6e..73f782c5 100644 --- a/api/browser/commands.md +++ b/api/browser/commands.md @@ -16,9 +16,9 @@ You can use the `readFile`, `writeFile`, and `removeFile` APIs to handle files i By default, Vitest uses `utf-8` encoding but you can override it with options. ::: tip -This API follows [`server.fs`](https://vitejs.dev/config/server-options.html#server-fs-allow) limitations for security reasons. +The built-in file commands follow Vite's [`server.fs`](https://vitejs.dev/config/server-options.html#server-fs-allow) restrictions for security reasons. -If [`browser.api.allowWrite`](/config/browser/api) or [`api.allowWrite`](/config/api#api-allowwrite) are disabled, `writeFile` and `removeFile` functions won't do anything. +`writeFile` and `removeFile` also require write access through [`browser.api.allowWrite`](/config/browser/api) and [`api.allowWrite`](/config/api#api-allowwrite). ::: ```ts @@ -124,6 +124,54 @@ declare module 'vitest/browser' { Custom functions will override built-in ones if they have the same name. ::: +::: warning Security +Custom commands run in the Vitest Node process and are callable from browser test code through Vitest's browser RPC connection. They can access local files, environment variables, network services, databases, shell commands, and other Node APIs. + +Vitest's built-in file commands validate paths against Vite's [`server.fs`](https://vite.dev/config/server-options#server-fs-allow) restrictions and separately check whether writes are allowed. Custom commands do not automatically inherit these protections. If a custom command accepts browser-provided input and uses it to read, write, delete, execute, or expose local resources, validate that input before using it. + +For file reads or fixture loading, use `isFileLoadingAllowed` from `vitest/node` or an explicit allowlist. For writes and deletes, also require an explicit mutation policy, such as [`browser.api.allowWrite`](/config/browser/api#api-allowwrite), [`api.allowWrite`](/config/api#api-allowwrite), and a command-specific allowed directory. For commands that execute code, shell commands, or project scripts, also check [`browser.api.allowExec`](/config/browser/api#api-allowexec) and [`api.allowExec`](/config/api#api-allowexec). + +For example, if you create your own file-writing command instead of using Vitest's built-in `writeFile`, apply the same checks: + +```ts +import { mkdir, writeFile } from 'node:fs/promises' +import { dirname, resolve } from 'node:path' +import { normalizePath } from 'vite' +import { isFileLoadingAllowed } from 'vitest/node' +import type { BrowserCommand } from 'vitest/node' + +function assertFileAccess(path: string, project: any) { + if ( + !isFileLoadingAllowed(project.vite.config, path) + && !isFileLoadingAllowed(project.vitest.vite.config, path) + ) { + throw new Error(`Access denied to "${path}".`) + } +} + +function assertWrite(project: any) { + if (!project.config.browser.api.allowWrite || !project.vitest.config.api.allowWrite) { + throw new Error('Writing files is disabled.') + } +} + +export const myWriteFileCommand: BrowserCommand<[path: string, content: string]> = async ( + { project }, + path, + content, +) => { + assertWrite(project) + + const file = resolve(project.config.root, path) + assertFileAccess(normalizePath(file), project) + + await mkdir(dirname(file), { recursive: true }) + await writeFile(file, content) +} +``` + +::: + ### Recording trace markers Custom commands can record [trace markers](/api/browser/context#mark) for the test that triggered them through `context.mark`. This is the server-side equivalent of `page.mark` and helps annotate the [trace view](/guide/browser/trace-view) with custom actions performed inside a command. diff --git a/config/browser/commands.md b/config/browser/commands.md index b933f093..8d53c636 100644 --- a/config/browser/commands.md +++ b/config/browser/commands.md @@ -9,3 +9,9 @@ outline: deep - **Default:** `{ readFile, writeFile, ... }` Custom [commands](/api/browser/commands) that can be imported during browser tests from `vitest/browser`. + +::: warning Security +Commands run in the Vitest Node process. If a command exposes filesystem, process, network, database, or shell access based on browser-provided input, validate and restrict that input inside the command. Built-in file commands apply Vite `server.fs` checks and write-access checks, but custom commands are responsible for their own protections. + +See [Custom Commands security notes](/api/browser/commands#custom-commands). +::: From c59e171db21be0783b10dbec81c9df7a750f4f48 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Tue, 26 May 2026 16:23:32 +0200 Subject: [PATCH 2/9] docs: update sponsors (#10462) --- .vitepress/sponsors.ts | 10 +++---- public/kraken.svg | 66 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 71 insertions(+), 5 deletions(-) create mode 100644 public/kraken.svg diff --git a/.vitepress/sponsors.ts b/.vitepress/sponsors.ts index 235e1ede..6bee0075 100644 --- a/.vitepress/sponsors.ts +++ b/.vitepress/sponsors.ts @@ -43,11 +43,6 @@ export const sponsors: SponsorTier[] = [ url: 'https://vital.io/', img: '/vital.svg', }, - { - name: 'OOMOL', - url: 'https://oomol.com/', - img: '/oomol.svg', - }, { name: 'Mailmeteor', url: 'https://mailmeteor.com/', @@ -58,6 +53,11 @@ export const sponsors: SponsorTier[] = [ url: 'https://www.liminity.se/', img: '/liminity.svg', }, + { + name: 'Kraken Tech', + url: 'https://kraken.tech/', + img: '/kraken.svg', + }, { name: 'Aerius Ventilation', url: 'https://aerius.se/', diff --git a/public/kraken.svg b/public/kraken.svg new file mode 100644 index 00000000..52ecee1f --- /dev/null +++ b/public/kraken.svg @@ -0,0 +1,66 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file From fb3dfb6e1c30b7f3c2d0325472dc43cfc5c03214 Mon Sep 17 00:00:00 2001 From: Hiroshi Ogawa Date: Thu, 28 May 2026 17:01:53 +0900 Subject: [PATCH 3/9] fix(browser): disable client `cdp` API when `allowWrite/allowExec: false` (#10444) Co-authored-by: Codex --- api/browser/commands.md | 2 ++ api/browser/context.md | 2 ++ config/browser/api.md | 4 ++-- 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/api/browser/commands.md b/api/browser/commands.md index 73f782c5..af72781a 100644 --- a/api/browser/commands.md +++ b/api/browser/commands.md @@ -59,6 +59,8 @@ expect(input).toHaveValue('a') ::: warning CDP session works only with `playwright` provider and only when using `chromium` browser. You can read more about it in playwright's [`CDPSession`](https://playwright.dev/docs/api/class-cdpsession) documentation. + +CDP is a privileged debugging API. It is available only when browser API write and exec operations are enabled through [`browser.api.allowWrite`](/config/browser/api#api-allowwrite), [`browser.api.allowExec`](/config/browser/api#api-allowexec), [`api.allowWrite`](/config/api#api-allowwrite), and [`api.allowExec`](/config/api#api-allowexec). ::: ## Custom Commands diff --git a/api/browser/context.md b/api/browser/context.md index f4e78b2c..97658f8a 100644 --- a/api/browser/context.md +++ b/api/browser/context.md @@ -211,6 +211,8 @@ The `cdp` export returns the current Chrome DevTools Protocol session. It is mos ::: warning CDP session works only with `playwright` provider and only when using `chromium` browser. You can read more about it in playwright's [`CDPSession`](https://playwright.dev/docs/api/class-cdpsession) documentation. + +CDP is a privileged debugging API. It is available only when browser API write and exec operations are enabled through [`browser.api.allowWrite`](/config/browser/api#api-allowwrite), [`browser.api.allowExec`](/config/browser/api#api-allowexec), [`api.allowWrite`](/config/api#api-allowwrite), and [`api.allowExec`](/config/api#api-allowexec). ::: ```ts diff --git a/config/browser/api.md b/config/browser/api.md index 1e2a9bfb..86e85c61 100644 --- a/config/browser/api.md +++ b/config/browser/api.md @@ -16,7 +16,7 @@ Configure options for Vite server that serves code in the browser. Does not affe - **Type:** `boolean` - **Default:** `true` if not exposed to the network, `false` otherwise -Vitest saves [annotation attachments](/guide/test-annotations), [artifacts](/api/advanced/artifacts) and [snapshots](/guide/snapshot) by receiving a WebSocket connection from the browser. This allows anyone who can connect to the API write any arbitrary code on your machine within the root of your project (configured by [`fs.allow`](https://vite.dev/config/server-options#server-fs-allow)). +Vitest saves [annotation attachments](/guide/test-annotations), [artifacts](/api/advanced/artifacts) and [snapshots](/guide/snapshot) by receiving a WebSocket connection from the browser. This allows anyone who can connect to the API write any arbitrary code on your machine within the root of your project (configured by [`fs.allow`](https://vite.dev/config/server-options#server-fs-allow)). This option also gates privileged browser APIs that can write files indirectly, such as raw Chrome DevTools Protocol access through [`cdp()`](/api/browser/context#cdp). If browser server is not exposed to the internet (the host is `localhost`), this should not be a problem, so the default value in that case is `true`. If you override the host, Vitest will set `allowWrite` to `false` by default to prevent potentially harmful writes. @@ -25,4 +25,4 @@ If browser server is not exposed to the internet (the host is `localhost`), this - **Type:** `boolean` - **Default:** `true` if not exposed to the network, `false` otherwise -Allows running any test file via the UI. This only applies to the interactive elements (and the server code behind them) in the [UI](/guide/ui) that can run the code. If UI is disabled, this has no effect. See [`api.allowExec`](/config/api#api-allowexec) for more information. +Allows running any test file via the UI. This applies to the interactive elements (and the server code behind them) in the [UI](/guide/ui) that can run the code. This option also gates privileged browser APIs that can execute code indirectly, such as raw Chrome DevTools Protocol access through [`cdp()`](/api/browser/context#cdp). See [`api.allowExec`](/config/api#api-allowexec) for more information. From a520cb5b1cd8fb3a9cbcd2d7fbeb986c0b891ea1 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Fri, 29 May 2026 13:05:07 +0200 Subject: [PATCH 4/9] feat(benchmark)!: rewrite the public API (#10113) --- .vitepress/config.ts | 4 + api/advanced/vitest.md | 30 +- api/test.md | 233 +---------- config/benchmark.md | 44 +- config/index.md | 2 +- config/runner.md | 1 - guide/advanced/index.md | 6 +- guide/benchmarking.md | 480 ++++++++++++++++++++++ guide/cli-generated.md | 2 +- guide/migration.md | 30 ++ guide/snippets/benchmark-per-project.ansi | 7 + guide/snippets/benchmark-table.ansi | 9 + guide/test-context.md | 23 ++ guide/test-tags.md | 2 +- 14 files changed, 578 insertions(+), 295 deletions(-) create mode 100644 guide/benchmarking.md create mode 100644 guide/snippets/benchmark-per-project.ansi create mode 100644 guide/snippets/benchmark-table.ansi diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 0f324f9b..6a57443d 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -901,6 +901,10 @@ export default ({ mode }: { mode: string }) => { text: 'Testing Types', link: '/guide/testing-types', }, + { + text: 'Benchmarking', + link: '/guide/benchmarking', + }, { text: 'In-Source Testing', link: '/guide/in-source', diff --git a/api/advanced/vitest.md b/api/advanced/vitest.md index 37e335f2..8ae0e43d 100644 --- a/api/advanced/vitest.md +++ b/api/advanced/vitest.md @@ -5,35 +5,9 @@ title: Vitest API # Vitest -Vitest instance requires the current test mode. It can be either: - -- `test` when running runtime tests -- `benchmark` when running benchmarks - -::: details New in Vitest 4 -Vitest 4 added several new APIs (they are marked with a "4.0.0+" badge) and removed deprecated APIs: - -- `invalidates` -- `changedTests` (use [`onFilterWatchedSpecification`](#onfilterwatchedspecification) instead) -- `server` (use [`vite`](#vite) instead) -- `getProjectsByTestFile` (use [`getModuleSpecifications`](#getmodulespecifications) instead) -- `getFileWorkspaceSpecs` (use [`getModuleSpecifications`](#getmodulespecifications) instead) -- `getModuleProjects` (filter by [`this.projects`](#projects) yourself) -- `updateLastChanged` (renamed to [`invalidateFile`](#invalidatefile)) -- `globTestSpecs` (use [`globTestSpecifications`](#globtestspecifications) instead) -- `globTestFiles` (use [`globTestSpecifications`](#globtestspecifications) instead) -- `listFile` (use [`getRelevantTestSpecifications`](#getrelevanttestspecifications) instead) -::: - -## mode - -### test - -Test mode will only call functions inside `test` or `it`, and throws an error when `bench` is encountered. This mode uses `include` and `exclude` options in the config to find test files. - -### benchmark {#benchmark} +## mode {#mode} -Benchmark mode calls `bench` functions and throws an error, when it encounters `test` or `it`. This mode uses `benchmark.include` and `benchmark.exclude` options in the config to find benchmark files. +Since Vitest 5, this property is always `'test'`. ## config diff --git a/api/test.md b/api/test.md index 670f4006..e6d074f1 100644 --- a/api/test.md +++ b/api/test.md @@ -670,235 +670,8 @@ Scoped `aroundAll` hook that inherits types from [`test.extend`](#test-extend). ## bench {#bench} -- **Type:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void` +::: warning Updated in Vitest 5 +The benchmarking API has been rewritten. `bench` is no longer a top-level import from `vitest`, and the `bench.skip` / `bench.only` / `bench.todo` helpers have been removed. `bench` is now a [test-context fixture](/guide/test-context#bench) accessed from inside a `test()`. -::: danger -Benchmarking is experimental and does not follow SemVer. +See the [Benchmarking guide](/guide/benchmarking) for the new API. ::: - -`bench` defines a benchmark. In Vitest terms, benchmark is a function that defines a series of operations. Vitest runs this function multiple times to display different performance results. - -Vitest uses the [`tinybench`](https://github.com/tinylibs/tinybench) library under the hood, inheriting all its options that can be used as a third argument. - -```ts -import { bench } from 'vitest' - -bench('normal sorting', () => { - const x = [1, 5, 4, 2, 3] - x.sort((a, b) => { - return a - b - }) -}, { time: 1000 }) -``` - -```ts -export interface Options { - /** - * time needed for running a benchmark task (milliseconds) - * @default 500 - */ - time?: number - - /** - * number of times that a task should run if even the time option is finished - * @default 10 - */ - iterations?: number - - /** - * function to get the current timestamp in milliseconds - */ - now?: () => number - - /** - * An AbortSignal for aborting the benchmark - */ - signal?: AbortSignal - - /** - * Throw if a task fails (events will not work if true) - */ - throws?: boolean - - /** - * warmup time (milliseconds) - * @default 100ms - */ - warmupTime?: number - - /** - * warmup iterations - * @default 5 - */ - warmupIterations?: number - - /** - * setup function to run before each benchmark task (cycle) - */ - setup?: Hook - - /** - * teardown function to run after each benchmark task (cycle) - */ - teardown?: Hook -} -``` -After the test case is run, the output structure information is as follows: - -``` - name hz min max mean p75 p99 p995 p999 rme samples -· normal sorting 6,526,368.12 0.0001 0.3638 0.0002 0.0002 0.0002 0.0002 0.0004 ±1.41% 652638 -``` -```ts -export interface TaskResult { - /* - * the last error that was thrown while running the task - */ - error?: unknown - - /** - * The amount of time in milliseconds to run the benchmark task (cycle). - */ - totalTime: number - - /** - * the minimum value in the samples - */ - min: number - /** - * the maximum value in the samples - */ - max: number - - /** - * the number of operations per second - */ - hz: number - - /** - * how long each operation takes (ms) - */ - period: number - - /** - * task samples of each task iteration time (ms) - */ - samples: number[] - - /** - * samples mean/average (estimate of the population mean) - */ - mean: number - - /** - * samples variance (estimate of the population variance) - */ - variance: number - - /** - * samples standard deviation (estimate of the population standard deviation) - */ - sd: number - - /** - * standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean) - */ - sem: number - - /** - * degrees of freedom - */ - df: number - - /** - * critical value of the samples - */ - critical: number - - /** - * margin of error - */ - moe: number - - /** - * relative margin of error - */ - rme: number - - /** - * median absolute deviation - */ - mad: number - - /** - * p50/median percentile - */ - p50: number - - /** - * p75 percentile - */ - p75: number - - /** - * p99 percentile - */ - p99: number - - /** - * p995 percentile - */ - p995: number - - /** - * p999 percentile - */ - p999: number -} -``` - -### bench.skip - -- **Type:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void` - -You can use `bench.skip` syntax to skip running certain benchmarks. - -```ts -import { bench } from 'vitest' - -bench.skip('normal sorting', () => { - const x = [1, 5, 4, 2, 3] - x.sort((a, b) => { - return a - b - }) -}) -``` - -### bench.only - -- **Type:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void` - -Use `bench.only` to only run certain benchmarks in a given suite. This is useful when debugging. - -```ts -import { bench } from 'vitest' - -bench.only('normal sorting', () => { - const x = [1, 5, 4, 2, 3] - x.sort((a, b) => { - return a - b - }) -}) -``` - -### bench.todo - -- **Type:** `(name: string | Function) => void` - -Use `bench.todo` to stub benchmarks to be implemented later. - -```ts -import { bench } from 'vitest' - -bench.todo('unimplemented test') -``` diff --git a/config/benchmark.md b/config/benchmark.md index 8958912f..9b63dc40 100644 --- a/config/benchmark.md +++ b/config/benchmark.md @@ -9,6 +9,13 @@ outline: deep Options used when running `vitest bench`. +## benchmark.enabled + +- **Type:** `boolean` +- **Default:** `false` + +Enables the benchmark project. When set, Vitest creates a dedicated benchmark project alongside your regular test project, runs files matching [`benchmark.include`](#benchmark-include) in it, and exposes the [`bench` fixture](/guide/test-context#bench) to those files. Running `vitest bench` enables this automatically. + ## benchmark.include - **Type:** `string[]` @@ -32,39 +39,18 @@ Include globs for in-source benchmark test files. This option is similar to [`in When defined, Vitest will run all matched files with `import.meta.vitest` inside. -## benchmark.reporters - -- **Type:** `Arrayable` -- **Default:** `'default'` - -Custom reporter for output. Can contain one or more built-in report names, reporter instances, and/or paths to custom reporters. - -## benchmark.outputFile - -Deprecated in favor of `benchmark.outputJson`. - -## benchmark.outputJson {#benchmark-outputJson} - -- **Type:** `string | undefined` -- **Default:** `undefined` +## benchmark.retainSamples -A file path to store the benchmark result, which can be used for `--compare` option later. +- **Type:** `boolean` +- **Default:** `false` -For example: +Include the `samples` array of per-iteration timings on every benchmark result. Disabled by default to reduce memory usage; enable when a custom reporter or API consumer needs the raw samples. -```sh -# save main branch's result -git checkout main -vitest bench --outputJson main.json -# change a branch and compare against main -git checkout feature -vitest bench --compare main.json -``` +## benchmark.suppressExportGetterWarnings -## benchmark.compare {#benchmark-compare} +- **Type:** `boolean` +- **Default:** `false` -- **Type:** `string | undefined` -- **Default:** `undefined` +Suppress the warning printed when a benchmark accesses module export getters too many times. Vitest tracks getter access during benchmark runs because Vite's module runner wraps every export in a getter, and excessive access can dominate the measurement (see [Module Runner Overhead](/guide/benchmarking#module-runner-overhead)). Enable this when you've intentionally accepted the overhead, or when the warning is noisy for benchmarks where the getter cost is negligible. -A file path to a previous benchmark result to compare against current runs. diff --git a/config/index.md b/config/index.md index e7182476..3a477823 100644 --- a/config/index.md +++ b/config/index.md @@ -8,7 +8,7 @@ If you are using Vite and have a `vite.config` file, Vitest will read it to matc - Create `vitest.config.ts`, which will have the higher priority and will **override** the configuration from `vite.config.ts` (Vitest supports all conventional JS and TS extensions, but doesn't support `json`) - it means all options in your `vite.config` will be **ignored** - Pass `--config` option to CLI, e.g. `vitest --config ./path/to/vitest.config.ts` -- Use `process.env.VITEST` or `mode` property on `defineConfig` (will be set to `test`/`benchmark` if not overridden with `--mode`) to conditionally apply different configuration in `vite.config.ts`. Note that like any other environment variable, `VITEST` is also exposed on `import.meta.env` in your tests +- Use `process.env.VITEST` or `mode` property on `defineConfig` (will be set to `test` if not overridden with `--mode`) to conditionally apply different configuration in `vite.config.ts`. Note that like any other environment variable, `VITEST` is also exposed on `import.meta.env` in your tests To configure `vitest` itself, add `test` property in your Vite config. You'll also need to add a reference to Vitest types using a [triple slash command](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-) at the top of your config file, if you are importing `defineConfig` from `vite` itself. diff --git a/config/runner.md b/config/runner.md index 1d47b953..cdcc3100 100644 --- a/config/runner.md +++ b/config/runner.md @@ -6,6 +6,5 @@ outline: deep # runner - **Type:** `VitestRunnerConstructor` -- **Default:** `node`, when running tests, or `benchmark`, when running benchmarks Path to a custom test runner. This is an advanced feature and should be used with custom library runners. You can read more about it in [the documentation](/api/advanced/runner). diff --git a/guide/advanced/index.md b/guide/advanced/index.md index 01bca471..f796eb5e 100644 --- a/guide/advanced/index.md +++ b/guide/advanced/index.md @@ -14,7 +14,6 @@ You can import any method from the `vitest/node` entry-point. ```ts function startVitest( - mode: VitestRunMode, cliFilters: string[] = [], options: CliOptions = {}, viteOverrides?: ViteUserConfig, @@ -27,7 +26,7 @@ You can start running Vitest tests using its Node API: ```js import { startVitest } from 'vitest/node' -const vitest = await startVitest('test') +const vitest = await startVitest() await vitest.close() ``` @@ -47,7 +46,7 @@ After running the tests, you can get the results from the [`state.getTestModules ```ts import type { TestModule } from 'vitest/node' -const vitest = await startVitest('test') +const vitest = await startVitest() console.log(vitest.state.getTestModules()) // [TestModule] ``` @@ -60,7 +59,6 @@ The ["Running Tests"](/guide/advanced/tests#startvitest) guide has a usage examp ```ts function createVitest( - mode: VitestRunMode, options: CliOptions, viteOverrides: ViteUserConfig = {}, vitestOptions: VitestOptions = {}, diff --git a/guide/benchmarking.md b/guide/benchmarking.md new file mode 100644 index 00000000..bfa5df87 --- /dev/null +++ b/guide/benchmarking.md @@ -0,0 +1,480 @@ +--- +title: Benchmarking | Guide +--- + +# Benchmarking + +Vitest lets you write benchmarks alongside your tests using the `bench` fixture from the [test context](/guide/test-context). Benchmarks are powered by [Tinybench](https://github.com/tinylibs/tinybench) and are defined inside regular `test()` calls, giving you access to the full power of Vitest's test runner: retries, lifecycle hooks, filtering, and assertions. + +## Defining a Benchmark + +Use the `bench` fixture to define a benchmark. Call `.run()` to execute it: + +```ts +import { expect, test } from 'vitest' + +test('parsing performance', async ({ bench }) => { + const result = await bench('parse', () => { + JSON.parse('{"key":"value"}') + }).run() +}) +``` + +The `bench()` function registers a benchmark without executing it. Calling `.run()` runs the benchmark and returns the result. After the test completes, Vitest prints a single-row version of the [comparison table](#comparing-benchmarks) (ops/sec, mean time, percentiles, etc.), so you get the same output for a one-off benchmark as you do for `bench.compare()`. + +::: warning +The `bench` fixture is only available in files matched by [`benchmark.include`](/config/#benchmark-include) (default: `**/*.{bench,benchmark}.?(c|m)[jt]s?(x)`). Using `{ bench }` inside a regular test file will throw an error. + +Whether a file participates in the benchmark run is decided by the filename, not by whether the test uses the `bench` fixture. Renaming `parser.test.ts` to `parser.bench.ts` (or adjusting `benchmark.include`) is what moves it into the benchmark project. +::: + +## Running Benchmarks + +Benchmark files are matched by [`benchmark.include`](/config/#benchmark-include) (default: `**/*.{bench,benchmark}.?(c|m)[jt]s?(x)`) and run in their own project, separate from your regular tests. There are three ways to run them, depending on whether you want to skip them, run them alongside tests, or run them on their own. + +### `vitest` (default) + +Without [`benchmark.enabled`](/config/#benchmark-enabled), the `vitest` command only runs regular tests. Benchmark files are ignored entirely. This is the default and the right choice for day-to-day development, since benchmarks are slow and noisy and shouldn't run on every save. + +### `vitest` with `benchmark.enabled` + +Set `benchmark.enabled: true` in your config to run benchmarks together with regular tests: + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + benchmark: { + enabled: true, + }, + }, +}) +``` + +With this config, `vitest` runs your regular tests first, then runs the benchmarks in a separate isolated group (so benchmark execution never overlaps with test execution and adds noise to results). Useful in CI when you want a single command to validate correctness and performance. + +### `vitest bench` + +The `bench` subcommand runs only benchmarks and skips regular tests: + +```bash +vitest bench +``` + +This implicitly enables `benchmark.enabled` for the run, so you don't need to set it in the config. Like the `vitest` command, it accepts filename filters and `-t`/`--testNamePattern` to narrow the run: + +```bash +# only benchmarks in files matching "parser" +vitest bench parser + +# only benchmarks whose test name matches "JSON" +vitest bench -t JSON +``` + +## Comparing Benchmarks + +Use `bench.compare()` to compare multiple benchmarks against each other: + +```ts +import { expect, test } from 'vitest' + +test('compare JSON libraries', async ({ bench }) => { + const input = '{"key":"value","nested":{"a":1}}' + + const result = await bench.compare( + bench('JSON.parse', () => { + JSON.parse(input) + }), + bench('custom parser', () => { + customParse(input) + }), + ) +}) +``` + +When comparing benchmarks, Vitest runs them using interleaved iterations to reduce environmental bias (CPU throttling, GC pressure, etc.) and prints a comparison table after the test completes: + +<<< ./snippets/benchmark-table.ansi + +### Options + +You can pass [options](https://tinylibs.github.io/tinybench/interfaces/BenchOptions.html) as the last argument to `bench.compare()`: + +```ts +test('compare with options', async ({ bench }) => { + const result = await bench.compare( + bench('lib1', () => { lib1() }), + bench('lib2', () => { lib2() }), + { + iterations: 100, + time: 1000, + }, + ) +}) +``` + +You can also pass per-benchmark [options](https://tinylibs.github.io/tinybench/interfaces/FnOptions.html) as the second argument, matching how `test()` accepts options: + +```ts +test('benchmarks with setup', async ({ bench }) => { + const result = await bench.compare( + bench('with-cache', () => { + readFromCache() + }), + bench( + 'without-cache', + { beforeEach: () => clearCache() }, + () => { readFromDisk() }, + ), + ) +}) +``` + +## Comparing Across Projects + +When your workspace defines multiple projects (e.g., different browsers or runtimes), pass `perProject: true` in the bench options to compare how the same benchmark performs across all of them. Vitest still prints the result inline for the current project, and additionally collects per-project results into a single comparison table at the end of the test run. + +```ts +import { test } from 'vitest' + +test('simple example', async ({ bench }) => { + await bench('1 + 1', { perProject: true }, () => { + 1 + 1 + }).run() +}) +``` + +The same test file runs in each project (chromium, firefox, webkit, etc.), and Vitest groups the results: + +<<< ./snippets/benchmark-per-project.ansi + +You can also mix `perProject` benchmarks with regular ones inside `bench.compare()`: + +```ts +test('compare implementations across browsers', async ({ bench }) => { + await bench.compare( + bench('JSON.parse', { perProject: true }, () => { + JSON.parse('{"key":"value"}') + }), + bench('custom parser', () => { + customParse('{"key":"value"}') + }), + ) +}) +``` + +In this case, `custom parser` appears in the normal inline comparison table per project, while `JSON.parse` is additionally collected into the cross-project comparison table at the end. + +## Asserting Performance + +Use `toBeFasterThan()` and `toBeSlowerThan()` matchers to assert relative performance between benchmarks: + +```ts +import { expect, test } from 'vitest' + +test('lib1 is faster than lib2', async ({ bench }) => { + const result = await bench.compare( + bench('lib1', () => { lib1() }), + bench('lib2', () => { lib2() }), + ) + + expect(result.get('lib1')).toBeFasterThan(result.get('lib2')) +}) +``` + +The `delta` option specifies the minimum relative difference required for the assertion to pass. This helps avoid flaky tests caused by benchmark noise: + +```ts +// lib1 must be at least 10% faster than lib2 +expect(result.get('lib1')).toBeFasterThan(result.get('lib2'), { + delta: 0.1, +}) + +// lib2 must be at least 20% slower than lib1 +expect(result.get('lib2')).toBeSlowerThan(result.get('lib1'), { + delta: 0.2, +}) +``` + +You can also assert absolute performance using standard matchers: + +```ts +test('parsing is fast enough', async ({ bench }) => { + const result = await bench('parse', () => { + parse(largeInput) + }).run() + + expect(result.throughput.mean).toBeGreaterThan(10_000) +}) +``` + +## Retries + +Since benchmarks can be noisy, use the `retry` option to automatically retry failing benchmark tests: + +```ts +test('performance comparison', { retry: 3 }, async ({ bench }) => { + const result = await bench.compare( + bench('lib1', () => { lib1() }), + bench('lib2', () => { lib2() }), + ) + + expect(result.get('lib1')).toBeFasterThan(result.get('lib2')) +}) +``` + +## Storing and Replaying Results + +Two primitives let you persist benchmark results to disk and compare against them in future runs: the `writeResult` option saves a result, and `bench.from()` reads one back. + +### `writeResult` + +Pass `writeResult` as a per-bench option to write the result to a JSON file every time the benchmark runs. The path is resolved against the project root: + +```ts +test('parse', async ({ bench }) => { + await bench( + 'parse', + { writeResult: './benchmarks/parse.json' }, + () => parse(largeInput), + ).run() +}) +``` + +- The benchmark always runs. There is no skip-when-cached behaviour and no CLI flag, the file is overwritten on every successful run. +- If the function throws, the file is not written. +- Commit these files alongside your code so reviewers and CI share the same reference points. + +::: warning +If you commit these files, keep in mind that benchmark results vary significantly between environments (developer machines, CI runners, different OSes). Designate a single environment (typically CI) to generate the file, and avoid regenerating it locally. +::: + +### `bench.from()` + +`bench.from(name, source)` is a registration that doesn't execute a function. It reads a previously stored result and feeds it into `bench.compare()` (or returns it directly when you call `.run()`). + +The source can be a path (relative to the project root) or a function that returns the result data, including a Promise: + +```ts +test('compare against the stored baseline', async ({ bench }) => { + const result = await bench.compare( + bench( + 'current', + { writeResult: './benchmarks/parse.json' }, + () => parse(largeInput), + ), + bench.from('previous', './benchmarks/parse.json'), + bench.from('remote', () => fetch('https://path/to/external/file.json').then(r => r.json())), + ) + + expect(result.get('current')).toBeFasterThan(result.get('previous')) +}) +``` + +You can keep historical artifacts for older versions and compare them against the current implementation. Because `bench.from()` never invokes the function that produced the file, the original benchmark code can be deleted once the artifact is committed: + +```ts +test('compare parser versions', async ({ bench }) => { + const input = '{"key":"value"}' + + await bench.compare( + bench.from('v1', './benchmarks/parse.v1.json'), + bench.from('v2', './benchmarks/parse.v2.json'), + bench( + 'current', + { writeResult: './benchmarks/parse.current.json' }, + () => customParser(input), + ), + ) +}) +``` + +To produce a new historical artifact, point a fresh `bench()` at that version's implementation, set `writeResult` to a versioned path (`./benchmarks/parse.v3.json`), run it once, then replace the call with `bench.from('v3', './benchmarks/parse.v3.json')`. + +To regenerate the baseline on demand, gate the write behind an environment variable so the same test either refreshes the artifact or compares against it: + +```ts +test('compare parser versions', async ({ bench }) => { + if (import.meta.env.VITE_WRITE_BENCH) { + const baseline = bench('baseline', { writeResult: './my-bench.json' }, () => fn()) + await baseline.run() + } + else { + const baseline = bench.from('baseline', './my-bench.json') + await bench.compare(bench('current', () => fn()), baseline) + } +}) +``` + +Run `VITE_WRITE_BENCH=1 vitest bench` to refresh the stored result, and `vitest bench` to compare the current implementation against it. + +### Per-project artifacts + +In a multi-project workspace (different browsers, different runtimes), share one benchmark file across projects by including `${projectName}` in the path. The placeholder is substituted with the current project name at write time: + +```ts +test('cross-project baseline', async ({ bench }) => { + await bench( + 'parse', + // eslint-disable-next-line no-template-curly-in-string + { perProject: true, writeResult: './benchmarks/parse.${projectName}.json' }, + () => parse(largeInput), + ).run() +}) +``` + +Use the same template in `bench.from()` so each project reads its own artifact. + +## Stability + +Benchmarks are inherently flaky: CPU load, thermal throttling, GC pressure, and background processes all affect results. Vitest takes several steps to minimize this noise: + +- **Separate project**: Benchmark files are grouped into their own project based on the [`benchmark.include`](/config/#benchmark-include) pattern. The `bench` fixture is only exposed in files matched by that pattern. Using it inside a regular test file will throw an error. +- **No concurrency**: Tests within a benchmark file always run sequentially. Benchmark files themselves also run one at a time, never in parallel. This prevents benchmarks from interfering with each other. + +To further improve stability: + +- Use the [`retry`](#retries) option to automatically rerun flaky benchmark assertions. +- Use the [`delta`](#asserting-performance) option in `toBeFasterThan` / `toBeSlowerThan` to allow for acceptable variance. +- Avoid running benchmarks alongside CPU-intensive processes. +- Close browsers, IDEs, and other applications that compete for CPU time. + +### Dead Code Elimination + +JavaScript engines can optimize away code that has no observable side effects. If your benchmark function doesn't use its result, the engine may skip the computation entirely, producing misleadingly fast numbers: + +```ts +test('parsing', async ({ bench }) => { + // BAD: the engine may eliminate the work + await bench('parse', () => { + JSON.parse(input) + }).run() + + // GOOD: the result is consumed + await bench('parse', () => { + const result = JSON.parse(input) + doSomething(result) + }).run() +}) +``` + +This applies to all engines (V8, JavaScriptCore, SpiderMonkey) but is especially aggressive in V8's TurboFan and JavaScriptCore's FTL compiler tiers. + +### Module Runner Overhead + +By default, Vitest runs tests in Node.js using Vite's module runner (configured by [`experimental.viteModuleRunner`](/config/experimental#experimental-vitemodulerunner)). This transforms all module exports into getters, so every access to an imported binding goes through something like `__vite_ssr_module__.value`. In regular tests this overhead is negligible, but in benchmarks where a function is called millions of times, the getter call itself can dominate the measurement. + +Vitest will print a warning if it detects excessive getter calls (which you can silence via [`benchmark.suppressExportGetterWarnings`](/config/benchmark#benchmark-suppressexportgetterwarnings)), but you should be aware of this when benchmarking imported functions: + +```ts +import { parse } from './parser.js' + +const _parse = parse + +test('parsing', async ({ bench }) => { + // BAD: every call to `parse` goes through a getter + await bench('parse', () => { + parse(input) + }).run() + + // GOOD: store the reference locally to bypass the getter + await bench('parse', () => { + _parse(input) + }).run() +}) +``` + +If you are the library author, the same overhead applies inside the library you are benchmarking: every cross-module call within its source goes through the same getter wrapper. If you are benchmarking your own library, you have two ways to remove this: + +**Benchmark the pre-built artifact.** Import the library through its package name (which resolves to its built output) instead of reaching into its source. The built file has already collapsed internal imports into direct references, so Vite's module runner sees a single module with no internal getters: + +```ts +// BAD: every internal call inside the library goes through a getter +import { parse } from '../src/index.ts' + +// GOOD: the published entry has no internal getters +import { parse } from 'my-library' +``` + +If you compare your library against other packages, benchmark the same kind of artifact for every implementation. For workspace packages, make sure the package name resolves to the built output instead of source, for example by externalizing the package in Vite or by importing from `dist`. + +**Disable the module runner for the benchmark.** If the benchmark does not need Vite transforms, mocks, or Vitest module interception, disable [`experimental.viteModuleRunner`](/config/experimental#experimental-vitemodulerunner) for the benchmark project so Node runs native ESM directly. + +This only affects Node.js mode. Browser mode uses native ESM imports and does not have this overhead. + +### Engine-Specific Considerations + +#### V8 (Node.js, Chrome) + +- **JIT tiering**: V8 compiles functions through multiple optimization tiers (Sparkplug → Maglev → TurboFan). A function may run at different speeds during warmup vs. steady-state. Tinybench handles warmup automatically, but very short benchmark runs may not reach the highest optimization tier. +- **Deoptimization**: V8 can "bail out" of optimized code mid-benchmark if it encounters unexpected types or shapes. Keep the types consistent in your benchmark function: + + ```ts + test('process items', async ({ bench }) => { + // BAD: mixed shapes cause deoptimization + await bench('process', () => { + for (const item of items) { + // some items have { name: string }, others have { name: string, id: number } + process(item) + } + }).run() + + // GOOD: consistent object shapes + await bench('process', () => { + for (const item of items) { + // all items have the same shape { name: string, id: number } + process(item) + } + }).run() + }) + ``` + +- **Garbage collection**: Large allocations inside the benchmark loop add GC noise. If you're measuring computation, pre-allocate data in a `setup` hook rather than inside the benchmarked function: + + ```ts + test('sorting', async ({ bench }) => { + const original = Array.from({ length: 10000 }, () => Math.random()) + let data: number[] + + // BAD: allocates a new array every iteration, GC adds noise + await bench('sort', () => { + const data = Array.from({ length: 10000 }, () => Math.random()) + data.sort() + }).run() + + // GOOD: pre-allocate, copy in beforeEach + await bench( + 'sort', + () => { data.sort() }, + { + beforeEach() { + data = [...original] + }, + }, + ).run() + }) + ``` + +#### JavaScriptCore (Bun, Safari) + +- **Different optimization thresholds**: JSC uses its own JIT tiers (LLInt → Baseline → DFG → FTL) with different inlining and optimization heuristics. A benchmark that is fast on V8 may behave very differently on JSC. +- **Async benchmarks**: Bun's event loop implementation differs from Node.js. If your benchmark involves async operations or timers, results may not be directly comparable across runtimes. + +#### Browser + +- **Timer resolution**: Browsers may reduce `performance.now()` precision (e.g., to 100μs or even 1ms) as a security mechanism. This makes very fast operations difficult to measure accurately, so increase iterations to compensate: + + ```ts + test('fast operations', async ({ bench }) => { + await bench.compare( + bench('fast-op', () => { fastOp() }), + bench('other-op', () => { otherOp() }), + { + // more iterations help overcome low timer resolution + iterations: 1000, + }, + ) + }) + ``` +- **Cross-browser differences**: V8 (Chrome), SpiderMonkey (Firefox), and JSC (Safari) optimize different patterns differently. A benchmark that shows one library winning in Chrome may show the opposite in Firefox. diff --git a/guide/cli-generated.md b/guide/cli-generated.md index 32484b6b..7538e4f2 100644 --- a/guide/cli-generated.md +++ b/guide/cli-generated.md @@ -311,7 +311,7 @@ Track coverage of the `node:child_process` and `node:worker_threads` spawned dur - **CLI:** `--mode ` - **Config:** [mode](/config/mode) -Override Vite mode (default: `test` or `benchmark`) +Override Vite mode (default: `test`) ### isolate diff --git a/guide/migration.md b/guide/migration.md index 2943981a..23e81f9b 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -13,6 +13,36 @@ 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. ::: +### Benchmarking API Rewrite + +The benchmarking API has been rewritten. `bench` is no longer a top-level import from `vitest`; it is a [test-context fixture](/guide/test-context#bench) accessed from inside a regular `test()`. See the [Benchmarking guide](/guide/benchmarking) for the new API. + +Removed, with replacements where applicable: + +- **`bench(name, fn)` at module scope**: destructure `bench` from the test context instead. + +```ts +// v4 +import { bench } from 'vitest' // [!code --] + +bench('sort', () => { // [!code --] + [3, 1, 2].sort() // [!code --] +}) // [!code --] + +// v5 +import { test } from 'vitest' // [!code ++] + +test('sort', async ({ bench }) => { // [!code ++] + await bench('sort', () => { [3, 1, 2].sort() }).run() // [!code ++] +}) // [!code ++] +``` + +- **`bench.skip`, `bench.only`, `bench.todo`** are removed. Use the regular `test.skip`, `test.only`, `test.todo` on the surrounding `test()` instead. +- **`benchmark.reporters` / `benchmark.outputFile`** are removed. Benchmark output is now part of the default reporter and the `json` reporter; configure those at the top level via `test.reporters` instead. +- **`benchmark.compare` config and the `--compare` CLI flag** are removed. Pass [`writeResult`](/guide/benchmarking#storing-and-replaying-results) as a per-bench option to persist a result, and read it back with [`bench.from()`](/guide/benchmarking#bench-from) inside `bench.compare()`. +- **`benchmark.outputJson` config and the `--outputJson` CLI flag** are removed. Use `--reporter=json --outputFile=` to capture benchmark results; the JSON reporter now includes a `benchmarks` field on each test case. +- **`Vitest` instance `mode` property** is now always `'test'`. The previous `'benchmark'` value is no longer used; benchmarks run inside a dedicated project of the same `Vitest` instance. + ### Removed `test.sequential`, `describe.sequential`, and `sequential` Options 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. diff --git a/guide/snippets/benchmark-per-project.ansi b/guide/snippets/benchmark-per-project.ansi new file mode 100644 index 00000000..2eb283f3 --- /dev/null +++ b/guide/snippets/benchmark-per-project.ansi @@ -0,0 +1,7 @@ + Cross-Project Benchmark Comparison + + test/parser.bench.ts > regular parsing > parsing + name   hz  min  max  mean  p75  p99  p995  p999  rme samples + webkit (bench) 6,730,808.15 0.0000 1.0000 0.0001 0.0000 0.0000 0.0000 0.0000 ±6.20% 6731808 fastest + chromium (bench) 3,841,599.95 0.0000 0.2000 0.0003 0.0000 0.0000 0.0000 0.1000 ±1.96% 3851571 + firefox (bench) 3,546,066.28 0.0000 5.0000 0.0003 0.0000 0.0000 0.0000 0.0000 ±6.26% 3547062 slowest diff --git a/guide/snippets/benchmark-table.ansi b/guide/snippets/benchmark-table.ansi new file mode 100644 index 00000000..8afa0770 --- /dev/null +++ b/guide/snippets/benchmark-table.ansi @@ -0,0 +1,9 @@ + ✓  bench  test/basic.bench.ts (1 test) 3446ms + ✓ different libraries 3445ms + name   hz  min  max  mean  p75  p99  p995  p999  rme samples + lib 1 40,678,348.31 0.0000 0.0002 0.0000 0.0000 0.0000 0.0000 0.0000 ±0.95%  53989 fastest + lib 3 39,152,132.23 0.0000 0.0002 0.0000 0.0000 0.0000 0.0000 0.0000 ±0.93%  52080 + lib 2 38,088,138.97 0.0000 0.0066 0.0000 0.0000 0.0000 0.0000 0.0000 ±1.58%  50618 slowest + + Test Files  1 passed (1) + Tests  1 passed (1) diff --git a/guide/test-context.md b/guide/test-context.md index f1d775ee..3bc41c23 100644 --- a/guide/test-context.md +++ b/guide/test-context.md @@ -117,6 +117,29 @@ it('stop request when test times out', async ({ signal }) => { }, 2000) ``` +### `bench` 5.0.0 {#bench} + +The `bench` fixture lets you define and run benchmarks inside regular tests. You can measure throughput, compare implementations, and assert relative performance: + +```ts +import { expect, test } from 'vitest' + +test('compare parsers', async ({ bench }) => { + const result = await bench.compare( + bench('JSON.parse', () => { + JSON.parse('{"key":"value"}') + }), + bench('custom parser', () => { + customParse('{"key":"value"}') + }), + ) + + expect(result.get('JSON.parse')).toBeFasterThan(result.get('custom parser')) +}) +``` + +See the [Benchmarks guide](/guide/benchmarking) for full documentation on comparisons, baselines, and assertion matchers. + ### `onTestFailed` The [`onTestFailed`](/api/hooks#ontestfailed) hook bound to the current test. This API is useful if you are running tests concurrently and need to have a special handling only for this specific test. diff --git a/guide/test-tags.md b/guide/test-tags.md index eea36179..5ec18b0a 100644 --- a/guide/test-tags.md +++ b/guide/test-tags.md @@ -233,7 +233,7 @@ If you are using a programmatic API, you can pass down a `tagsFilter` option to ```ts import { startVitest } from 'vitest/node' -await startVitest('test', [], { +await startVitest([], { tagsFilter: ['frontend and backend'], }) ``` From f5fcf2774def3e83452be59e93eae1519bf5de33 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Sat, 30 May 2026 11:20:14 +0200 Subject: [PATCH 5/9] feat!: `toHaveTextContent` is strict, add `toMatchTextContent` as alternative (#10473) --- api/browser/assertions.md | 45 ++++++++++++++++++++++++++++++--------- guide/migration.md | 15 +++++++++++++ 2 files changed, 50 insertions(+), 10 deletions(-) diff --git a/api/browser/assertions.md b/api/browser/assertions.md index 7c2d905d..72f3535f 100644 --- a/api/browser/assertions.md +++ b/api/browser/assertions.md @@ -34,7 +34,7 @@ test('error banner is rendered', async () => { // It will repeatedly check that the element exists in the DOM and that // the content of `element.textContent` is equal to "Error!" // until all the conditions are met - await expect.element(banner).toHaveTextContent('Error!') + await expect.element(banner).toMatchTextContent('Error!') }) ``` @@ -56,11 +56,11 @@ 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. -`toHaveTextContent` and all other assertions are still available on a regular `expect` without a built-in retry-ability mechanism: +`toMatchTextContent` and all other assertions are still available on a regular `expect` without a built-in retry-ability mechanism: ```ts // will fail immediately if .textContent is not `'Error!'` -expect(banner).toHaveTextContent('Error!') +expect(banner).toMatchTextContent('Error!') ``` ::: @@ -738,12 +738,37 @@ The usual rules of css precedence apply. ```ts function toHaveTextContent( - text: string | RegExp, + text: string | number, options?: { normalizeWhitespace: boolean } ): Promise ``` -This allows you to check whether the given node has a text content or not. This +This matcher allows you to validate that an element's text matches provided string exactly. This +supports elements, but also text nodes and fragments. + +If you wish to perform a partial check or do a case-sensitive match, use [`toMatchTextContent`](#tomatchtextcontent) instead. + +```html +Text Content +``` + +```ts +const element = getByTestId('text-content') + +await expect.element(element).toHaveTextContent('Text Content') +await expect.element(element).not.toHaveTextContent('Content') +``` + +## toMatchTextContent + +```ts +function toMatchTextContent( + text: string | number | RegExp, + options?: { normalizeWhitespace: boolean } +): Promise +``` + +This matcher allows you to check whether the given node has a text content or not. This supports elements, but also text nodes and fragments. When a `string` argument is passed through, it will perform a partial @@ -752,7 +777,7 @@ case-sensitive match to the node content. To perform a case-insensitive match, you can use a `RegExp` with the `/i` modifier. -If you want to match the whole content, you can use a `RegExp` to do it. +If you want to match the whole content, you can use a `RegExp` to do it or [`toHaveTextContent`](#tohavetextcontent) matcher instead. ```html Text Content @@ -761,12 +786,12 @@ If you want to match the whole content, you can use a `RegExp` to do it. ```ts const element = getByTestId('text-content') -await expect.element(element).toHaveTextContent('Content') +await expect.element(element).toMatchTextContent('Content') // to match the whole content -await expect.element(element).toHaveTextContent(/^Text Content$/) +await expect.element(element).toMatchTextContent(/^Text Content$/) // to use case-insensitive match -await expect.element(element).toHaveTextContent(/content$/i) -await expect.element(element).not.toHaveTextContent('content') +await expect.element(element).toMatchTextContent(/content$/i) +await expect.element(element).not.toMatchTextContent('content') ``` ## toHaveValue diff --git a/guide/migration.md b/guide/migration.md index 23e81f9b..4d8ebbe3 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -110,6 +110,21 @@ Several entry points were marked as deprecated in Vitest 4.1. This release remov - `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 +### `toHaveTextContent` Now Performs Strict Equality + +The browser-mode [`toHaveTextContent`](/api/browser/assertions#tohavetextcontent) matcher now validates that an element's text content is exactly equal to the expected string instead of performing a partial, case-sensitive match. Regular expressions are no longer accepted. The previous behaviour, including `RegExp` support, has moved to the new [`toMatchTextContent`](/api/browser/assertions#tomatchtextcontent) matcher. + +```ts +// Partial or regex matches: +await expect.element(banner).toHaveTextContent('Error') // [!code --] +await expect.element(banner).toHaveTextContent(/error/i) // [!code --] +await expect.element(banner).toMatchTextContent('Error') // [!code ++] +await expect.element(banner).toMatchTextContent(/error/i) // [!code ++] + +// Exact matches stay on `toHaveTextContent`: +await expect.element(banner).toHaveTextContent('Error!') +``` + ## Migrating to Vitest 4.0 {#vitest-4} ::: warning Prerequisites From 43546c55676ad74db529b64b8344931032f15e09 Mon Sep 17 00:00:00 2001 From: "Bonaventure C. J. Ugwu" <73999585+BonaventureCJ@users.noreply.github.com> Date: Mon, 1 Jun 2026 11:52:03 +0100 Subject: [PATCH 6/9] docs: fix grammar & sentence fragments in intro (#10487) --- guide/why.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guide/why.md b/guide/why.md index 14c2b7f4..07ca396b 100644 --- a/guide/why.md +++ b/guide/why.md @@ -12,7 +12,7 @@ Vitest is powered by Vite. While you do not need to know Vite to use Vitest, und Vite's out-of-the-box support for common web patterns, features like glob imports and SSR primitives, and its many plugins and integrations are fostering a vibrant ecosystem. Its dev and build story are key to its success. For docs, there are several SSG-based alternatives powered by Vite. Vite's Unit Testing story hasn't been clear though. Existing options like [Jest](https://jestjs.io/) were created in a different context. There is a lot of duplication between Jest and Vite, forcing users to configure two different pipelines. -Using Vite dev server to transform your files during testing, enables the creation of a simple runner that doesn't need to deal with the complexity of transforming source files and can solely focus on providing the best DX during testing. A test runner that uses the same configuration of your App (through `vite.config.js`), sharing a common transformation pipeline during dev, build, and test time. That is extensible with the same plugin API that lets you and the maintainers of your tools provide first-class integration with Vite. A tool that is built with Vite in mind from the start, taking advantage of its improvements in DX, like its instant Hot Module Reload (HMR). This is Vitest, a next generation testing framework powered by Vite. +Using the Vite dev server to transform your files during testing enables the creation of a simple runner that doesn't need to deal with the complexity of transforming source files and can solely focus on providing the best DX during testing. It is a test runner that uses the same configuration as your app (through `vite.config.js`), sharing a common transformation pipeline during dev, build, and test time. It is extensible with the same plugin API that lets you and the maintainers of your tools provide first-class integration with Vite. It is a tool that is built with Vite in mind from the start, taking advantage of its improvements in DX, like its instant Hot Module Replacement (HMR). This is Vitest, a next-generation testing framework powered by Vite. Given Jest's massive adoption, Vitest provides a compatible API that allows you to use it as a drop-in replacement in most projects. It also includes the most common features required when setting up your unit tests (mocking, snapshots, coverage). Vitest cares a lot about performance and uses Worker threads to run as much as possible in parallel. Some ports have seen test running an order of magnitude faster. Watch mode is enabled by default, aligning itself with the way Vite pushes for a dev first experience. Even with all these improvements in DX, Vitest stays lightweight by carefully choosing its dependencies (or directly inlining needed pieces). From 95a42b873eceba8fb4b8c4636c7cb7e121d17102 Mon Sep 17 00:00:00 2001 From: Wouter Kroes Date: Mon, 1 Jun 2026 15:34:44 +0200 Subject: [PATCH 7/9] feat(coverage): `thresholds.autoUpdate` to receive previous threshold as argument (#10495) --- config/coverage.md | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/config/coverage.md b/config/coverage.md index 8f1cfb42..12cb00c1 100644 --- a/config/coverage.md +++ b/config/coverage.md @@ -250,7 +250,7 @@ Check thresholds per file. Update all threshold values `lines`, `functions`, `branches` and `statements` to configuration file when current coverage is better than the configured thresholds. This option helps to maintain thresholds when coverage is improved. -You can also pass a function for formatting the updated threshold values: +You can also pass a function for formatting the updated threshold values. The function receives the new threshold as the first argument and the previous threshold as the second: ```ts @@ -260,6 +260,12 @@ You can also pass a function for formatting the updated threshold values: // Update thresholds without decimals autoUpdate: (newThreshold) => Math.floor(newThreshold), + // Log the change and update without decimals + autoUpdate: (newThreshold, previousThreshold) => { + console.log(`Updated threshold from ${previousThreshold} to ${newThreshold}`) + return Math.floor(newThreshold) + }, + // 95.85 -> 95 functions: 95, } From 936ed6d426553283fcf072f4823241bc6f55052f Mon Sep 17 00:00:00 2001 From: noise Date: Tue, 2 Jun 2026 20:59:50 +0800 Subject: [PATCH 8/9] docs(cn): dissolve the conflict --- .vitepress/config.ts | 6 +- api/advanced/vitest.md | 32 ----- api/browser/assertions.md | 54 ++------- api/browser/commands.md | 19 +-- api/browser/context.md | 6 +- api/test.md | 237 +------------------------------------ config/benchmark.md | 39 +----- config/browser/api.md | 14 +-- config/browser/commands.md | 6 +- config/coverage.md | 8 +- config/index.md | 8 +- config/runner.md | 5 - eslint.config.js | 1 + guide/benchmarking.md | 2 +- guide/cli-generated.md | 6 +- guide/migration.md | 14 +-- guide/test-context.md | 2 +- guide/test-tags.md | 2 +- guide/why.md | 4 - 19 files changed, 29 insertions(+), 436 deletions(-) diff --git a/.vitepress/config.ts b/.vitepress/config.ts index cde0ba3a..61e0fdcf 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -903,15 +903,11 @@ export default ({ mode }: { mode: string }) => { link: '/guide/testing-types', }, { -<<<<<<< HEAD - text: '内联测试', -======= text: 'Benchmarking', link: '/guide/benchmarking', }, { - text: 'In-Source Testing', ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 + text: '内联测试', link: '/guide/in-source', }, ], diff --git a/api/advanced/vitest.md b/api/advanced/vitest.md index 3f495922..4f456a71 100644 --- a/api/advanced/vitest.md +++ b/api/advanced/vitest.md @@ -5,41 +5,9 @@ title: Vitest API # Vitest -<<<<<<< HEAD -Vitest 实例需要当前的测试模式。它可以是以下之一: - -- `test`:运行运行时测试时 -- `benchmark`:运行基准测试时 - -::: details New in Vitest 4 -Vitest 4 新增了多个 API(它们都标记有 "4.0.0+" 徽章),并移除了已弃用的 API: - -- `invalidates` -- `changedTests`(使用 [`onFilterWatchedSpecification`](#onfilterwatchedspecification) 代替) -- `server`(使用 [`vite`](#vite) 代替) -- `getProjectsByTestFile`(使用 [`getModuleSpecifications`](#getmodulespecifications) 代替) -- `getFileWorkspaceSpecs`(使用 [`getModuleSpecifications`](#getmodulespecifications) 代替) -- `getModuleProjects`(自行通过 [`this.projects`](#projects) 过滤) -- `updateLastChanged`(重命名为 [`invalidateFile`](#invalidatefile)) -- `globTestSpecs`(使用 [`globTestSpecifications`](#globtestspecifications) 代替) -- `globTestFiles`(使用 [`globTestSpecifications`](#globtestspecifications) 代替) -- `listFile`(使用 [`getRelevantTestSpecifications`](#getrelevanttestspecifications) 代替) -::: - -## mode - -### test - -测试模式只会调用 `test` 或 `it` 中的函数,并在遇到 `bench` 时抛出错误。此模式使用配置中的 `include` 和 `exclude` 选项来查找测试文件。 - -### benchmark {#benchmark} - -基准测试模式调用 `bench` 函数,并在遇到 `test` 或 `it` 时抛出错误。此模式使用配置中的 `benchmark.include` 和 `benchmark.exclude` 选项来查找基准测试文件。 -======= ## mode {#mode} Since Vitest 5, this property is always `'test'`. ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 ## config diff --git a/api/browser/assertions.md b/api/browser/assertions.md index 31b94fa9..689a80b1 100644 --- a/api/browser/assertions.md +++ b/api/browser/assertions.md @@ -30,19 +30,11 @@ test('error banner is rendered', async () => { name: /error/i, }) -<<<<<<< HEAD // Vitest 提供了带有内置重试能力的 `expect.element`。 // 它会反复检查该元素是否存在于 DOM 中,并且 // `element.textContent` 的内容等于 "Error!" // 直到所有条件都满足为止 - await expect.element(banner).toHaveTextContent('Error!') -======= - // Vitest provides `expect.element` with built-in retry-ability - // It will repeatedly check that the element exists in the DOM and that - // the content of `element.textContent` is equal to "Error!" - // until all the conditions are met await expect.element(banner).toMatchTextContent('Error!') ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 }) ``` @@ -64,19 +56,11 @@ 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. -<<<<<<< HEAD -`toHaveTextContent` 以及其他所有断言在常规的 `expect` 中仍然可用,但没有内置的重试机制: +`toMatchTextContent` 以及其他所有断言在常规的 `expect` 中仍然可用,但没有内置的重试机制: ```ts // 如果 .textContent 不是 `'Error!'`,则会立即失败。 -expect(banner).toHaveTextContent('Error!') -======= -`toMatchTextContent` and all other assertions are still available on a regular `expect` without a built-in retry-ability mechanism: - -```ts -// will fail immediately if .textContent is not `'Error!'` expect(banner).toMatchTextContent('Error!') ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 ``` ::: @@ -716,21 +700,11 @@ function toHaveTextContent( options?: { normalizeWhitespace: boolean } ): Promise ``` - -<<<<<<< HEAD -此功能允许你检查给定节点是否具有文本内容。它支持元素,同时也支持文本节点和片段。 - -当传递一个 `string` 类型的参数时,它会对节点内容进行部分区分大小写的匹配。 - -若要进行不区分大小写的匹配,可以使用带有 `/i` 修饰符的 `RegExp`。 - -如果你想匹配整段内容,可以使用 `RegExp` 来实现。 -======= + This matcher allows you to validate that an element's text matches provided string exactly. This supports elements, but also text nodes and fragments. If you wish to perform a partial check or do a case-sensitive match, use [`toMatchTextContent`](#tomatchtextcontent) instead. ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 ```html Text Content @@ -739,14 +713,6 @@ If you wish to perform a partial check or do a case-sensitive match, use [`toMat ```ts const element = getByTestId('text-content') -<<<<<<< HEAD -await expect.element(element).toHaveTextContent('Content') -// 匹配整段内容 -await expect.element(element).toHaveTextContent(/^Text Content$/) -// 不区分大小写匹配 -await expect.element(element).toHaveTextContent(/content$/i) -await expect.element(element).not.toHaveTextContent('content') -======= await expect.element(element).toHaveTextContent('Text Content') await expect.element(element).not.toHaveTextContent('Content') ``` @@ -760,16 +726,13 @@ function toMatchTextContent( ): Promise ``` -This matcher allows you to check whether the given node has a text content or not. This -supports elements, but also text nodes and fragments. +此功能允许你检查给定节点是否具有文本内容。它支持元素,同时也支持文本节点和片段。 -When a `string` argument is passed through, it will perform a partial -case-sensitive match to the node content. +当传递一个 `string` 类型的参数时,它会对节点内容进行部分区分大小写的匹配。 -To perform a case-insensitive match, you can use a `RegExp` with the `/i` -modifier. +若要进行不区分大小写的匹配,可以使用带有 `/i` 修饰符的 `RegExp`。 -If you want to match the whole content, you can use a `RegExp` to do it or [`toHaveTextContent`](#tohavetextcontent) matcher instead. +如果你想匹配整段内容,可以使用 `RegExp` 或 [`toHaveTextContent`](#tohavetextcontent) 来实现。 ```html Text Content @@ -779,12 +742,11 @@ If you want to match the whole content, you can use a `RegExp` to do it or [`toH const element = getByTestId('text-content') await expect.element(element).toMatchTextContent('Content') -// to match the whole content +// 匹配整段内容 await expect.element(element).toMatchTextContent(/^Text Content$/) -// to use case-insensitive match +// 不区分大小写匹配 await expect.element(element).toMatchTextContent(/content$/i) await expect.element(element).not.toMatchTextContent('content') ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 ``` ## toHaveValue diff --git a/api/browser/commands.md b/api/browser/commands.md index ef763e5e..284e4b04 100644 --- a/api/browser/commands.md +++ b/api/browser/commands.md @@ -14,18 +14,11 @@ outline: deep 在浏览器测试中,可借助 `readFile`、`writeFile` 与 `removeFile` 三个 API 完成文件操作。自 Vitest 3.2 起,所有路径均以 [project](/guide/projects) 根目录为基准解析(根目录默认为 `process.cwd()`,可手动重写);旧版本则以当前测试文件所在目录为基准。 默认情况下,Vitest 使用 `utf-8` 编码,但你可以使用选项覆盖它。 - -::: tip -<<<<<<< HEAD -此 API 遵循 [`server.fs`](https://vitejs.dev/config/server-options.html#server-fs-allow) 出于安全原因的限制。 - -If [`browser.api.allowWrite`](/config/browser/api) or [`api.allowWrite`](/config/api#api-allowwrite) are disabled, `writeFile` and `removeFile` functions won't do anything. -======= +::: tip The built-in file commands follow Vite's [`server.fs`](https://vitejs.dev/config/server-options.html#server-fs-allow) restrictions for security reasons. `writeFile` and `removeFile` also require write access through [`browser.api.allowWrite`](/config/browser/api) and [`api.allowWrite`](/config/api#api-allowwrite). ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 ::: ```ts @@ -65,13 +58,9 @@ expect(input).toHaveValue('a') ``` ::: warning -<<<<<<< HEAD -CDP session仅适用于 `playwright` provider,并且仅在使用 `chromium` 浏览器时有效。有关详细信息,请参阅 playwright 的 [`CDPSession`](https://playwright.dev/docs/api/class-cdpsession)文档。 -======= -CDP session works only with `playwright` provider and only when using `chromium` browser. You can read more about it in playwright's [`CDPSession`](https://playwright.dev/docs/api/class-cdpsession) documentation. +CDP session 仅适用于 `playwright` provider,并且仅在使用 `chromium` 浏览器时有效。有关详细信息,请参阅 playwright 的 [`CDPSession`](https://playwright.dev/docs/api/class-cdpsession) 文档。 CDP is a privileged debugging API. It is available only when browser API write and exec operations are enabled through [`browser.api.allowWrite`](/config/browser/api#api-allowwrite), [`browser.api.allowExec`](/config/browser/api#api-allowexec), [`api.allowWrite`](/config/api#api-allowwrite), and [`api.allowExec`](/config/api#api-allowexec). ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 ::: ## 自定义命令 {#custom-commands} @@ -137,9 +126,6 @@ declare module 'vitest/browser' { ::: warning 如果自定义命令具有相同的名称,则它们将覆盖内置命令。 ::: -<<<<<<< HEAD - -======= ::: warning Security Custom commands run in the Vitest Node process and are callable from browser test code through Vitest's browser RPC connection. They can access local files, environment variables, network services, databases, shell commands, and other Node APIs. @@ -189,7 +175,6 @@ export const myWriteFileCommand: BrowserCommand<[path: string, content: string]> ::: ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 ### Recording trace markers Custom commands can record [trace markers](/api/browser/context#mark) for the test that triggered them through `context.mark`. This is the server-side equivalent of `page.mark` and helps annotate the [trace view](/guide/browser/trace-view) with custom actions performed inside a command. diff --git a/api/browser/context.md b/api/browser/context.md index e53895bb..534508b3 100644 --- a/api/browser/context.md +++ b/api/browser/context.md @@ -205,13 +205,9 @@ At the moment, the `frameLocator` method is only supported by the `playwright` p `cdp` 导出返回当前的 Chrome DevTools 协议会话。它主要用于库作者在其基础上构建工具。 ::: warning -<<<<<<< HEAD CDP 会话仅适用于 `playwright` provider,并且仅在使用 `chromium` 浏览器时有效。有关详细信息,请参阅 playwright 的 [`CDPSession`](https://playwright.dev/docs/api/class-cdpsession)文档。 -======= -CDP session works only with `playwright` provider and only when using `chromium` browser. You can read more about it in playwright's [`CDPSession`](https://playwright.dev/docs/api/class-cdpsession) documentation. - + CDP is a privileged debugging API. It is available only when browser API write and exec operations are enabled through [`browser.api.allowWrite`](/config/browser/api#api-allowwrite), [`browser.api.allowExec`](/config/browser/api#api-allowexec), [`api.allowWrite`](/config/api#api-allowwrite), and [`api.allowExec`](/config/api#api-allowexec). ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 ::: ```ts diff --git a/api/test.md b/api/test.md index f5cc25fd..38fb60c0 100644 --- a/api/test.md +++ b/api/test.md @@ -666,244 +666,9 @@ test.concurrent.for([ 挂载在 test.extend 实例上的 `aroundAll` 钩子,继承 [`test.extend`](#test-extend) 的类型。更多内容请参阅 [aroundAll](/api/hooks#aroundall)。 ## bench {#bench} - -<<<<<<< HEAD -- **类型:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void` - -::: danger -基准测试是实验性功能,不遵循 SemVer。 -::: - -`bench` 用于定义基准测试。在 Vitest 中,基准测试是一个定义了一系列操作的函数,Vitest 会多次运行该函数以展示不同的性能指标。 - - Vitest 底层使用 [`tinybench`](https://github.com/tinylibs/tinybench) 库,所有选项均可作为第三个参数传入。 - -```ts -import { bench } from 'vitest' - -bench('normal sorting', () => { - const x = [1, 5, 4, 2, 3] - x.sort((a, b) => { - return a - b - }) -}, { time: 1000 }) -``` - -```ts -export interface Options { - /** - * 运行基准任务所需时间(毫秒) - * @default 500 - */ - time?: number - - /** - * 如果连时间选项都已完成,任务应运行的次数 - * @default 10 - */ - iterations?: number - - /** - * 函数以毫秒为单位获取当前时间戳 - */ - now?: () => number - - /** - * 用于中止基准测试的中止信号 - */ - signal?: AbortSignal - - /** - * 任务失败时抛出(如果为 true,事件将不起作用) - */ - throws?: boolean - - /** - * 预热时间(毫秒) - * @default 100ms - */ - warmupTime?: number - - /** - * 热身迭代 - * @default 5 - */ - warmupIterations?: number - - /** - * 在每个基准任务(周期)之前运行的设置函数 - */ - setup?: Hook - - /** - * 在每个基准任务(周期)之后运行的拆机函数 - */ - teardown?: Hook -} -``` -测试用例运行后,输出结构信息如下: - -``` - name hz min max mean p75 p99 p995 p999 rme samples -· normal sorting 6,526,368.12 0.0001 0.3638 0.0002 0.0002 0.0002 0.0002 0.0004 ±1.41% 652638 -``` -```ts -export interface TaskResult { - /* - * 运行任务时发生的最后一次错误 - */ - error?: unknown - - /** - * 以毫秒为单位的基准任务运行时间(周期)。 - */ - totalTime: number - - /** - * 样本中的最小值 - */ - min: number - /** - * 样本中的最大值 - */ - max: number - - /** - * 每秒的操作次数 - */ - hz: number - - /** - * 每个操作需要多长时间(毫秒) - */ - period: number - - /** - * 每个任务的任务样本迭代时间(毫秒) - */ - samples: number[] - - /** - * 样本平均数/平均值(总体平均数的估计值) - */ - mean: number - - /** - * 样本方差(总体方差的估计值) - */ - variance: number - - /** - * 样本标准差(总体标准差的估计值) - */ - sd: number - - /** - * 平均值的标准误差(又称样本平均值的抽样分布标准差) - */ - sem: number - - /** - * 自由度 - */ - df: number - - /** - * 样本临界值 - */ - critical: number - - /** - * 误差率 - */ - moe: number - - /** - * 相对误差 - */ - rme: number - - /** - * 中位绝对偏差 - */ - mad: number - - /** - * P50/中位百分位数 - */ - p50: number - - /** - * p75 百分位数 - */ - p75: number - - /** - * p99 百分位数 - */ - p99: number - - /** - * p995 百分位数 - */ - p995: number - - /** - * p999 百分位数 - */ - p999: number -} -``` - -### bench.skip - -- **类型:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void` - -使用 `bench.skip` 跳过某些基准测试的运行。 - -```ts -import { bench } from 'vitest' - -bench.skip('normal sorting', () => { - const x = [1, 5, 4, 2, 3] - x.sort((a, b) => { - return a - b - }) -}) -``` - -### bench.only - -- **类型:** `(name: string | Function, fn: BenchFunction, options?: BenchOptions) => void` - -使用 `bench.only` 在指定套件中只运行某些基准测试。适用于调试场景。 - -```ts -import { bench } from 'vitest' - -bench.only('normal sorting', () => { - const x = [1, 5, 4, 2, 3] - x.sort((a, b) => { - return a - b - }) -}) -``` - -### bench.todo - -- **类型:** `(name: string | Function) => void` - -使用 `bench.todo` 为待实现的基准测试占位。 - -```ts -import { bench } from 'vitest' - -bench.todo('unimplemented test') -``` -======= + ::: warning Updated in Vitest 5 The benchmarking API has been rewritten. `bench` is no longer a top-level import from `vitest`, and the `bench.skip` / `bench.only` / `bench.todo` helpers have been removed. `bench` is now a [test-context fixture](/guide/test-context#bench) accessed from inside a `test()`. See the [Benchmarking guide](/guide/benchmarking) for the new API. ::: ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 diff --git a/config/benchmark.md b/config/benchmark.md index 2a3c7e2d..d40775ad 100644 --- a/config/benchmark.md +++ b/config/benchmark.md @@ -8,7 +8,7 @@ outline: deep - **类型:** `{ include?, exclude?, ... }` 运行 `vitest bench` 时使用的选项。 - + ## benchmark.enabled - **Type:** `boolean` @@ -41,53 +41,16 @@ Enables the benchmark project. When set, Vitest creates a dedicated benchmark pr ## benchmark.retainSamples -<<<<<<< HEAD -- **类型:** `Arrayable` -- **默认值:** `'default'` - -用于定义输出的自定义报告器。它可以包含一个或多个内置报告名称、报告实例和(或)自定义报告的路径。 -======= - **Type:** `boolean` - **Default:** `false` Include the `samples` array of per-iteration timings on every benchmark result. Disabled by default to reduce memory usage; enable when a custom reporter or API consumer needs the raw samples. ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 -<<<<<<< HEAD -已弃用,建议使用 `benchmark.outputJson`。 -======= ## benchmark.suppressExportGetterWarnings ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 - **Type:** `boolean` - **Default:** `false` -<<<<<<< HEAD -- **类型:** `string | undefined` -- **默认值:** `undefined` - -存储基准测试结果的文件路径,可用于稍后的 `--compare` 选项。 - -例如: - -```sh -# 保存主分支的结果 -git checkout main -vitest bench --outputJson main.json - -# 切换到另一个分支并与主分支进行比较 -git checkout feature -vitest bench --compare main.json -``` - -## benchmark.compare {#benchmark-compare} - -- **类型:** `string | undefined` -- **默认值:** `undefined` - -用于与当前运行结果进行比较的先前基准测试结果的文件路径。 -======= Suppress the warning printed when a benchmark accesses module export getters too many times. Vitest tracks getter access during benchmark runs because Vite's module runner wraps every export in a getter, and excessive access can dominate the measurement (see [Module Runner Overhead](/guide/benchmarking#module-runner-overhead)). Enable this when you've intentionally accepted the overhead, or when the warning is noisy for benchmarks where the getter cost is negligible. ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 diff --git a/config/browser/api.md b/config/browser/api.md index 0a5fdba3..99931666 100644 --- a/config/browser/api.md +++ b/config/browser/api.md @@ -15,12 +15,8 @@ outline: deep - **类型:** `boolean` - **默认值:** 未暴露公网时默认为 `true`,否则为 `false` - -<<<<<<< HEAD -Vitest 通过接收来自浏览器的 WebSocket 连接来保存 [测试注解](/guide/test-annotations)、[测试产物](/api/advanced/artifacts) 和 [快照](/guide/snapshot)。这意味着任何能连接到该 API 的人都可在你机器的项目根目录(由 [`fs.allow`](https://cn.vite.dev/config/server-options#server-fs-allow) 配置)内执行任意代码。 -======= -Vitest saves [annotation attachments](/guide/test-annotations), [artifacts](/api/advanced/artifacts) and [snapshots](/guide/snapshot) by receiving a WebSocket connection from the browser. This allows anyone who can connect to the API write any arbitrary code on your machine within the root of your project (configured by [`fs.allow`](https://vite.dev/config/server-options#server-fs-allow)). This option also gates privileged browser APIs that can write files indirectly, such as raw Chrome DevTools Protocol access through [`cdp()`](/api/browser/context#cdp). ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 + +Vitest 通过接收来自浏览器的 WebSocket 连接来保存 [测试注解](/guide/test-annotations)、[测试产物](/api/advanced/artifacts) 和 [快照](/guide/snapshot)。这意味着任何能连接到该 API 的人都可在你机器的项目根目录(由 [`fs.allow`](https://cn.vite.dev/config/server-options#server-fs-allow) 配置)内执行任意代码。 This option also gates privileged browser APIs that can write files indirectly, such as raw Chrome DevTools Protocol access through [`cdp()`](/api/browser/context#cdp). 当浏览器服务器未暴露至互联网(主机为 `localhost`)时,默认值设为 `true` 不会构成安全隐患。若你修改了主机配置,Vitest 将默认将 `allowWrite` 设为 `false` 以防止潜在的恶意写入风险。 @@ -29,8 +25,4 @@ Vitest saves [annotation attachments](/guide/test-annotations), [artifacts](/api - **类型:** `boolean` - **默认值:** 未暴露至公网时默认为 `true`,否则为 `false` -<<<<<<< HEAD -允许通过 [UI 模式](/guide/ui) 运行任意测试文件。此配置仅作用于界面交互元素(及其背后的服务端代码)的可执行权限。如果 UI 模式被禁用,则该配置不生效。更多信息请参阅 [`api.allowExec`](/config/api#api-allowexec)。 -======= -Allows running any test file via the UI. This applies to the interactive elements (and the server code behind them) in the [UI](/guide/ui) that can run the code. This option also gates privileged browser APIs that can execute code indirectly, such as raw Chrome DevTools Protocol access through [`cdp()`](/api/browser/context#cdp). See [`api.allowExec`](/config/api#api-allowexec) for more information. ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 +允许通过 [UI 模式](/guide/ui) 运行任意测试文件。此配置仅作用于界面交互元素(及其背后的服务端代码)的可执行权限。如果 UI 模式被禁用,则该配置不生效。This option also gates privileged browser APIs that can execute code indirectly, such as raw Chrome DevTools Protocol access through [`cdp()`](/api/browser/context#cdp). 更多信息请参阅 [`api.allowExec`](/config/api#api-allowexec)。 diff --git a/config/browser/commands.md b/config/browser/commands.md index 6a101060..a6b30a9b 100644 --- a/config/browser/commands.md +++ b/config/browser/commands.md @@ -8,14 +8,10 @@ outline: deep - **类型:** `Record` - **默认值:** `{ readFile, writeFile, ... }` -<<<<<<< HEAD 可在浏览器测试中通过 `vitest/browser` 导入 [自定义命令](/api/browser/commands)。 -======= -Custom [commands](/api/browser/commands) that can be imported during browser tests from `vitest/browser`. - + ::: warning Security Commands run in the Vitest Node process. If a command exposes filesystem, process, network, database, or shell access based on browser-provided input, validate and restrict that input inside the command. Built-in file commands apply Vite `server.fs` checks and write-access checks, but custom commands are responsible for their own protections. See [Custom Commands security notes](/api/browser/commands#custom-commands). ::: ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 diff --git a/config/coverage.md b/config/coverage.md index eed07489..68ac8fea 100644 --- a/config/coverage.md +++ b/config/coverage.md @@ -249,12 +249,8 @@ npx vitest --coverage.enabled --coverage.provider=istanbul 当实际覆盖率超过配置阈值时,自动将 `lines`、`functions`、`branches` 和 `statements` 的阈值更新到配置文件中。 此选项适用于覆盖率提高时保持阈值不变。 - -<<<<<<< HEAD -你也可以通过传入函数自定义阈值更新值的格式: -======= -You can also pass a function for formatting the updated threshold values. The function receives the new threshold as the first argument and the previous threshold as the second: ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 + +你也可以通过传入函数自定义阈值更新值的格式,The function receives the new threshold as the first argument and the previous threshold as the second: ```ts diff --git a/config/index.md b/config/index.md index b5987f8a..0fa77688 100644 --- a/config/index.md +++ b/config/index.md @@ -6,15 +6,9 @@ outline: deep 如果我们正在使用 Vite 并且拥有一个 `vite.config` 文件,Vitest 会读取它来匹配我们的 Vite 应用的插件和设置。如果我们想要为测试配置不同的设置,或者我们的并不特别依赖于 Vite,我们我们可以选择: -<<<<<<< HEAD - 创建 `vitest.config.ts`,它将具有更高的优先级,并且会**覆盖** `vite.config.ts` 中的配置(Vitest 支持所有传统的 JS 和 TS 文件扩展名,但不支持 `json`) - 这意味着我们在 `vite.config` 中的所有选项将被**忽略**。 - 向 CLI 传递 `--config` 选项,例如 `vitest --config ./path/to/vitest.config.ts`。 -- 使用 `process.env.VITEST` 或在 `defineConfig` 上的 `mode` 属性(如果没有用 `--mode` 覆盖,默认设置为 `test`/`benchmark`)来在 `vite.config.ts` 中有条件地应用不同的配置。请注意,像任何其他环境变量一样,`VITEST` 也会在测试中的 `import.meta.env` 上暴露出来。 -======= -- Create `vitest.config.ts`, which will have the higher priority and will **override** the configuration from `vite.config.ts` (Vitest supports all conventional JS and TS extensions, but doesn't support `json`) - it means all options in your `vite.config` will be **ignored** -- Pass `--config` option to CLI, e.g. `vitest --config ./path/to/vitest.config.ts` -- Use `process.env.VITEST` or `mode` property on `defineConfig` (will be set to `test` if not overridden with `--mode`) to conditionally apply different configuration in `vite.config.ts`. Note that like any other environment variable, `VITEST` is also exposed on `import.meta.env` in your tests ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 +- 使用 `process.env.VITEST` 或在 `defineConfig` 上的 `mode` 属性(如果没有用 `--mode` 覆盖,默认设置为 `test`)也可以在 `vite.config.ts` 中有条件地应用不同的配置。请注意,像任何其他环境变量一样,`VITEST` 也会在测试中的 `import.meta.env` 上暴露出来。 要配置 Vitest 本身,请在我们的 Vite 配置中添加 `test` 属性。如果我们是从 `vite` 本身导入 `defineConfig`,我们还需要在配置文件顶部使用 [三斜杠指令](https://www.typescriptlang.org/docs/handbook/triple-slash-directives.html#-reference-types-) 添加对 Vitest 类型引用。 diff --git a/config/runner.md b/config/runner.md index 5b7acec9..df99bc4f 100644 --- a/config/runner.md +++ b/config/runner.md @@ -5,11 +5,6 @@ outline: deep # runner -<<<<<<< HEAD - **类型**: `VitestRunnerConstructor` -- **默认值**: 运行测试时为 `node`,运行基准测试时为 `benchmark` -======= -- **Type:** `VitestRunnerConstructor` ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 自定义测试运行器的路径。这是一项高级功能,应与自定义库运行器一起使用。你可以在 [文档](/api/advanced/runner) 中阅读更多相关信息。 diff --git a/eslint.config.js b/eslint.config.js index 2ee0d214..d59eba32 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -76,6 +76,7 @@ export default antfu( 'import/no-mutable-exports': 'off', 'no-throw-literal': 'off', 'markdown/no-missing-link-fragments': 'off', + 'import/no-duplicates': 'off', }, }, createSimplePlugin({ diff --git a/guide/benchmarking.md b/guide/benchmarking.md index bfa5df87..8c859f68 100644 --- a/guide/benchmarking.md +++ b/guide/benchmarking.md @@ -3,7 +3,7 @@ title: Benchmarking | Guide --- # Benchmarking - + Vitest lets you write benchmarks alongside your tests using the `bench` fixture from the [test context](/guide/test-context). Benchmarks are powered by [Tinybench](https://github.com/tinylibs/tinybench) and are defined inside regular `test()` calls, giving you access to the full power of Vitest's test runner: retries, lifecycle hooks, filtering, and assertions. ## Defining a Benchmark diff --git a/guide/cli-generated.md b/guide/cli-generated.md index 74ffee5d..c99b18cd 100644 --- a/guide/cli-generated.md +++ b/guide/cli-generated.md @@ -311,11 +311,7 @@ UI 模式和 HTML 报告器中提供的 HTML 覆盖率输出目录。 - **命令行终端:** `--mode ` - **配置:** [mode](/config/mode) -<<<<<<< HEAD -覆盖 Vite 模式 (默认值: `test` 或 `benchmark`) -======= -Override Vite mode (default: `test`) ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 +覆盖 Vite 模式 (默认值: `test`) ### isolate diff --git a/guide/migration.md b/guide/migration.md index dd5b8106..f2283d1c 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -13,9 +13,6 @@ outline: deep Vitest 5.0 目前处于 beta 阶段。本章节跟踪已合并的重大变更,在稳定版发布前可能还会发生变化。 ::: -<<<<<<< HEAD -### 移除 `test.sequential`, `describe.sequential`, 和 `sequential` 选项 {##removed-test-sequential-describe-sequential-and-sequential-options} -======= ### Benchmarking API Rewrite The benchmarking API has been rewritten. `bench` is no longer a top-level import from `vitest`; it is a [test-context fixture](/guide/test-context#bench) accessed from inside a regular `test()`. See the [Benchmarking guide](/guide/benchmarking) for the new API. @@ -46,8 +43,7 @@ test('sort', async ({ bench }) => { // [!code ++] - **`benchmark.outputJson` config and the `--outputJson` CLI flag** are removed. Use `--reporter=json --outputFile=` to capture benchmark results; the JSON reporter now includes a `benchmarks` field on each test case. - **`Vitest` instance `mode` property** is now always `'test'`. The previous `'benchmark'` value is no longer used; benchmarks run inside a dedicated project of the same `Vitest` instance. -### Removed `test.sequential`, `describe.sequential`, and `sequential` Options ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 +### 移除 `test.sequential`, `describe.sequential`, 和 `sequential` 选项 {##removed-test-sequential-describe-sequential-and-sequential-options} Vitest 5.0 移除了已弃用的 `test.sequential`、`describe.sequential` 和 `sequential` 选项。当你需要让某个测试或测试套件不再沿用继承来的并发设置,或退出全局配置的并发时,请使用 `concurrent: false`。 @@ -113,10 +109,7 @@ await locator.click() - `vitest/suite`:改用 `vitest` 中 `TestRunner` 的静态方法(例如,`TestRunner.getCurrentTest()`) - `vitest/mocker` 已移除,请直接使用 `@vitest/mocker` 包(这个包曾意外发布过一次且从未被移除) - `vitest/internal/module-runner` 已移除 - -<<<<<<< HEAD -## 迁移至 Vitest 4.0 {#vitest-4} -======= + ### `toHaveTextContent` Now Performs Strict Equality The browser-mode [`toHaveTextContent`](/api/browser/assertions#tohavetextcontent) matcher now validates that an element's text content is exactly equal to the expected string instead of performing a partial, case-sensitive match. Regular expressions are no longer accepted. The previous behaviour, including `RegExp` support, has moved to the new [`toMatchTextContent`](/api/browser/assertions#tomatchtextcontent) matcher. @@ -132,8 +125,7 @@ await expect.element(banner).toMatchTextContent(/error/i) // [!code ++] await expect.element(banner).toHaveTextContent('Error!') ``` -## Migrating to Vitest 4.0 {#vitest-4} ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 +## 迁移至 Vitest 4.0 {#vitest-4} ::: warning 前提条件 Vitest 4.0 要求 **Vite >= 6.0.0** 和 **Node.js >= 20.0.0**。 diff --git a/guide/test-context.md b/guide/test-context.md index b4526206..1cde36b4 100644 --- a/guide/test-context.md +++ b/guide/test-context.md @@ -116,7 +116,7 @@ it('stop request when test times out', async ({ signal }) => { await fetch('/resource', { signal }) }, 2000) ``` - + ### `bench` 5.0.0 {#bench} The `bench` fixture lets you define and run benchmarks inside regular tests. You can measure throughput, compare implementations, and assert relative performance: diff --git a/guide/test-tags.md b/guide/test-tags.md index e585dd2f..df3e312c 100644 --- a/guide/test-tags.md +++ b/guide/test-tags.md @@ -6,7 +6,7 @@ outline: deep # 测试标签 4.1.0 {#test-tags} 允许你在测试用例上添加 [`标签`](/config/tags),在必要时可以使用标签进行过滤测试,或覆盖测试配置。 - + ## Why tags Tags become useful once a suite has groups of tests that share runner options, like a longer timeout for database queries or retries for integration tests on CI. Repeating those options on every relevant test by hand is brittle, and the categories often don't line up with file paths anyway, so splitting them out by file isn't an option. Flaky tests in particular tend to accumulate wherever the bugs landed, not in a `flaky/` folder. diff --git a/guide/why.md b/guide/why.md index a4907aa0..92115e40 100644 --- a/guide/why.md +++ b/guide/why.md @@ -12,11 +12,7 @@ Vitest 基于 Vite 构建。虽然使用 Vitest 并不要求掌握 Vite 知识 Vite 天然支持常见的 Web 模式,同时支持 glob 导入和 SSR 等功能,而且它拥有许多插件和集成框架,从而慢慢形成一个活跃的生态社区。它的开发和构建模式是其成功的关键。对于文档构建框架,Vite 提供了一些基于 SSG 的替代方案。但是 Vite 的单元测试形式还不是十分清晰,而对于目前一些现有方案,比如 [Jest](https://jestjs.io/zh-Hans/) 而言,它们会在不同的上下文环境中被创建的。并且 Jest 和 Vite 之间有很多重复的部分,让用户不得不创建两个不同的配置文件。 -<<<<<<< HEAD 使用 Vite 开发服务器在测试过程中对文件进行转换,使得创建一个简单的测试运行器变得更加容易。该测试运行器无需处理源文件转换的复杂性,只需专注于提供最佳的开发体验。Vitest 与你的应用使用相同的配置(通过 `vite.config.js` ),在开发、构建和测试过程中共享相同的转换流水线。它还提供了可扩展的插件 API ,让你和你的工具维护者能够与 Vite 进行一流的集成。Vitest 从一开始就考虑了与 Vite 的协同工作,充分利用了 Vite 在开发体验方面的改进,如即时的热模块重载( HMR )。这就是 Vitest ,一个由 Vite 驱动的下一代测试框架。 -======= -Using the Vite dev server to transform your files during testing enables the creation of a simple runner that doesn't need to deal with the complexity of transforming source files and can solely focus on providing the best DX during testing. It is a test runner that uses the same configuration as your app (through `vite.config.js`), sharing a common transformation pipeline during dev, build, and test time. It is extensible with the same plugin API that lets you and the maintainers of your tools provide first-class integration with Vite. It is a tool that is built with Vite in mind from the start, taking advantage of its improvements in DX, like its instant Hot Module Replacement (HMR). This is Vitest, a next-generation testing framework powered by Vite. ->>>>>>> 95a42b873eceba8fb4b8c4636c7cb7e121d17102 由于 Jest 的大规模使用,Vitest 提供了与之兼容的 API,允许大家在大多数项目中将其作为备选使用。同时还包括了单元测试时最常见的功能(模拟,快照以及覆盖率)。Vitest 非常注重性能,尽可能多地使用 Worker 线程进行并发运行。并且在一些端口的测试运行速度提高了一个数量级。监听模式默认启用,与 Vite 推动开发者优先体验的理念保持一致。 即使在开发体验上进行了改进,Vitest 通过仔细挑选其依赖项(或直接内联所需的部分)来保持轻量级。 From f399b3185cf5b103c25b62bc43772109b0ba43fc Mon Sep 17 00:00:00 2001 From: "autofix-ci[bot]" <114827586+autofix-ci[bot]@users.noreply.github.com> Date: Tue, 2 Jun 2026 13:01:19 +0000 Subject: [PATCH 9/9] [autofix.ci] apply automated fixes --- guide/benchmarking.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guide/benchmarking.md b/guide/benchmarking.md index 8c859f68..31a0a104 100644 --- a/guide/benchmarking.md +++ b/guide/benchmarking.md @@ -434,12 +434,12 @@ This only affects Node.js mode. Browser mode uses native ESM imports and does no ```ts test('sorting', async ({ bench }) => { - const original = Array.from({ length: 10000 }, () => Math.random()) + const original = Array.from({ length: 10000 }).fill(Math.random()) let data: number[] // BAD: allocates a new array every iteration, GC adds noise await bench('sort', () => { - const data = Array.from({ length: 10000 }, () => Math.random()) + const data = Array.from({ length: 10000 }).fill(Math.random()) data.sort() }).run()