From 23f950c82e038ec04d37a6db45c6624e2c436ad0 Mon Sep 17 00:00:00 2001 From: NoiseFan Date: Mon, 18 May 2026 19:05:45 +0800 Subject: [PATCH 1/6] docs: use unified `` label (#10372) --- api/advanced/vitest.md | 6 +++--- api/browser/assertions.md | 2 +- guide/browser/aria-snapshots.md | 2 +- guide/features.md | 4 ++-- guide/migration.md | 2 +- guide/snapshot.md | 6 +++--- 6 files changed, 11 insertions(+), 11 deletions(-) diff --git a/api/advanced/vitest.md b/api/advanced/vitest.md index 7ff17b48..37e335f2 100644 --- a/api/advanced/vitest.md +++ b/api/advanced/vitest.md @@ -8,7 +8,7 @@ title: Vitest API Vitest instance requires the current test mode. It can be either: - `test` when running runtime tests -- `benchmark` when running benchmarks experimental +- `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: @@ -31,7 +31,7 @@ Vitest 4 added several new APIs (they are marked with a "4.0.0+" badge) and remo 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 experimental +### benchmark {#benchmark} 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. @@ -47,7 +47,7 @@ This is Vitest config, it doesn't extend _Vite_ config. It only has resolved val This is a global [`ViteDevServer`](https://vite.dev/guide/api-javascript#vitedevserver). -## state experimental +## state {#state} ::: warning Public `state` is an experimental API (except `vitest.state.getReportedEntity`). Breaking changes might not follow SemVer, please pin Vitest's version when using it. diff --git a/api/browser/assertions.md b/api/browser/assertions.md index c1c0cd07..7c2d905d 100644 --- a/api/browser/assertions.md +++ b/api/browser/assertions.md @@ -1068,7 +1068,7 @@ await expect.element(queryByTestId('prev')).not.toHaveSelection() await expect.element(queryByTestId('next')).toHaveSelection('ne') ``` -## toMatchScreenshot experimental {#tomatchscreenshot} +## toMatchScreenshot {#tomatchscreenshot} ```ts function toMatchScreenshot( diff --git a/guide/browser/aria-snapshots.md b/guide/browser/aria-snapshots.md index d7d8ae76..57294418 100644 --- a/guide/browser/aria-snapshots.md +++ b/guide/browser/aria-snapshots.md @@ -3,7 +3,7 @@ title: ARIA Snapshots | Guide outline: deep --- -# ARIA Snapshots experimental 4.1.4 +# ARIA Snapshots 4.1.4 {#aria-snapshots} ARIA snapshots let you test the accessibility structure of your pages. Instead of asserting against raw HTML or visual output, you assert against the accessibility tree — the same structure that screen readers and other assistive technologies use. diff --git a/guide/features.md b/guide/features.md index 31bc5373..3c73a69c 100644 --- a/guide/features.md +++ b/guide/features.md @@ -194,7 +194,7 @@ if (import.meta.vitest) { Learn more at [In-source testing](/guide/in-source). -## Benchmarking Experimental {#benchmarking} +## Benchmarking {#benchmarking} You can run benchmark tests with [`bench`](/api/test#bench) function via [Tinybench](https://github.com/tinylibs/tinybench) to compare performance results. @@ -221,7 +221,7 @@ describe('sort', () => { Benchmark report Benchmark report -## Type Testing Experimental {#type-testing} +## Type Testing {#type-testing} You can [write tests](/guide/testing-types) to catch type regressions. Vitest comes with [`expect-type`](https://github.com/mmkal/expect-type) package to provide you with a similar and easy to understand API. diff --git a/guide/migration.md b/guide/migration.md index 45b19e63..93c10a08 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -712,7 +712,7 @@ export default defineConfig({ Otherwise your snapshots will have a lot of escaped `"` characters. -### Custom Snapshot Matchers experimental 4.1.3 +### Custom Snapshot Matchers 4.1.3 {#custom-snapshot-matcher} Jest imports snapshot composables from `jest-snapshot`. In Vitest, use `Snapshots` from `vitest` instead: diff --git a/guide/snapshot.md b/guide/snapshot.md index e99faccd..9db1f71f 100644 --- a/guide/snapshot.md +++ b/guide/snapshot.md @@ -124,7 +124,7 @@ test('button looks correct', async () => { This captures screenshots and compares them against reference images to detect unintended visual changes. Learn more in the [Visual Regression Testing guide](/guide/browser/visual-regression-testing). -## ARIA Snapshots experimental 4.1.4 +## ARIA Snapshots 4.1.4 {#aria-snapshots} ARIA snapshots capture the accessibility tree of a DOM element and compare it against a stored template. Based on [Playwright's ARIA snapshots](https://playwright.dev/docs/aria-snapshots), they provide a semantic alternative to visual regression testing — asserting structure and meaning rather than pixels. @@ -236,7 +236,7 @@ Pretty foo: Object { } ``` -## Custom Snapshot Matchers experimental 4.1.3 {#custom-snapshot-matchers} +## Custom Snapshot Matchers 4.1.3 {#custom-snapshot-matchers} You can build custom snapshot matchers using the composable functions exposed on `Snapshots` from `vitest`. These let you transform values before snapshotting while preserving full snapshot lifecycle support (creation, update, inline rewriting). @@ -335,7 +335,7 @@ declare module 'vitest' { See [Extending Matchers](/guide/extending-matchers) for more on `expect.extend` and custom matcher conventions. ::: -## Custom Snapshot Domain experimental 4.1.4 {#custom-snapshot-domain} +## Custom Snapshot Domain 4.1.4 {#custom-snapshot-domain} Custom serializers control how values are _rendered_ into snapshot strings, but comparison is still string equality. A **domain snapshot adapter** goes further: it owns the entire comparison pipeline for a custom matcher, including how to capture a value, render it, parse a stored snapshot, and match them semantically. From 899ff0570118bc056d86219c4081b521e6b7a7bc Mon Sep 17 00:00:00 2001 From: Mukunda Rao Katta Date: Mon, 18 May 2026 04:08:25 -0700 Subject: [PATCH 2/6] docs: mention xvfb for headful WebdriverIO Chrome (#10358) --- config/browser/webdriverio.md | 20 +++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/config/browser/webdriverio.md b/config/browser/webdriverio.md index 5b1c8e0b..329010cb 100644 --- a/config/browser/webdriverio.md +++ b/config/browser/webdriverio.md @@ -60,5 +60,23 @@ You can find most available options in the [WebdriverIO documentation](https://w ::: tip Most useful options are located on `capabilities` object. WebdriverIO allows nested capabilities, but Vitest will ignore those options because we rely on a different mechanism to spawn several browsers. -Note that Vitest will ignore `capabilities.browserName` — use [`test.browser.instances.browser`](/config/browser/instances#browser) instead. +Note that Vitest will ignore `capabilities.browserName`; use [`test.browser.instances.browser`](/config/browser/instances#browser) instead. ::: + +## Headful Chrome in CI + +Vitest enables [`browser.headless`](/config/browser/headless) automatically in CI. +If you explicitly set `headless: false` for Chrome on a Linux CI runner, Chrome +still needs a display server. Without one, WebDriverIO or ChromeDriver can fail +with a misleading error such as `session not created: probably user data +directory is already in use`. + +Run the test command through `xvfb-run` when you need headful Chrome in GitHub +Actions or another Linux CI environment: + +```bash +xvfb-run npm test +``` + +Alternatively, keep `browser.headless` enabled in CI and use headful mode only +for local debugging. From ff03f134b02e14068700e97fa973406f861c5f06 Mon Sep 17 00:00:00 2001 From: Ben Scott <227292+BPScott@users.noreply.github.com> Date: Mon, 18 May 2026 05:28:08 -0700 Subject: [PATCH 3/6] fix(deps): update fake-timers to 15.3.2. support `toNotFake` (#10043) Co-authored-by: Hiroshi Ogawa Co-authored-by: Codex --- api/vi.md | 12 +++++++++++- config/faketimers.md | 19 +++++++++++++++---- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/api/vi.md b/api/vi.md index 1a0d99c5..1f040959 100644 --- a/api/vi.md +++ b/api/vi.md @@ -1051,7 +1051,7 @@ vi.useRealTimers() ### vi.useFakeTimers ```ts -function useFakeTimers(config?: FakeTimerInstallOpts): Vitest +function useFakeTimers(config?: FakeTimersConfig): Vitest ``` To enable mocking timers, you need to call this method. It will wrap all further calls to timers (such as `setTimeout`, `setInterval`, `clearTimeout`, `clearInterval`, `setImmediate`, `clearImmediate`, and `Date`) until [`vi.useRealTimers()`](#vi-userealtimers) is called. @@ -1065,6 +1065,16 @@ The implementation is based internally on [`@sinonjs/fake-timers`](https://githu But you can enable it by specifying the option in `toFake` argument: `vi.useFakeTimers({ toFake: ['nextTick', 'queueMicrotask'] })`. ::: +You can use `toFake` to specify which timers to mock, or `toNotFake` to specify which timers to keep native. Note that `toFake` and `toNotFake` cannot be specified together. + +```ts +// only mock setTimeout and clearTimeout +vi.useFakeTimers({ toFake: ['setTimeout', 'clearTimeout'] }) + +// mock all timers except setInterval +vi.useFakeTimers({ toNotFake: ['setInterval'] }) +``` + ### vi.setTimerTickMode 4.1.0 {#vi-settimertickmode} - **Type:** `(mode: 'manual' | 'nextTimerAsync') => Vitest | (mode: 'interval', interval?: number) => Vitest` diff --git a/config/faketimers.md b/config/faketimers.md index 68b43e1c..2336bc3c 100644 --- a/config/faketimers.md +++ b/config/faketimers.md @@ -5,7 +5,7 @@ outline: deep # fakeTimers -- **Type:** `FakeTimerInstallOpts` +- **Type:** `FakeTimerConfig` Options that Vitest will pass down to [`@sinon/fake-timers`](https://npmx.dev/package/@sinonjs/fake-timers) when using [`vi.useFakeTimers()`](/api/vi#vi-usefaketimers). @@ -21,12 +21,23 @@ Installs fake timers with the specified Unix epoch. - **Type:** `('setTimeout' | 'clearTimeout' | 'setImmediate' | 'clearImmediate' | 'setInterval' | 'clearInterval' | 'Date' | 'nextTick' | 'hrtime' | 'requestAnimationFrame' | 'cancelAnimationFrame' | 'requestIdleCallback' | 'cancelIdleCallback' | 'performance' | 'queueMicrotask')[]` - **Default:** everything available globally except `nextTick` and `queueMicrotask` -An array with names of global methods and APIs to fake. - -To only mock `setTimeout()` and `nextTick()`, specify this property as `['setTimeout', 'nextTick']`. +An array with names of global methods and APIs to fake. For example, to only mock `setTimeout()` and `nextTick()`, specify this property as `['setTimeout', 'nextTick']`. Mocking `nextTick` is not supported when running Vitest inside `node:child_process` by using `--pool=forks`. NodeJS uses `process.nextTick` internally in `node:child_process` and hangs when it is mocked. Mocking `nextTick` is supported when running Vitest with `--pool=threads`. +## fakeTimers.toNotFake + +- **Type:** `('setTimeout' | 'clearTimeout' | 'setImmediate' | 'clearImmediate' | 'setInterval' | 'clearInterval' | 'Date' | 'nextTick' | 'hrtime' | 'requestAnimationFrame' | 'cancelAnimationFrame' | 'requestIdleCallback' | 'cancelIdleCallback' | 'performance' | 'queueMicrotask')[]` +- **Default:** `[]` + +An array with names of global methods and APIs to keep native. All other available timers will be mocked. For example, to keep `setInterval()` native and mock all other timers, specify this property as `['setInterval']`. + +Mocking `nextTick` is not supported when running Vitest inside `node:child_process` by using `--pool=forks`. When running with `--pool=forks`, Vitest automatically adds `nextTick` to the `toNotFake` array. + +::: warning +Using both `toFake` and `toNotFake` together is not supported. +::: + ## fakeTimers.loopLimit - **Type:** `number` From 0a829fdaed7099fdcbf92a89bd185746aeec4e85 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 18 May 2026 16:23:37 +0200 Subject: [PATCH 4/6] ci: update node-version to 24 by default (#10379) --- guide/improving-performance.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/guide/improving-performance.md b/guide/improving-performance.md index af0db788..f2af48b7 100644 --- a/guide/improving-performance.md +++ b/guide/improving-performance.md @@ -160,7 +160,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Install pnpm uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 @@ -191,7 +191,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version: 24 - name: Install pnpm uses: pnpm/action-setup@a7487c7e89a18df4991f7f222e4898a00d66ddda # v4.1.0 From 3a513123224c0041b8cda52ce1f47c912ce05789 Mon Sep 17 00:00:00 2001 From: Vladimir Date: Mon, 18 May 2026 16:31:27 +0200 Subject: [PATCH 5/6] docs: update guide sidebar, add more recipes (#10226) --- .vitepress/config.ts | 197 +++++++++++++++++++-------- api/vi.md | 2 +- blog/vitest-3-2.md | 2 +- config/watchtriggerpatterns.md | 2 +- guide/debugging.md | 6 +- guide/migration.md | 2 +- guide/recipes.md | 63 --------- guide/recipes/browser-locators.md | 116 ++++++++++++++++ guide/recipes/cancellable.md | 56 ++++++++ guide/recipes/custom-assertions.md | 55 ++++++++ guide/recipes/db-transaction.md | 80 +++++++++++ guide/recipes/disable-isolation.md | 71 ++++++++++ guide/recipes/explicit-resources.md | 105 ++++++++++++++ guide/recipes/parallel-sequential.md | 89 ++++++++++++ guide/recipes/schema-matching.md | 90 ++++++++++++ guide/recipes/type-narrowing.md | 64 +++++++++ guide/recipes/wait-for.md | 99 ++++++++++++++ guide/recipes/watch-templates.md | 64 +++++++++ guide/test-tags.md | 65 ++++++--- 19 files changed, 1084 insertions(+), 144 deletions(-) delete mode 100644 guide/recipes.md create mode 100644 guide/recipes/browser-locators.md create mode 100644 guide/recipes/cancellable.md create mode 100644 guide/recipes/custom-assertions.md create mode 100644 guide/recipes/db-transaction.md create mode 100644 guide/recipes/disable-isolation.md create mode 100644 guide/recipes/explicit-resources.md create mode 100644 guide/recipes/parallel-sequential.md create mode 100644 guide/recipes/schema-matching.md create mode 100644 guide/recipes/type-narrowing.md create mode 100644 guide/recipes/wait-for.md create mode 100644 guide/recipes/watch-templates.md diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 363f27c4..3e6405af 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -818,30 +818,19 @@ export default ({ mode }: { mode: string }) => { }, ], }, + // Authoring — how to express a test in code: constructing it, + // asserting, mocking dependencies, attaching metadata. The page is + // about *test content*, not the runner. Discriminator: "How do I + // write X in a test?" If yes, it belongs here. Mocking sub-pages + // live nested because they're a multi-page subtopic. { - text: 'Guides', + text: 'Authoring', collapsed: false, items: [ - { - text: 'CLI', - link: '/guide/cli', - }, - { - text: 'Test Filtering', - link: '/guide/filtering', - }, - { - text: 'Test Tags', - link: '/guide/test-tags', - }, { text: 'Test Context', link: '/guide/test-context', }, - { - text: 'Test Environment', - link: '/guide/environment', - }, { text: 'Test Run Lifecycle', link: '/guide/lifecycle', @@ -890,45 +879,85 @@ export default ({ mode }: { mode: string }) => { ], }, { - text: 'Parallelism', - link: '/guide/parallelism', - }, - { - text: 'Test Projects', - link: '/guide/projects', + text: 'Test Tags', + link: '/guide/test-tags', }, { - text: 'Reporters', - link: '/guide/reporters', + text: 'Test Annotations', + link: '/guide/test-annotations', }, { - text: 'Coverage', - link: '/guide/coverage', + text: 'Extending Matchers', + link: '/guide/extending-matchers', }, { text: 'Testing Types', link: '/guide/testing-types', }, - { - text: 'Vitest UI', - link: '/guide/ui', - }, { text: 'In-Source Testing', link: '/guide/in-source', }, + ], + }, + // Workflow — how to invoke, select, and orchestrate test runs + // across files/projects/processes. The page is about the *runner + // and tooling around it*, not what's inside a test. Discriminator: + // "How do I run / filter / parallelize / integrate Vitest?" If a + // page is about the runtime environment of the tests themselves + // (jsdom, node), it still belongs here — that's a workflow choice. + { + text: 'Workflow', + collapsed: false, + items: [ { - text: 'Test Annotations', - link: '/guide/test-annotations', + text: 'CLI', + link: '/guide/cli', }, { - text: 'Extending Matchers', - link: '/guide/extending-matchers', + text: 'Test Filtering', + link: '/guide/filtering', + }, + { + text: 'Test Projects', + link: '/guide/projects', + }, + { + text: 'Test Environment', + link: '/guide/environment', + }, + { + text: 'Parallelism', + link: '/guide/parallelism', + }, + { + text: 'Reporters', + link: '/guide/reporters', + }, + { + text: 'Vitest UI', + link: '/guide/ui', }, { text: 'IDE Integration', link: '/guide/ide', }, + ], + }, + // Quality & Debugging — how to verify the test run is healthy and + // diagnose it when it isn't. Coverage, perf, leak detection, error + // triage, observability. Discriminator: "Is my suite good?" or + // "Why did this fail / leak / slow down?" If a page primarily + // measures or fixes the suite (rather than authoring or running + // it), put it here. + { + text: 'Quality & Debugging', + collapsed: false, + items: [ + { + text: 'Coverage', + link: '/guide/coverage', + }, { text: 'Debugging', link: '/guide/debugging', @@ -937,25 +966,6 @@ export default ({ mode }: { mode: string }) => { text: 'Common Errors', link: '/guide/common-errors', }, - { - text: 'Migration Guide', - link: '/guide/migration', - collapsed: false, - items: [ - { - text: 'Migrating to Vitest 4.0', - link: '/guide/migration#vitest-4', - }, - { - text: 'Migrating from Jest', - link: '/guide/migration#jest', - }, - { - text: 'Migrating from Mocha + Chai + Sinon', - link: '/guide/migration#mocha-chai-sinon', - }, - ], - }, { text: 'Performance', collapsed: false, @@ -976,6 +986,62 @@ export default ({ mode }: { mode: string }) => { }, ], }, + // Recipes — end-to-end patterns that solve a concrete problem by + // combining multiple features. Each entry is titled by the problem + // ("Database Transaction per Test"), not the feature. Add a recipe + // when a single feature page would over-explain, when the value + // comes from composition, or when users would search by intent + // rather than by API name. + { + text: 'Recipes', + collapsed: false, + items: [ + { + text: 'Database Transaction per Test', + link: '/guide/recipes/db-transaction', + }, + { + text: 'Cancelling Long-Running Operations Gracefully', + link: '/guide/recipes/cancellable', + }, + { + text: 'Waiting for Async Conditions', + link: '/guide/recipes/wait-for', + }, + { + text: 'Type Narrowing in Tests', + link: '/guide/recipes/type-narrowing', + }, + { + text: 'Custom Assertion Helpers', + link: '/guide/recipes/custom-assertions', + }, + { + text: 'Watching Non-Imported Files', + link: '/guide/recipes/watch-templates', + }, + { + text: 'Extending Browser Locators', + link: '/guide/recipes/browser-locators', + }, + { + text: 'Schema-Driven Assertions', + link: '/guide/recipes/schema-matching', + }, + { + text: 'Auto-Cleanup with `using`', + link: '/guide/recipes/explicit-resources', + }, + { + text: 'Per-File Isolation Settings', + link: '/guide/recipes/disable-isolation', + }, + { + text: 'Parallel and Sequential Test Files', + link: '/guide/recipes/parallel-sequential', + }, + ], + }, { text: 'Advanced', collapsed: false, @@ -998,12 +1064,31 @@ export default ({ mode }: { mode: string }) => { }, ], }, + // Migration — one-time transitional content: cross-version + // upgrades and porting from other test runners (Jest, Mocha). + // Sits near the bottom because it's not daily-use and would push + // active-use guides further from the user's first scroll. { + text: 'Migration', + link: '/guide/migration', + collapsed: false, items: [ { - text: 'Recipes', - link: '/guide/recipes', + text: 'Migrating to Vitest 4.0', + link: '/guide/migration#vitest-4', }, + { + text: 'Migrating from Jest', + link: '/guide/migration#jest', + }, + { + text: 'Migrating from Mocha + Chai + Sinon', + link: '/guide/migration#mocha-chai-sinon', + }, + ], + }, + { + items: [ { text: 'Comparisons', link: '/guide/comparisons', diff --git a/api/vi.md b/api/vi.md index 1f040959..656a199a 100644 --- a/api/vi.md +++ b/api/vi.md @@ -1346,7 +1346,7 @@ function resetConfig(): void If [`vi.setConfig`](#vi-setconfig) was called before, this will reset config to the original state. -### vi.defineHelper 4.1.0 {#vi-defineHelper} +### vi.defineHelper 4.1.0 {#vi-definehelper} ```ts function defineHelper any>(fn: F): F diff --git a/blog/vitest-3-2.md b/blog/vitest-3-2.md index 690b8f00..38aecaaa 100644 --- a/blog/vitest-3-2.md +++ b/blog/vitest-3-2.md @@ -219,7 +219,7 @@ export default defineConfig({ test: { watchTriggerPatterns: [ { - pattern: /^src\/templates\/(.*)\.(ts|html|txt)$/, + pattern: /src\/templates\/(.*)\.(ts|html|txt)$/, testsToRun: (file, match) => { return `api/tests/mailers/${match[2]}.test.ts` }, diff --git a/config/watchtriggerpatterns.md b/config/watchtriggerpatterns.md index 2041def7..d341e546 100644 --- a/config/watchtriggerpatterns.md +++ b/config/watchtriggerpatterns.md @@ -18,7 +18,7 @@ export default defineConfig({ test: { watchTriggerPatterns: [ { - pattern: /^src\/(mailers|templates)\/(.*)\.(ts|html|txt)$/, + pattern: /src\/(mailers|templates)\/(.*)\.(ts|html|txt)$/, testsToRun: (id, match) => { // relative to the root value return `./api/tests/mailers/${match[2]}.test.ts` diff --git a/guide/debugging.md b/guide/debugging.md index f43b7a5e..39b077ff 100644 --- a/guide/debugging.md +++ b/guide/debugging.md @@ -14,6 +14,8 @@ When debugging tests you might want to use following options: ## VS Code +The [official VS Code](https://vitest.dev/vscode) extension supports debugging tests via "Debug Tests" button. However Vitest also exposes tools to define a custom configuration. + Quick way to debug tests in VS Code is via `JavaScript Debug Terminal`. Open a new `JavaScript Debug Terminal` and run `npm run test` or `vitest` directly. *this works with any code run in Node, so will work with most JS testing frameworks* ![image](https://user-images.githubusercontent.com/5594348/212169143-72bf39ce-f763-48f5-822a-0c8b2e6a8484.png) @@ -44,7 +46,9 @@ Then in the debug tab, ensure 'Debug Current Test File' is selected. You can the ### Browser mode -To debug [Vitest Browser Mode](/guide/browser/index.md), pass `--inspect` or `--inspect-brk` in CLI or define it in your Vitest configuration: +The simplest way to debug browser tests is to use the [official VS Code](https://vitest.dev/vscode) extension. + +However you can also pass `--inspect` or `--inspect-brk` in CLI or define it in your Vitest configuration: ::: code-group ```bash [CLI] diff --git a/guide/migration.md b/guide/migration.md index 93c10a08..5d4e15ea 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -479,7 +479,7 @@ export default defineConfig({ ``` ::: -See [Recipes](/guide/recipes) for more examples. +See [Per-File Isolation Settings](/guide/recipes/disable-isolation) and [Parallel and Sequential Test Files](/guide/recipes/parallel-sequential) for more examples. ### Reporter Updates diff --git a/guide/recipes.md b/guide/recipes.md deleted file mode 100644 index 7ab89617..00000000 --- a/guide/recipes.md +++ /dev/null @@ -1,63 +0,0 @@ ---- -title: Recipes | Guide ---- - -# Recipes - -## Disabling Isolation for Specific Test Files Only - -You can speed up your test run by disabling isolation for specific set of files by specifying `isolate` per `projects` entries: - -```ts [vitest.config.ts] -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - projects: [ - { - test: { - // Non-isolated unit tests - name: 'Unit tests', - isolate: false, - exclude: ['**.integration.test.ts'], - }, - }, - { - test: { - // Isolated integration tests - name: 'Integration tests', - include: ['**.integration.test.ts'], - }, - }, - ], - }, -}) -``` - -## Parallel and Sequential Test Files - -You can split test files into parallel and sequential groups by using `projects` option: - -```ts [vitest.config.ts] -import { defineConfig } from 'vitest/config' - -export default defineConfig({ - test: { - projects: [ - { - test: { - name: 'Parallel', - exclude: ['**.sequential.test.ts'], - }, - }, - { - test: { - name: 'Sequential', - include: ['**.sequential.test.ts'], - fileParallelism: false, - }, - }, - ], - }, -}) -``` diff --git a/guide/recipes/browser-locators.md b/guide/recipes/browser-locators.md new file mode 100644 index 00000000..91e8445c --- /dev/null +++ b/guide/recipes/browser-locators.md @@ -0,0 +1,116 @@ +--- +title: Extending Browser Locators | Recipes +--- + +# Domain Locators + +Built-in [locators](/api/browser/locators) like `getByRole` and `getByText` cover queries that map onto accessibility attributes. They run out when an app has shapes that don't fit ARIA, like a "comment with N replies" or a row in a custom table component. + +The fallback is to use `querySelector`. That works, but the result is a plain query rather than a locator, so you lose auto-retry and strict-mode protection. + +[`locators.extend`](/api/browser/locators#custom-locators) 3.2.0 adds a domain-specific locator without giving up the locator API. The value the method returns is still a locator, so auto-retry, strict-mode protection, and chaining all carry through to your custom methods. The names you give those methods become part of the team's test vocabulary: `page.getByCard({ id: 'product-1' })` reads like the product instead of the DOM, and the same name shows up consistently across the suite. + +## Returning a Playwright string + +The simplest form returns a [Playwright locator string](https://playwright.dev/docs/other-locators). Vitest treats the returned string as a child query of whatever locator the method was called on: when called on `page`, the string runs against the entire page; when called on a parent locator, it runs scoped to that parent's subtree. + +Reach for this form when the new query has no good expression in built-in locators, like a CSS-with-text selector for a widget that doesn't map onto a built-in role, or an XPath for a legacy component you don't control. + +```ts +import { locators } from 'vitest/browser' + +locators.extend({ + getByCommentsCount(count: number) { + return `.comments :text("${count} comments")` + }, +}) +``` + +```ts +import { expect, test } from 'vitest' +import { page } from 'vitest/browser' + +test('article shows comment count', async () => { + await expect.element(page.getByCommentsCount(1)).toBeVisible() + await expect.element( + page.getByRole('article', { name: 'Hello World' }) + .getByCommentsCount(1) + ).toBeVisible() +}) +``` + +## Composing existing locators + +When you return a locator instead of a string, Vitest uses that locator directly. Inside the extension, `this` is bound to the locator the method was called on (or to `page` for top-level calls), so you can chain existing locators or apply `filter` to express relationships between elements that no single built-in option captures. + +The example below uses `filter({ has })` to narrow a row locator to those that contain a button with a given name, encoding a common per-row-actions pattern as a single named lookup: + +```ts +import { locators } from 'vitest/browser' +import type { Locator } from 'vitest/browser' + +locators.extend({ + getRowWithAction(this: Locator, action: string) { + return this.getByRole('row').filter({ + has: this.getByRole('button', { name: action }), + }) + }, +}) +``` + +```ts +await page.getRowWithAction('Delete').first().click() +``` + +Prefer this over the raw-string form when both options can express the query. Built-in locators encode accessibility-aware lookups, and chaining or filtering them preserves those guarantees. Reach for the raw-string form only when no chain of built-ins covers the query, since the string runs whatever selector you wrote and bypasses the locator mechanism you're trying to keep. + +## Custom interactions + +Methods that perform an interaction instead of returning a locator also work. This is the same mechanism used for shaping your own DSL of user actions, defined alongside your queries so the test vocabulary stays consistent. + +`locators.extend` types `this` as `BrowserPage | Locator`, since custom methods are reachable from both. For query helpers that's fine, since `getByRole` and other query methods exist on both. For interaction helpers it isn't: `page` has no `click` or `fill`, so calling `page.clickAndFill('x')` would fail at runtime. Guard against that by comparing `this` against the `page` singleton, which lets TypeScript narrow `this` to `Locator` after the throw: + +```ts +import { locators, page } from 'vitest/browser' +import type { BrowserPage, Locator } from 'vitest/browser' + +locators.extend({ + async clickAndFill(this: BrowserPage | Locator, text: string) { + if (this === page) { + throw new TypeError( + 'clickAndFill must be called on a locator, like page.getByRole(\'textbox\').clickAndFill(...)', + ) + } + await this.click() + await this.fill(text) + }, +}) + +await page.getByRole('textbox').clickAndFill('Hello World') +``` + +Interaction methods don't compose into selectors. `page.getByRole('textbox').clickAndFill('Hello')` works because `getByRole` returns a locator; `page.clickAndFill('Hello')` would hit the guard. Reach for this form for action helpers, not for query helpers. + +## Augmenting locator types + +`locators.extend` is a runtime registration. TypeScript doesn't know about the new methods until you augment the [`LocatorSelectors`](/api/browser/locators) interface, usually in a shared `.d.ts` file: + +```ts +import 'vitest/browser' + +declare module 'vitest/browser' { + interface LocatorSelectors { + getByCommentsCount: (count: number) => Locator + getRowWithAction: (action: string) => Locator + clickAndFill: (text: string) => Promise + } +} +``` + +`LocatorSelectors` is the interface that both `Locator` and `BrowserPage` extend, so any method declared on it shows up on both. That matches what `locators.extend` does at runtime, and it's why interaction helpers like `clickAndFill` need the guard above: TypeScript will let `page.clickAndFill('x')` type-check, but the guard catches the misuse before it hits a missing method. + +## See also + +- [Custom Locators API](/api/browser/locators#custom-locators) +- [Built-in Locators](/api/browser/locators) +- [Playwright "other locators"](https://playwright.dev/docs/other-locators) diff --git a/guide/recipes/cancellable.md b/guide/recipes/cancellable.md new file mode 100644 index 00000000..677ad28d --- /dev/null +++ b/guide/recipes/cancellable.md @@ -0,0 +1,56 @@ +--- +title: Cancellable Test Resources | Recipes +--- + +# Cancellable Test Resources + +A test can hold onto resources that don't stop when the test stops. A `fetch`, a child process, a file stream, a polling loop: none of those notice when Vitest has cancelled the test, and the worker has to sit there waiting for them to finish on their own. Vitest cancels a test when it exceeds its `timeout`, when another test fails under `--bail`, or when someone presses Ctrl+C in the terminal. + +The test context provides a [`signal`](/guide/test-context#signal) 3.2.0 that fires in all of those cases. Pass it to anything that accepts an `AbortSignal` and the resource is released when Vitest cancels. + +## Pattern + +```ts +import { test } from 'vitest' + +test('stop request when test times out', async ({ signal }) => { + await fetch('/heavy-resource', { signal }) +}, 2000) +``` + +If the request hasn't completed within 2 seconds, `fetch` rejects with `AbortError` instead of the test hanging until the operation finishes. + +## Other Web APIs that accept an `AbortSignal` + +- [`fetch`](https://developer.mozilla.org/docs/Web/API/fetch) +- [`addEventListener`](https://developer.mozilla.org/docs/Web/API/EventTarget/addEventListener), where passing `{ signal }` removes the listener on abort +- [`ReadableStream.pipeTo`](https://developer.mozilla.org/docs/Web/API/ReadableStream/pipeTo) +- Node.js APIs like [`fs.readFile`](https://nodejs.org/api/fs.html#fspromisesreadfilepath-options), [`child_process.spawn`](https://nodejs.org/api/child_process.html#child_processspawncommand-args-options), and [`setTimeout` or `setInterval`](https://nodejs.org/api/timers.html), all of which accept `{ signal }` +- Any custom code that calls `signal.throwIfAborted()` or listens for `'abort'` + +## Forwarding the signal + +Wire the test's signal into your own helpers so cancellation propagates all the way down: + +```ts +async function pollUntilReady(url: string, signal: AbortSignal) { + while (!signal.aborted) { + const res = await fetch(url, { signal }) + if (res.ok) { + return + } + await new Promise(r => setTimeout(r, 200)) + } + signal.throwIfAborted() +} + +test('worker becomes ready', async ({ signal }) => { + await pollUntilReady('http://localhost:4000/health', signal) +}, 5000) +``` + +## See also + +- [`signal` in Test Context](/guide/test-context#signal) +- [`bail`](/config/bail) +- [`testTimeout`](/config/testtimeout) diff --git a/guide/recipes/custom-assertions.md b/guide/recipes/custom-assertions.md new file mode 100644 index 00000000..8c04aeba --- /dev/null +++ b/guide/recipes/custom-assertions.md @@ -0,0 +1,55 @@ +--- +title: Custom Assertion Helpers | Recipes +--- + +# Custom Assertion Helpers + +Reusable assertion helpers make tests easier to read, at the cost of stack traces. When an assertion fails inside a helper, the trace points at the line inside the helper rather than the test that called it. With the same helper used across many tests, the stack trace alone doesn't identify which call site failed. + +[`vi.defineHelper`](/api/vi#vi-defineHelper) 4.1.0 wraps a function so Vitest strips its internals from the stack and points the error back at the call site instead. + +## Pattern + +```ts +import { expect, test, vi } from 'vitest' + +const assertPair = vi.defineHelper((a: unknown, b: unknown) => { + expect(a).toEqual(b) // ❌ failure does NOT point here +}) + +test('example', () => { + assertPair('left', 'right') // ✅ failure points here +}) +``` + +When `assertPair` fails, the diff and stack frame surface the test line that called it. That's the same behaviour built-in matchers give you. + +## Composing multiple expectations + +The same wrapper works for helpers that bundle several assertions: + +```ts +import { expect, test, vi } from 'vitest' + +const expectValidUser = vi.defineHelper((user: unknown) => { + expect(user).toHaveProperty('id') + expect(user).toHaveProperty('email') + expect(user.email).toMatch(/@/) +}) + +test('returns a valid user', async () => { + const user = await fetchUser('alice') + expectValidUser(user) +}) +``` + +A failure in any of the inner `expect` calls is reported against the `expectValidUser(user)` line in the test. + +Reach for `defineHelper` whenever a reusable check calls `expect` more than once, whether that's a domain-specific helper like `expectValidJWT` or any block of `expect` calls you'd otherwise inline into every test. + +For asymmetric matchers and custom matchers attached to `expect.extend`, see [Extending Matchers](/guide/extending-matchers). + +## See also + +- [`vi.defineHelper`](/api/vi#vi-defineHelper) +- [Extending Matchers](/guide/extending-matchers) diff --git a/guide/recipes/db-transaction.md b/guide/recipes/db-transaction.md new file mode 100644 index 00000000..c0ae9f20 --- /dev/null +++ b/guide/recipes/db-transaction.md @@ -0,0 +1,80 @@ +--- +title: Database Transaction per Test | Recipes +--- + +# Database Transaction per Test + +Integration tests that touch a real database need to start from a clean state. Truncating tables between every test is slow, so the conventional workaround is to wrap each test in a transaction that's rolled back when it finishes. Nothing ever commits, and there's no per-test cleanup to write. + +Vitest exposes this through [`aroundEach`](/api/hooks#aroundeach) 4.1.0 and a [scoped fixture](/guide/test-context#fixture-scopes) 3.2.0. + +## Pattern + +```ts +import { test as baseTest } from 'vitest' +import { createTestDatabase } from './db.ts' + +export const test = baseTest + .extend('db', { scope: 'file' }, async ({}, { onCleanup }) => { + const db = await createTestDatabase() + onCleanup(() => db.close()) + return db + }) + +test.aroundEach(async (runTest, { db }) => { + await db.transaction(runTest) +}) + +test('insert user', async ({ db }) => { + await db.insert({ name: 'Alice' }) + // rolled back automatically when the test ends +}) +``` + +## How it works + +The `db` fixture is created once per file via `scope: 'file'`, so connection setup happens once instead of on every test, and `onCleanup` closes the connection when the file is done. `aroundEach` wraps every test in `db.transaction(runTest)`, and anything the test writes gets rolled back when `runTest` resolves. The test receives the same `db` instance through its context, with no awareness that it's running inside a transaction. + +This works as long as your database driver supports nested transactions or savepoints, which covers most modern databases. The same `aroundEach` hook can also wrap an [`AsyncLocalStorage`](https://nodejs.org/api/async_context.html#class-asynclocalstorage) context if you want to propagate things like tenant or trace IDs through the test alongside the transaction. + +## One connection per worker + +If the suite has many files, paying for a fresh database connection on every file adds up. Switching the fixture to `scope: 'worker'` and turning off isolation lets multiple files share a single connection per worker process: + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + isolate: false, + }, +}) +``` + +```ts +import { test as baseTest } from 'vitest' +import { createTestDatabase } from './db.ts' + +export const test = baseTest + .extend('db', { scope: 'worker' }, async ({}, { onCleanup }) => { + const db = await createTestDatabase() + onCleanup(() => db.close()) + return db + }) + +test.aroundEach(async (runTest, { db }) => { + await db.transaction(runTest) +}) +``` + +By default, every test file runs in its own worker, so `scope: 'file'` and `scope: 'worker'` behave identically. With `isolate: false`, Vitest reuses workers across files (capped by [`maxWorkers`](/config/maxworkers)), so a worker-scoped fixture is created once per worker instead of once per file. For a suite of 200 files running on 8 workers, that's 8 connections instead of 200. + +Reusing workers isn't a free optimization. With isolation off, files share module instances inside the worker, and tests that mutate top-level state (counters, caches, monkey-patched globals) can leak that state to whichever file runs next in the same worker. The per-test rollback handles data isolation in the database. It can't protect module state in the worker. Read the trade-offs in the [Per-File Isolation Settings](/guide/recipes/disable-isolation) recipe before turning isolation off suite-wide. + +[`vmThreads` and `vmForks`](/config/pool) always run isolated regardless of the `isolate` flag, so worker-scoped fixtures fall back to per-file behavior in those pools. + +## See also + +- [`aroundEach` and `aroundAll`](/api/hooks#aroundeach) +- [Fixture scopes](/guide/test-context#fixture-scopes) +- [Builder pattern](/guide/test-context#builder-pattern) diff --git a/guide/recipes/disable-isolation.md b/guide/recipes/disable-isolation.md new file mode 100644 index 00000000..03ad3997 --- /dev/null +++ b/guide/recipes/disable-isolation.md @@ -0,0 +1,71 @@ +--- +title: Per-File Isolation Settings | Recipes +--- + +# Per-File Isolation Settings + +By default, every test file runs in its own isolated module graph, which protects against one file leaking state into another. That isolation costs setup time on every file, which is fine for integration tests that genuinely need it and wasted on pure unit tests that don't share mutable state. + +Use [`projects`](/guide/projects) to apply [`isolate: false`](/config/isolate) to the unit suite while keeping the integration suite isolated. + +## Pattern + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + projects: [ + { + test: { + // Non-isolated unit tests + name: 'Unit tests', + isolate: false, + exclude: ['**.integration.test.ts'], + }, + }, + { + test: { + // Isolated integration tests + name: 'Integration tests', + include: ['**.integration.test.ts'], + }, + }, + ], + }, +}) +``` + +## When isolation matters + +A test file is safe to deisolate when it does not: + +- mutate module-level state (counters, caches, top-level `let` bindings) +- call [`vi.stubGlobal`](/api/vi#vi-stubglobal) or [`vi.stubEnv`](/api/vi#vi-stubenv) +- monkey-patch prototypes (`Date.prototype`, `Array.prototype`, …) +- register listeners on `process` or other long-lived emitters +- depend on a fresh module instance for `vi.mock` factories + +If any of those apply, isolation is doing real work and should stay on. + +## Verifying it's safe + +Run the suite twice with shuffling to surface inter-file pollution: + +```sh +vitest --shuffle --run --project='Unit tests' +vitest --shuffle --run --project='Unit tests' +``` + +If the second run produces different results, you have order-dependent tests. Either fix the offender or leave isolation enabled for that file. + +## Per-pool isolation + +`isolate` only governs the [`threads`](/config/pool) and [`forks`](/config/pool) pools. The `vmThreads` and `vmForks` pools always run isolated regardless of the flag, since they trade startup cost for stronger guarantees. + +## See also + +- [`isolate`](/config/isolate) +- [Test Projects](/guide/projects) +- [Improving Performance](/guide/improving-performance) +- [Parallel and Sequential Test Files](/guide/recipes/parallel-sequential) diff --git a/guide/recipes/explicit-resources.md b/guide/recipes/explicit-resources.md new file mode 100644 index 00000000..67dc35c1 --- /dev/null +++ b/guide/recipes/explicit-resources.md @@ -0,0 +1,105 @@ +--- +title: Auto-Cleanup with `using` | Recipes +--- + +# Auto-Cleanup with `using` + +Spies and mocks need to be restored after the test that installed them, otherwise state leaks between tests. The usual approaches are an `afterEach(() => vi.restoreAllMocks())` at the suite level or a per-test [`onTestFinished(() => spy.mockRestore())`](/api/hooks#ontestfinished) inline. + +If your runtime supports [Explicit Resource Management](https://github.com/tc39/proposal-explicit-resource-management) (Node.js 24+, or via TypeScript 5.2+ in modern bundlers), there's a tighter option: declare the spy with `using` instead of `const`, and restoration happens automatically when the block exits. + +This works for [`vi.spyOn`](/api/vi#vi-spyon), [`vi.fn`](/api/vi#vi-fn), and [`vi.doMock`](/api/vi#vi-domock). 3.2.0 + +## Pattern + +```ts +import { expect, it, vi } from 'vitest' + +function debug(message: string) { + console.log(message) +} + +it('calls console.log', () => { + using spy = vi.spyOn(console, 'log').mockImplementation(() => {}) + debug('message') + expect(spy).toHaveBeenCalled() +}) + +// console.log is restored here without an afterEach +``` + +The same pattern works with `vi.doMock`, which returns a disposable that queues an unmock when the scope exits: + +```ts +import { expect, it, vi } from 'vitest' + +it('uses the mocked module, then the real one', async () => { + { + using _mock = vi.doMock('./users', () => ({ + loadUser: () => ({ id: '1', name: 'Alice' }), + })) + const { loadUser } = await import('./users') + expect(loadUser('alice').name).toBe('Alice') + } + + // ./users is unmocked from here on +}) +``` + +## Scoped to any block + +`using` is block-scoped, so you can install a spy for just part of a test. This is the case neither `afterEach` nor `onTestFinished` covers, since both run after the test ends: + +```ts +import { expect, it, vi } from 'vitest' + +it('only mocks fetch for the auth call', async () => { + // real fetch here + await preloadConfig() + + { + using fetchSpy = vi.spyOn(globalThis, 'fetch') + .mockResolvedValue(new Response('{"ok":true}')) + + await login('alice', 'secret') + expect(fetchSpy).toHaveBeenCalledOnce() + } + + // real fetch is back + await reportSuccess() +}) +``` + +This is also a way to avoid turning on the global [`restoreMocks: true`](/config/restoremocks) config when only a handful of calls actually need restoration. + +## Compatibility + +`using` requires support for the TC39 Explicit Resource Management proposal: + +- TypeScript ≥ 5.2 (with `target: 'es2022'` or higher and the `disposable` lib included by default). +- Node.js ≥ 24 (or Node 22+ with `--harmony`-style flags) for native runtime support. + +If your environment doesn't support it yet, the closest equivalent for whole-test cleanup is [`onTestFinished`](/api/hooks#ontestfinished), which registers the cleanup inline and runs after the test completes regardless of pass or failure: + +```ts +import { expect, it, onTestFinished, vi } from 'vitest' + +it('calls console.log', () => { + const spy = vi.spyOn(console, 'log').mockImplementation(() => {}) + onTestFinished(() => spy.mockRestore()) + + debug('message') + expect(spy).toHaveBeenCalled() +}) +``` + +`onTestFinished` can't tear down a spy mid-test the way `using` can, so the block-scoped pattern above remains specific to ERM. + +## See also + +- [`vi.spyOn`](/api/vi#vi-spyon) +- [`vi.fn`](/api/vi#vi-fn) +- [`vi.doMock`](/api/vi#vi-domock) +- [`onTestFinished`](/api/hooks#ontestfinished) +- [`restoreMocks`](/config/restoremocks) +- [TC39 Explicit Resource Management proposal](https://github.com/tc39/proposal-explicit-resource-management) diff --git a/guide/recipes/parallel-sequential.md b/guide/recipes/parallel-sequential.md new file mode 100644 index 00000000..f8dd5a47 --- /dev/null +++ b/guide/recipes/parallel-sequential.md @@ -0,0 +1,89 @@ +--- +title: Parallel and Sequential Test Files | Recipes +--- + +# Parallel and Sequential Test Files + +Most test files are independent and run faster in parallel. The exception is the handful that share an exclusive resource, like a fixed port, a writable temp directory, or a database without per-test isolation. Those files flake when other tests run concurrently with them. + +Disabling parallelism globally would slow down every test in the suite. Splitting the suite into two [`projects`](/guide/projects), one parallel and one sequential, lets only the affected files pay the cost. + +## Pattern + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + projects: [ + { + test: { + name: 'Parallel', + exclude: ['**.sequential.test.ts'], + }, + }, + { + test: { + name: 'Sequential', + include: ['**.sequential.test.ts'], + fileParallelism: false, + }, + }, + ], + }, +}) +``` + +[`fileParallelism: false`](/config/fileparallelism) at the project level keeps the rest of your suite running concurrently while the matched files run one at a time. It's a shorthand for [`maxWorkers: 1`](/config/maxworkers); the two settings are equivalent. + +## Run sequential after parallel + +By default, projects run in parallel with each other, so the sequential project's first file may overlap with parallel files that still hold the same resource. Use [`sequence.groupOrder`](/config/sequence#sequence-grouporder) 3.2.0 to force the parallel batch to finish first: + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + projects: [ + { + test: { + name: 'Parallel', + exclude: ['**.sequential.test.ts'], + sequence: { groupOrder: 0 }, + }, + }, + { + test: { + name: 'Sequential', + include: ['**.sequential.test.ts'], + fileParallelism: false, + sequence: { groupOrder: 1 }, + }, + }, + ], + }, +}) +``` + +The parallel batch finishes, *then* the sequential batch starts. Total wall clock stays close to the parallel time plus sum of the sequential test run time. + +## File scope vs. test scope + +There are two different "parallel" knobs in Vitest. Don't confuse them: + +| Scope | Knob | Controls | +| --- | --- | --- | +| Across files | [`fileParallelism`](/config/fileparallelism) | Whether two test *files* run in parallel workers | +| Within a file | `describe.concurrent` / `test.concurrent` | Whether tests *inside one file* run concurrently | + +`fileParallelism: false` doesn't make tests inside a file concurrent; tests inside a file are sequential by default. And `concurrent` on a `describe` or `test` doesn't affect how files are scheduled. + +## See also + +- [`fileParallelism`](/config/fileparallelism) +- [`maxWorkers`](/config/maxworkers) +- [`sequence.groupOrder`](/config/sequence#sequence-grouporder) +- [Parallelism](/guide/parallelism) +- [Test Projects](/guide/projects) +- [Per-File Isolation Settings](/guide/recipes/disable-isolation) diff --git a/guide/recipes/schema-matching.md b/guide/recipes/schema-matching.md new file mode 100644 index 00000000..b22ace77 --- /dev/null +++ b/guide/recipes/schema-matching.md @@ -0,0 +1,90 @@ +--- +title: Schema-Driven Assertions | Recipes +--- + +# Schema-Driven Assertions + +If your project already validates data with [Zod](https://zod.dev), [Valibot](https://valibot.dev), or [ArkType](https://arktype.io), those schemas already describe what a valid value looks like. Reusing them in tests is more direct than duplicating shape checks across `toEqual` and `toMatchObject`. + +[`expect.schemaMatching`](/api/expect#expect-schemamatching) 4.0.0 is an asymmetric matcher that takes any [Standard Schema v1](https://standardschema.dev) object and passes if the value conforms to it. + +## Pattern + +```ts +import { expect, test } from 'vitest' +import { z } from 'zod' + +test('email validation', () => { + const user = { email: 'john@example.com' } + + expect(user).toEqual({ + email: expect.schemaMatching(z.string().email()), + }) +}) +``` + +`expect.schemaMatching` is an asymmetric matcher, so it composes inside any equality check the same way `expect.any` or `expect.stringMatching` do: + +- `toEqual` / `toStrictEqual` +- `toMatchObject` +- `toContainEqual` +- `toThrow` +- `toHaveBeenCalledWith` +- `toHaveReturnedWith` +- `toHaveBeenResolvedWith` + +## Works with any Standard Schema library + +```ts +import { expect, test } from 'vitest' +import { z } from 'zod' +import * as v from 'valibot' +import { type } from 'arktype' + +const user = { email: 'john@example.com' } + +// Zod +expect(user).toEqual({ + email: expect.schemaMatching(z.string().email()), +}) + +// Valibot +expect(user).toEqual({ + email: expect.schemaMatching(v.pipe(v.string(), v.email())), +}) + +// ArkType +expect(user).toEqual({ + email: expect.schemaMatching(type('string.email')), +}) +``` + +## Verifying call arguments + +A common use is asserting that a mock was called with data that conforms to a schema, without spelling out every field: + +```ts +import { expect, test, vi } from 'vitest' +import { z } from 'zod' + +const UserSchema = z.object({ + id: z.string().uuid(), + email: z.string().email(), + createdAt: z.date(), +}) + +test('persists a valid user', () => { + const repo = { save: vi.fn() } + registerUser(repo, { email: 'a@b.com' }) + + expect(repo.save).toHaveBeenCalledWith(expect.schemaMatching(UserSchema)) +}) +``` + +Reach for `schemaMatching` when you already have a schema for the value and would otherwise spell out every property by hand. It's especially useful for assertions over generated fields like UUIDs or timestamps, where you can validate the format without predicting the exact value. + +## See also + +- [`expect.schemaMatching`](/api/expect#expect-schemamatching) +- [Standard Schema](https://standardschema.dev) +- [Asymmetric Matchers](/api/expect) diff --git a/guide/recipes/type-narrowing.md b/guide/recipes/type-narrowing.md new file mode 100644 index 00000000..9fea33ef --- /dev/null +++ b/guide/recipes/type-narrowing.md @@ -0,0 +1,64 @@ +--- +title: Type Narrowing in Tests | Recipes +--- + +# Type Narrowing in Tests + +Tests deal with possibly-null values everywhere. `document.querySelector` returns `Element | null`, `Map.get(key)` returns `T | undefined`, and similar optional shapes show up throughout. The usual workarounds in test code are an unsafe cast with `as`, a non-null assertion with `!` on every access, or a runtime check like `expect(x).toBeTruthy()` that throws when the value is missing. All three add noise, and the runtime check is actively misleading because it doesn't narrow the type the way it looks like it should. + +[`expect.assert`](/api/expect#assert) 4.0.0 throws at runtime and narrows the TypeScript type. The same call replaces all three. + +## Pattern + +```ts +import { expect, test } from 'vitest' + +test('reads stored user', () => { + const cache = new Map() + cache.set('alice', { id: '1', name: 'Alice' }) + + const user = cache.get('alice') // typed as `{ id, name } | undefined` + expect.assert(user) // throws if undefined, narrows below + expect(user.name).toBe('Alice') // no `!`, no `as`, type is `{ id, name }` +}) +``` + +The same shape collapses any "look up a value, check it exists, then use it" sequence: + +```ts +const job = queue.find(j => j.id === 'build-42') // Job | undefined +expect.assert(job) +job.cancel() // narrowed to Job +``` + +## Why `toBeTruthy` doesn't narrow + +`expect(x).toBeTruthy()` and `expect(x).toBeDefined()` throw at runtime when the value is missing, so the test fails the way you want. They don't narrow the type, though, because their TypeScript signature returns `void` rather than the special `asserts` form. + +`expect.assert` is typed as an assertion function, so the same call serves both jobs. + +## Narrowing beyond null + +`expect.assert` accepts any boolean expression and applies the same narrowing TypeScript would do for an `if` branch. That covers `typeof` and `instanceof` checks: + +```ts +expect.assert(typeof input === 'string') +input.toUpperCase() // input is `string` + +expect.assert(error instanceof MyError) +expect(error.code).toBe('E_FOO') // error is `MyError` +``` + +For common shapes there are pre-built helpers from chai's [`assert` API](/api/assert), reachable via the same `expect.assert` namespace: + +```ts +expect.assert.isDefined(maybeUser) // narrows away `undefined` +expect.assert.isString(input) // narrows to string +expect.assert.instanceOf(error, MyError) // narrows to MyError +``` + +## See also + +- [`expect.assert`](/api/expect#assert) +- [Chai `assert` API](/api/assert) +- [Waiting for Async Conditions](/guide/recipes/wait-for) diff --git a/guide/recipes/wait-for.md b/guide/recipes/wait-for.md new file mode 100644 index 00000000..4c5e5291 --- /dev/null +++ b/guide/recipes/wait-for.md @@ -0,0 +1,99 @@ +--- +title: Waiting for Async Conditions | Recipes +--- + +# Waiting for Async Conditions + +Plenty of things in tests don't happen synchronously. A server takes a moment to boot, or a DOM element renders after a microtask. Waiting with `setTimeout` tends to land on either a flaky undershoot or a wasteful long sleep, and a manual polling loop is more code than you want to write per test. + +Vitest provides helpers that poll on your behalf, retrying on a fixed interval until the condition holds or a timeout elapses. + +## `expect.poll`: retry an assertion + +Use [`expect.poll`](/api/expect#poll) when the wait condition is an assertion. The callback returns the value to assert on, the matcher does the comparison, and Vitest retries the whole expression at each interval until the matcher passes. + +```ts +import { expect, test } from 'vitest' +import { createServer } from './server.ts' + +test('server starts', async () => { + const server = createServer() + + await expect.poll(() => server.isReady, { + timeout: 500, + interval: 20 + }).toBe(true) +}) +``` + +The failure message is the standard `expect` diff, with no manual `throw new Error('Server not started')` to maintain. This is the right tool for most "wait for X to become Y" cases. + +`expect.poll` makes every assertion asynchronous, so the call must be awaited. Some matchers don't pair with it: snapshot matchers (which would always succeed under polling), `.resolves` and `.rejects` (the condition is already awaited), and `toThrow` (the value is resolved before the matcher sees it). For any of those, reach for `vi.waitFor` instead. + +## `vi.waitFor`: wait and capture the value + +[`vi.waitFor`](/api/vi#vi-waitfor) is the right tool when the wait condition is the work itself succeeding rather than an assertion you write. It runs the callback at each interval; a thrown error queues another attempt, and the first call that doesn't throw resolves the wait with whatever the callback returned. + +```ts +import { expect, test, vi } from 'vitest' +import { connect, DB_URL } from './db.ts' + +test('database is reachable', async () => { + // `connect` throws ECONNREFUSED until the database accepts connections + const client = await vi.waitFor(() => connect(DB_URL), { + timeout: 5000, + interval: 100, + }) + + const rows = await client.query('SELECT 1 AS ok') + expect(rows[0].ok).toBe(1) +}) +``` + +The throw that drives the retry comes from `connect` itself, not from an `expect` you wrote inside the callback. `expect.poll` doesn't fit this shape because it's built around assertions, and "retry until this call stops throwing and hand me the result" isn't an assertion. Wrapping the call in a `try`/`catch` to fake one would either duplicate the work after the wait or require building the retry loop by hand. + +## `vi.waitUntil`: poll until truthy, fail fast on errors + +Use [`vi.waitUntil`](/api/vi#vi-waituntil) for a value lookup where any thrown error should fail the test on the spot rather than be retried away. Each interval calls the callback again. A truthy return resolves the wait; a falsy return waits for the next interval. A thrown error fails the test immediately. + +```ts +import { expect, test, vi } from 'vitest' +import { jobResults, startJob } from './worker.ts' + +test('worker completes the job', async () => { + startJob('build-42') + + const result = await vi.waitUntil( + () => jobResults.get('build-42'), + { timeout: 5000, interval: 100 }, + ) + + expect(result.status).toBe('ok') + expect(result.steps).toHaveLength(4) +}) +``` + +`jobResults.get('build-42')` returns `JobResult | undefined`. `waitUntil` polls until it returns a truthy value, narrows the resolved type to `JobResult`, and hands it back for further assertions. If the lookup itself throws because of a programming error like a typo in the import, `waitUntil` surfaces the error on the first attempt rather than retrying past it. + +In browser mode, prefer [`page.locator`](/api/browser/locators) and [`expect.element`](/api/browser/assertions) over `waitUntil` for DOM queries: locators retry on their own and produce richer failure messages. + +## Picking between them + +| | `expect.poll` | `vi.waitFor` | `vi.waitUntil` | +| --- | --- | --- | --- | +| Reach for it when | the wait is an assertion | the work might fail until it's ready | a lookup might be falsy and that's fine | +| Retries on thrown error | yes | yes | no, fails fast | +| Resolves with | the assertion | callback's return value | callback's return value | + +Each of these accepts `{ timeout, interval }` options, defaulting to a 1000 ms timeout and 50 ms intervals. `vi.waitFor` and `vi.waitUntil` also accept a number in place of the options object as shorthand for the timeout. + +## Fake timers + +If [`vi.useFakeTimers`](/api/vi#vi-usefaketimers) is active, `vi.waitFor` automatically calls `vi.advanceTimersByTime(interval)` between attempts. That keeps `setTimeout`-based code under test reachable without leaking real time into the test. + +## See also + +- [`expect.poll`](/api/expect#poll) +- [`vi.waitFor`](/api/vi#vi-waitfor) +- [`vi.waitUntil`](/api/vi#vi-waituntil) +- [`vi.useFakeTimers`](/api/vi#vi-usefaketimers) diff --git a/guide/recipes/watch-templates.md b/guide/recipes/watch-templates.md new file mode 100644 index 00000000..49c1abd0 --- /dev/null +++ b/guide/recipes/watch-templates.md @@ -0,0 +1,64 @@ +--- +title: Watching Non-Imported Files | Recipes +--- + +# Watching Non-Imported Files + +In watch mode, Vitest tracks the import graph: when you change a file, every test whose imports reach that file reruns. That covers most cases. It misses tests that depend on files they don't `import`, like email templates loaded with `fs.readFile`, JSON fixtures parsed at runtime, HTML or CSS pulled in by a build step, or generated artifacts the tests assert against. Editing one of those files leaves the related tests stale, and the watch loop has no way to know. + +[`watchTriggerPatterns`](/config/watchtriggerpatterns) 3.2.0 makes these dependencies explicit. You declare a regex over file paths and a callback that returns which tests to rerun when a matching file changes. + +## Pattern + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + watchTriggerPatterns: [ + { + pattern: /src\/templates\/(.*)\.(ts|html|txt)$/, + testsToRun: (file, match) => { + // edit `src/templates/welcome.html` ⇒ rerun `api/tests/mailers/welcome.test.ts` + return `api/tests/mailers/${match[1]}.test.ts` + }, + }, + ], + }, +}) +``` + +`testsToRun` returns one or more test file paths to rerun (as a string or string array), or `undefined` if no tests should rerun. Paths are resolved against the workspace root and are not interpreted as globs. `match` is the result of `RegExp.exec` against the changed file. + +## Variations + +Multiple patterns can coexist. The first below derives the test path from the directory of the changed file; the second maps a single shared fixture to a fixed list of test files: + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + watchTriggerPatterns: [ + { + pattern: /src\/(.*)\/schema\.json$/, + testsToRun: (_file, match) => `src/${match[1]}/__tests__/index.test.ts`, + }, + { + pattern: /test\/shared-fixture\.json$/, + testsToRun: () => [ + 'test/integration/users.test.ts', + 'test/integration/billing.test.ts', + ], + }, + ], + }, +}) +``` + +[`forceRerunTriggers`](/config/forcereruntriggers) covers the same general gap, except it reruns every test on every match. `watchTriggerPatterns` reruns only the tests you map for a given pattern, which keeps the watch loop fast. + +## See also + +- [`watchTriggerPatterns`](/config/watchtriggerpatterns) +- [`forceRerunTriggers`](/config/forcereruntriggers) diff --git a/guide/test-tags.md b/guide/test-tags.md index 106455c7..eea36179 100644 --- a/guide/test-tags.md +++ b/guide/test-tags.md @@ -7,9 +7,28 @@ outline: deep [`Tags`](/config/tags) let you label tests so you can filter what runs and override their options when needed. +## 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. + +A tag captures that kind of category: the definition holds the shared options, and any test marked with the tag inherits them. Those tag names can also be combined into expressions: `--tags-filter='db && !flaky'` runs database tests that aren't marked flaky. [`TestRunner.matchesTags`](#checking-tags-filter-at-runtime) exposes the same expression at runtime, useful when `globalSetup` does expensive work that should be skipped if no tagged tests are scheduled. + +## When to reach for tags + +| If you want to… | Use | +| --- | --- | +| Apply timeout/retry to a *category* of tests | **Tags** | +| Mark cross-cutting categories (`flaky`, `slow`, `frontend`) scattered across many files | **Tags** | +| Conditionally run expensive setup based on what's filtered | **Tags** + [`matchesTags`](#checking-tags-filter-at-runtime) | +| Run a subset by test name match | [`-t` / `testNamePattern`](/config/testnamepattern) | +| Run a subset by file path | `--include` / `--exclude` | +| Run different files with different *runner settings* (isolation, pool, environment) | [Test Projects](/guide/projects) | + +You can combine projects and tags. A test that sits in a `Sequential` project can also carry a `flaky` tag, and Vitest applies both. + ## Defining Tags -Tags must be defined in your configuration file — Vitest does not provide any built-in tags. If a test uses a tag that isn't defined in the config, the test runner will throw an error. This prevents unexpected behavior from mistyped tag names. You can disable this check with the [`strictTags`](/config/stricttags) option. +Tags must be defined in your configuration file. By default, Vitest does not provide any built-in tags. If a test uses a tag that isn't defined in the config, the test runner will throw an error. This prevents unexpected behavior from mistyped tag names. You can disable this check with the [`strictTags`](/config/stricttags) option. You must define a `name` of the tag, and you may define additional options that will be applied to every test marked with the tag, e.g., a `timeout`, or `retry`. For the full list of available options, see [`tags`](/config/tags). @@ -44,24 +63,6 @@ export default defineConfig({ }) ``` -::: warning -If several tags have the same options and are used on the same test, they will be resolved in the order they were specified, or sorted by priority first (the lower the number, the higher the priority). Tags without a defined priority are merged first and will be overridden by higher priority ones: - -```ts -test('flaky database test', { tags: ['flaky', 'db'] }) -// { timeout: 30_000, retry: 3 } -``` - -Note that the `timeout` is 30 seconds (and not 60) because `flaky` tag has a priority of `1` while `db` (that defines 60 second timeout) has no priority. - -If test defines its own options, they will have the highest priority: - -```ts -test('flaky database test', { tags: ['flaky', 'db'], timeout: 120_000 }) -// { timeout: 120_000, retry: 3 } -``` -::: - If you are using TypeScript, you can enforce what tags are available by augmenting the `TestTags` type with a property that contains a union of strings (make sure this file is included by your `tsconfig`): ```ts [vitest.shims.ts] @@ -119,6 +120,24 @@ To print it in JSON, pass down `--list-tags=json`: } ``` +### Resolving option conflicts + +If several tags define the same option and are applied to the same test, they are resolved by `priority` first (lower number wins), then by the order they appear in the test's `tags` array. Tags without a `priority` are merged first and overridden by higher-priority ones: + +```ts +test('flaky database test', { tags: ['flaky', 'db'] }) +// { timeout: 30_000, retry: 3 } +``` + +The `timeout` is 30 seconds (not 60) because `flaky` has priority `1` while `db` has no priority. + +Options defined on the test itself always win: + +```ts +test('flaky database test', { tags: ['flaky', 'db'], timeout: 120_000 }) +// { timeout: 120_000, retry: 3 } +``` + ## Using Tags in Tests You can apply tags to individual tests or entire suites using the `tags` option: @@ -303,7 +322,7 @@ vitest --tags-filter="unit || e2e" --tags-filter="!slow" ### Checking Tags Filter at Runtime -You can use `TestRunner.matchesTags` (since Vitest 4.1.1) to check whether the current tags filter matches a set of tags. This is useful for conditionally running expensive setup logic only when relevant tests are included: +You can use `TestRunner.matchesTags` to check whether the current tags filter matches a set of tags. This is useful for conditionally running expensive setup logic only when relevant tests are included: ```ts import { beforeAll, TestRunner } from 'vitest' @@ -317,3 +336,9 @@ beforeAll(async () => { ``` The method accepts an array of tags and returns `true` if the current `--tags-filter` would include a test with those tags. If no tags filter is active, it always returns `true`. + +## See also + +- [Per-File Isolation Settings](/guide/recipes/disable-isolation) and [Parallel and Sequential Test Files](/guide/recipes/parallel-sequential) use projects to partition tests by file. Reach for projects when categories need different runner settings rather than different timeouts or retries. +- [Test Filtering](/guide/filtering) covers `-t`, `--include`, and the rest of the CLI filters. +- [`tags`](/config/tags) and [`strictTags`](/config/stricttags) configuration reference. From 1f8967abe0aaa18f5438748f3b19e053e0f65e47 Mon Sep 17 00:00:00 2001 From: noise Date: Tue, 19 May 2026 16:05:48 +0800 Subject: [PATCH 6/6] docs(cn): dissolve the conflict --- .vitepress/config.ts | 140 +++++------------------------ api/advanced/vitest.md | 15 +--- api/browser/assertions.md | 4 - api/vi.md | 2 +- config/browser/webdriverio.md | 6 +- config/faketimers.md | 16 +--- guide/browser/aria-snapshots.md | 6 +- guide/browser/playwright-traces.md | 32 +++---- guide/debugging.md | 14 +-- guide/features.md | 12 +-- guide/migration.md | 12 +-- guide/snapshot.md | 18 +--- guide/test-tags.md | 56 ++---------- 13 files changed, 65 insertions(+), 268 deletions(-) diff --git a/.vitepress/config.ts b/.vitepress/config.ts index a0483be3..a7f5075c 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -808,9 +808,9 @@ export default ({ mode }: { mode: string }) => { docFooterText: '追踪查看器 | 浏览器模式', }, { - text: 'Playwright Traces', + text: 'Playwright 追踪', link: '/guide/browser/playwright-traces', - docFooterText: 'Playwright Traces | Browser Mode', + docFooterText: 'Playwright 追踪 | 浏览器模式', }, { text: 'ARIA 快照', @@ -825,43 +825,15 @@ export default ({ mode }: { mode: string }) => { // write X in a test?" If yes, it belongs here. Mocking sub-pages // live nested because they're a multi-page subtopic. { -<<<<<<< HEAD - text: '指南', + text: '编写测试', collapsed: false, items: [ - { - text: '命令行界面', - link: '/guide/cli', - }, - { - text: '测试筛选', - link: '/guide/filtering', - }, - { - text: '测试标签', - link: '/guide/test-tags', - }, { text: '测试上下文', link: '/guide/test-context', }, { - text: '测试环境', - link: '/guide/environment', - }, - { - text: '测试运行生命周期', -======= - text: 'Authoring', - collapsed: false, - items: [ - { - text: 'Test Context', - link: '/guide/test-context', - }, - { - text: 'Test Run Lifecycle', ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 + text: '运行生命周期', link: '/guide/lifecycle', }, { @@ -908,37 +880,8 @@ export default ({ mode }: { mode: string }) => { ], }, { -<<<<<<< HEAD - text: '并行测试', - link: '/guide/parallelism', - }, - { - text: '测试项目', - link: '/guide/projects', - }, - { - text: '报告器', - link: '/guide/reporters', - }, - { - text: '覆盖率', - link: '/guide/coverage', - }, - { - text: '类型测试', - link: '/guide/testing-types', - }, - { - text: 'UI 模式', - link: '/guide/ui', - }, - { - text: '内联测试', - link: '/guide/in-source', -======= - text: 'Test Tags', + text: '测试', link: '/guide/test-tags', ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 }, { text: '测试注释', @@ -949,14 +892,11 @@ export default ({ mode }: { mode: string }) => { link: '/guide/extending-matchers', }, { -<<<<<<< HEAD - text: 'IDE 插件', -======= - text: 'Testing Types', + text: '类型测试', link: '/guide/testing-types', }, { - text: 'In-Source Testing', + text: '内联测试', link: '/guide/in-source', }, ], @@ -968,40 +908,39 @@ export default ({ mode }: { mode: string }) => { // page is about the runtime environment of the tests themselves // (jsdom, node), it still belongs here — that's a workflow choice. { - text: 'Workflow', + text: '工作流', collapsed: false, items: [ { - text: 'CLI', + text: '命令行界面', link: '/guide/cli', }, { - text: 'Test Filtering', + text: '测试筛选', link: '/guide/filtering', }, { - text: 'Test Projects', + text: '测试项目', link: '/guide/projects', }, { - text: 'Test Environment', + text: '测试环境', link: '/guide/environment', }, { - text: 'Parallelism', + text: '并行测试', link: '/guide/parallelism', }, { - text: 'Reporters', + text: '报告器', link: '/guide/reporters', }, { - text: 'Vitest UI', + text: 'UI 模式', link: '/guide/ui', }, { - text: 'IDE Integration', ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 + text: 'IDE 插件', link: '/guide/ide', }, ], @@ -1013,11 +952,11 @@ export default ({ mode }: { mode: string }) => { // measures or fixes the suite (rather than authoring or running // it), put it here. { - text: 'Quality & Debugging', + text: '质量与调试', collapsed: false, items: [ { - text: 'Coverage', + text: '覆盖率', link: '/guide/coverage', }, { @@ -1029,30 +968,7 @@ export default ({ mode }: { mode: string }) => { link: '/guide/common-errors', }, { -<<<<<<< HEAD - text: '迁移指南', - link: '/guide/migration', - collapsed: false, - items: [ - { - text: '迁移到 Vitest 4', - link: '/guide/migration#vitest-4', - }, - { - text: '从 Jest 迁移', - link: '/guide/migration#jest', - }, - { - text: '从 Mocha + Chai + Sinon 迁移', - link: '/guide/migration#mocha-chai-sinon', - }, - ], - }, - { text: '性能', -======= - text: 'Performance', ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 collapsed: false, items: [ { @@ -1078,7 +994,7 @@ export default ({ mode }: { mode: string }) => { // comes from composition, or when users would search by intent // rather than by API name. { - text: 'Recipes', + text: '技巧', collapsed: false, items: [ { @@ -1154,27 +1070,20 @@ export default ({ mode }: { mode: string }) => { // Sits near the bottom because it's not daily-use and would push // active-use guides further from the user's first scroll. { - text: 'Migration', + text: '迁移', link: '/guide/migration', collapsed: false, items: [ { -<<<<<<< HEAD - text: '测试技巧', - link: '/guide/recipes', - }, - { - text: '测试框架比较', -======= - text: 'Migrating to Vitest 4.0', + text: '迁移至 Vitest 4.0', link: '/guide/migration#vitest-4', }, { - text: 'Migrating from Jest', + text: '从 Jest 迁移', link: '/guide/migration#jest', }, { - text: 'Migrating from Mocha + Chai + Sinon', + text: '从 Mocha + Chai + Sinon 迁移', link: '/guide/migration#mocha-chai-sinon', }, ], @@ -1182,8 +1091,7 @@ export default ({ mode }: { mode: string }) => { { items: [ { - text: 'Comparisons', ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 + text: '测试框架比较', link: '/guide/comparisons', }, ], diff --git a/api/advanced/vitest.md b/api/advanced/vitest.md index 68f18f49..bd75a509 100644 --- a/api/advanced/vitest.md +++ b/api/advanced/vitest.md @@ -7,13 +7,8 @@ title: Vitest API Vitest 实例需要当前的测试模式。它可以是以下之一: -<<<<<<< HEAD - `test`:运行运行时测试时 -- `benchmark`:运行基准测试时 实验性 -======= -- `test` when running runtime tests -- `benchmark` when running benchmarks ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 +- `benchmark`:运行基准测试时 ::: details New in Vitest 4 Vitest 4 新增了多个 API(它们都标记有 "4.0.0+" 徽章),并移除了已弃用的 API: @@ -36,11 +31,7 @@ Vitest 4 新增了多个 API(它们都标记有 "4.0.0+" 徽章),并移除 测试模式只会调用 `test` 或 `it` 中的函数,并在遇到 `bench` 时抛出错误。此模式使用配置中的 `include` 和 `exclude` 选项来查找测试文件。 -<<<<<<< HEAD -### benchmark 实验性 {#benchmark-experimental} -======= ### benchmark {#benchmark} ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 基准测试模式调用 `bench` 函数,并在遇到 `test` 或 `it` 时抛出错误。此模式使用配置中的 `benchmark.include` 和 `benchmark.exclude` 选项来查找基准测试文件。 @@ -56,11 +47,7 @@ Vitest 4 新增了多个 API(它们都标记有 "4.0.0+" 徽章),并移除 这是全局的 [`ViteDevServer`](https://vite.dev/guide/api-javascript#vitedevserver)。 -<<<<<<< HEAD -## state 实验性 {#state-experimental} -======= ## state {#state} ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 ::: warning 公共 `state` 是一个实验性 API(除了 `vitest.state.getReportedEntity`)。破坏性更改可能不遵循 SemVer,请在使用时固定 Vitest 的版本。 diff --git a/api/browser/assertions.md b/api/browser/assertions.md index 647cf14f..f31eb6af 100644 --- a/api/browser/assertions.md +++ b/api/browser/assertions.md @@ -1003,11 +1003,7 @@ await expect.element(queryByTestId('prev')).not.toHaveSelection() await expect.element(queryByTestId('next')).toHaveSelection('ne') ``` -<<<<<<< HEAD -## toMatchScreenshot 实验性 {#tomatchscreenshot} -======= ## toMatchScreenshot {#tomatchscreenshot} ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 ```ts function toMatchScreenshot( diff --git a/api/vi.md b/api/vi.md index 86824614..0530efb4 100644 --- a/api/vi.md +++ b/api/vi.md @@ -1068,7 +1068,7 @@ function useFakeTimers(config?: FakeTimersConfig): Vitest `vi.useFakeTimers()` 不再自动模拟 `process.nextTick` 。 仍然可以通过在 `toFake` 参数中指定选项来模拟: `vi.useFakeTimers({ toFake: ['nextTick', 'queueMicrotask'] })` 。 ::: - + You can use `toFake` to specify which timers to mock, or `toNotFake` to specify which timers to keep native. Note that `toFake` and `toNotFake` cannot be specified together. ```ts diff --git a/config/browser/webdriverio.md b/config/browser/webdriverio.md index e8bbd641..7febaaa4 100644 --- a/config/browser/webdriverio.md +++ b/config/browser/webdriverio.md @@ -60,13 +60,9 @@ export default defineConfig({ ::: tip 最有用的选项位于 `capabilities` 对象上。WebdriverIO 允许嵌套功能,但 Vitest 将忽略这些选项,因为我们依赖于不同的机制来生成多个浏览器。 -<<<<<<< HEAD 请注意,Vitest 将忽略 `capabilities.browserName` — 请改用 [`test.browser.instances.browser`](/config/browser/instances#browser)。 -======= -Note that Vitest will ignore `capabilities.browserName`; use [`test.browser.instances.browser`](/config/browser/instances#browser) instead. ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 ::: - + ## Headful Chrome in CI Vitest enables [`browser.headless`](/config/browser/headless) automatically in CI. diff --git a/config/faketimers.md b/config/faketimers.md index 0b167b8c..f92d1400 100644 --- a/config/faketimers.md +++ b/config/faketimers.md @@ -5,11 +5,7 @@ outline: deep # fakeTimers -<<<<<<< HEAD -- **类型:** `FakeTimerInstallOpts` -======= -- **Type:** `FakeTimerConfig` ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 +- **类型:** `FakeTimerConfig` 当使用 [`vi.useFakeTimers()`](/api/vi#vi-usefaketimers) 时,Vitest 会将此选项传递给 [`@sinon/fake-timers`](https://npmx.dev/package/@sinonjs/fake-timers)。 @@ -25,16 +21,10 @@ outline: deep - **类型:** `('setTimeout' | 'clearTimeout' | 'setImmediate' | 'clearImmediate' | 'setInterval' | 'clearInterval' | 'Date' | 'nextTick' | 'hrtime' | 'requestAnimationFrame' | 'cancelAnimationFrame' | 'requestIdleCallback' | 'cancelIdleCallback' | 'performance' | 'queueMicrotask')[]` - **默认值:** 全局可用的所有方法,除了 `nextTick` 和 `queueMicrotask` -<<<<<<< HEAD -需要模拟的全局方法和 API 名称数组。 - -如果仅需模拟 `setTimeout()` 和 `nextTick()`,可将此属性指定为 `['setTimeout', 'nextTick']`。 -======= -An array with names of global methods and APIs to fake. For example, to only mock `setTimeout()` and `nextTick()`, specify this property as `['setTimeout', 'nextTick']`. ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 +需要模拟的全局方法和 API 名称数组。例如仅需模拟 `setTimeout()` 和 `nextTick()`,可将此属性指定为 `['setTimeout', 'nextTick']`。 当使用 `--pool=forks` 在 `node:child_process` 中运行 Vitest 时,不支持模拟 `nextTick`。NodeJS 会在 `node:child_process` 内部使用 `process.nextTick`,模拟后会导致进程挂起。使用 `--pool=threads` 运行 Vitest 时支持模拟 `nextTick`。 - + ## fakeTimers.toNotFake - **Type:** `('setTimeout' | 'clearTimeout' | 'setImmediate' | 'clearImmediate' | 'setInterval' | 'clearInterval' | 'Date' | 'nextTick' | 'hrtime' | 'requestAnimationFrame' | 'cancelAnimationFrame' | 'requestIdleCallback' | 'cancelIdleCallback' | 'performance' | 'queueMicrotask')[]` diff --git a/guide/browser/aria-snapshots.md b/guide/browser/aria-snapshots.md index 02ff2d38..aba1d5a6 100644 --- a/guide/browser/aria-snapshots.md +++ b/guide/browser/aria-snapshots.md @@ -3,11 +3,7 @@ title: ARIA 快照 | 指南 outline: deep --- -<<<<<<< HEAD -# ARIA 快照 4.1.4 {#aria-snapshots} -======= -# ARIA Snapshots 4.1.4 {#aria-snapshots} ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 +# ARIA 快照 4.1.4 {#aria-snapshots} ARIA 快照可以让你测试页面的无障碍结构。你不是去断言原始 HTML 或视觉输出,而是去断言无障碍树,也就是屏幕阅读器和其他辅助技术所使用的那套结构。 diff --git a/guide/browser/playwright-traces.md b/guide/browser/playwright-traces.md index 72135180..1bc1dc83 100644 --- a/guide/browser/playwright-traces.md +++ b/guide/browser/playwright-traces.md @@ -1,9 +1,9 @@ -# Playwright 跟踪 {#playwright-traces} +# Playwright 追踪 {#playwright-traces} -Vitest 浏览器模式支持生成 Playwright 的 [跟踪文件](https://playwright.dev/docs/trace-viewer#viewing-remote-traces)。要启用跟踪,请在 `test.browser` 配置中设置 [`trace`](/config/browser/trace) 选项。 +Vitest 浏览器模式支持生成 Playwright 的 [追踪文件](https://playwright.dev/docs/trace-viewer#viewing-remote-traces)。要启用追踪,请在 `test.browser` 配置中设置 [`trace`](/config/browser/trace) 选项。 ::: warning -生成跟踪文件仅在 [Playwright provider](/config/browser/playwright) 下可用。 +生成追踪文件仅在 [Playwright provider](/config/browser/playwright) 下可用。 ::: ::: code-group @@ -28,7 +28,7 @@ vitest --browser.trace=on ::: -默认情况下,Vitest 会为每个测试生成一个跟踪文件。你也可以将 `trace` 设置为 `'on-first-retry'`、`'on-all-retries'` 或 `'retain-on-failure'`,使其仅在测试失败时生成跟踪。跟踪文件将保存在测试文件旁边的 `__traces__` 文件夹中。跟踪文件的名称包含项目名称、测试名称、[`repeats`](/api/test#repeats) 计数和 [`retry`](/api/test#retry) 计数: +默认情况下,Vitest 会为每个测试生成一个追踪文件。你也可以将 `trace` 设置为 `'on-first-retry'`、`'on-all-retries'` 或 `'retain-on-failure'`,使其仅在测试失败时生成追踪。追踪文件将保存在测试文件旁边的 `__traces__` 文件夹中。追踪文件的名称包含项目名称、测试名称、[`repeats`](/api/test#repeats) 计数和 [`retry`](/api/test#retry) 计数: ``` chromium-my-test-0-0.trace.zip @@ -38,7 +38,7 @@ chromium-my-test-0-0.trace.zip ^ retry count ``` -如果要更改输出目录,你可以在 `test.browser.trace` 配置中设置 `tracesDir` 参数。这样所有跟踪文件将按测试文件分组,存储在同一个目录中,。 +如果要更改输出目录,你可以在 `test.browser.trace` 配置中设置 `tracesDir` 参数。这样所有追踪文件将按测试文件分组,存储在同一个目录中,。 ```ts [vitest.config.js] import { defineConfig } from 'vitest/config' @@ -58,13 +58,13 @@ export default defineConfig({ }) ``` -跟踪文件在报告器中以 [注释](/guide/test-annotations) 形式提供。例如,在 HTML 报告器中,你可以在测试详情中找到指向跟踪文件的链接。 +追踪文件在报告器中以 [注释](/guide/test-annotations) 形式提供。例如,在 HTML 报告器中,你可以在测试详情中找到指向追踪文件的链接。 -## 跟踪标记 {#trace-markers} +## 追踪标记 {#trace-markers} 你也可以使用 `page.mark(name, callback)` 将多个操作分组在一个标记下: -你可以添加显式的命名标记,让跟踪时间线更易于阅读: +你可以添加显式的命名标记,让追踪时间线更易于阅读: ```ts import { page } from 'vitest/browser' @@ -88,7 +88,7 @@ await page.mark('sign in flow', async () => { }) ``` -你还可以使用 [`vi.defineHelper()`](/api/vi#vi-defineHelper) 包装可复用的工具函数,以便跟踪条目会指向工具函数的调用位置,而不是具体内部实现: +你还可以使用 [`vi.defineHelper()`](/api/vi#vi-defineHelper) 包装可复用的工具函数,以便追踪条目会指向工具函数的调用位置,而不是具体内部实现: ```ts import { vi } from 'vitest' @@ -106,18 +106,18 @@ test('renders content', async () => { ## 预览 {#preview} -打开跟踪文件,你可以使用 Playwright Trace Viewer。在终端中运行以下命令: +打开追踪文件,你可以使用 Playwright Trace Viewer。在终端中运行以下命令: ```bash npx playwright show-trace "path-to-trace-file" ``` -这将启动 Trace Viewer 并加载指定的跟踪文件。 +这将启动 Trace Viewer 并加载指定的追踪文件。 -或者,你可以在浏览器中打开 https://trace.playwright.dev 并在此处上传跟踪文件。 +或者,你可以在浏览器中打开 https://trace.playwright.dev 并在此处上传追踪文件。 -Trace Viewer 显示跟踪时间线和渲染的组件 -Trace Viewer 显示跟踪时间线和渲染的组件 +Trace Viewer 显示追踪时间线和渲染的组件 +Trace Viewer 显示追踪时间线和渲染的组件 ## 源码位置 {#source-location} @@ -126,6 +126,6 @@ npx playwright show-trace "path-to-trace-file" - `expect.element(...)` 断言 - 交互式操作,如 `click`、`fill`、`type`、`hover`、`selectOptions`、`upload`、`dragAndDrop`、`tab`、`keyboard`、`wheel` 和截图 -在底层,Playwright 仍然像往常一样记录其自己的底层操作事件。Vitest 用源码位置组包装它们,这样你可以直接从跟踪时间线跳转到测试中的相关行。 +在底层,Playwright 仍然像往常一样记录其自己的底层操作事件。Vitest 用源码位置组包装它们,这样你可以直接从追踪时间线跳转到测试中的相关行。 -对于未自动覆盖的内容,你可以使用 `page.mark()` 或 `locator.mark()` 添加自己的跟踪组,详情请参阅上文 [跟踪标记](#trace-markers)。 +对于未自动覆盖的内容,你可以使用 `page.mark()` 或 `locator.mark()` 添加自己的追踪组,详情请参阅上文 [追踪标记](#trace-markers)。 diff --git a/guide/debugging.md b/guide/debugging.md index b46e2fd6..5c46aef3 100644 --- a/guide/debugging.md +++ b/guide/debugging.md @@ -27,14 +27,10 @@ ndb npm run test ::: ## VS Code - -<<<<<<< HEAD -在 VSCode 中快速调试测试的方法是通过 `JavaScript Debug Terminal` 。打开一个新的 `JavaScript Debug Terminal` 并直接运行 `npm run test` 或 `vitest` 。*这适用于在 Node 中运行的任何代码,因此将适用于大多数 JS 测试框架*。 -======= + The [official VS Code](https://vitest.dev/vscode) extension supports debugging tests via "Debug Tests" button. However Vitest also exposes tools to define a custom configuration. -Quick way to debug tests in VS Code is via `JavaScript Debug Terminal`. Open a new `JavaScript Debug Terminal` and run `npm run test` or `vitest` directly. *this works with any code run in Node, so will work with most JS testing frameworks* ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 +在 VSCode 中快速调试测试的方法是通过 `JavaScript Debug Terminal` 。打开一个新的 `JavaScript Debug Terminal` 并直接运行 `npm run test` 或 `vitest` 。*这适用于在 Node 中运行的任何代码,因此将适用于大多数 JS 测试框架*。 ![image](https://user-images.githubusercontent.com/5594348/212169143-72bf39ce-f763-48f5-822a-0c8b2e6a8484.png) @@ -63,14 +59,10 @@ Quick way to debug tests in VS Code is via `JavaScript Debug Terminal`. Open a n 然后在调试选项卡中确保选择 'Debug Current Test File',然后你可以打开要调试的测试文件并按 F5 开始调试。 ### 浏览器模式 {#browser-mode} - -<<<<<<< HEAD -要调试 [Vitest 浏览器模式](/guide/browser/),请在 CLI 中传递 `--inspect` 或 `--inspect-brk`,或在 Vitest 配置中定义它: -======= + The simplest way to debug browser tests is to use the [official VS Code](https://vitest.dev/vscode) extension. However you can also pass `--inspect` or `--inspect-brk` in CLI or define it in your Vitest configuration: ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 ::: code-group ```bash [CLI] diff --git a/guide/features.md b/guide/features.md index f9e543ca..136affe0 100644 --- a/guide/features.md +++ b/guide/features.md @@ -220,11 +220,7 @@ if (import.meta.vitest) { 了解更多信息 [源码内联测试](/guide/in-source) -<<<<<<< HEAD -## 基准测试 实验性 {#benchmarking} -======= -## Benchmarking {#benchmarking} ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 +## 基准测试 {#benchmarking} 你可以使用 [`bench`](/api/test#bench) 运行基准测试通过 [Tinybench](https://github.com/tinylibs/tinybench) 函数来比较基准测试结果。 @@ -251,11 +247,7 @@ describe('sort', () => { Benchmark report Benchmark report -<<<<<<< HEAD -## 类型测试 实验性 {#type-testing} -======= -## Type Testing {#type-testing} ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 +## 类型测试 {#type-testing} 你可以 [编写测试](/guide/testing-types) 来捕获类型回归。 Vitest 附带 [`expect-type`](https://github.com/mmkal/expect-type) 包,为你提供类似且易于理解的 API。 diff --git a/guide/migration.md b/guide/migration.md index b8b257b2..a07ce24d 100644 --- a/guide/migration.md +++ b/guide/migration.md @@ -483,12 +483,8 @@ export default defineConfig({ ``` ::: - -<<<<<<< HEAD -更多示例请参阅 [测试技巧](/guide/recipes)。 -======= + See [Per-File Isolation Settings](/guide/recipes/disable-isolation) and [Parallel and Sequential Test Files](/guide/recipes/parallel-sequential) for more examples. ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 ### 报告器升级 {#reporter-updates} @@ -719,11 +715,7 @@ export default defineConfig({ 否则快照中会出现大量转义的 `"` 字符。 -<<<<<<< HEAD -### 自定义快照匹配器 4.1.3 {#custom-snapshot-matchers} -======= -### Custom Snapshot Matchers 4.1.3 {#custom-snapshot-matcher} ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 +### 自定义快照匹配器 4.1.3 {#custom-snapshot-matcher} Jest 从 `jest-snapshot` 导入快照组合函数。在 Vitest 中,请改用 `vitest` 中的 `Snapshots`: diff --git a/guide/snapshot.md b/guide/snapshot.md index a5f06de6..8dfef293 100644 --- a/guide/snapshot.md +++ b/guide/snapshot.md @@ -124,11 +124,7 @@ test('button looks correct', async () => { 它会捕获屏幕截图并与参考图像进行比较,以检测意外的视觉变化。在 [视觉回归测试指南](/guide/browser/visual-regression-testing)中了解更多内容。 -<<<<<<< HEAD -## ARIA 快照 实验性 4.1.4 {#aria-snapshots} -======= -## ARIA Snapshots 4.1.4 {#aria-snapshots} ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 +## ARIA 快照 4.1.4 {#aria-snapshots} ARIA 快照会捕获 DOM 元素的无障碍访问树,并与存储的模板进行比对。基于 [Playwright 的 ARIA 快照](https://playwright.dev/docs/aria-snapshots) 实现,它提供了视觉回归测试之外的语义化替代方案,断言结构和含义而非像素。 @@ -234,11 +230,7 @@ Pretty foo: Object { } ``` -<<<<<<< HEAD -## 自定义快照匹配器 实验性 4.1.3 {#custom-snapshot-matchers} -======= -## Custom Snapshot Matchers 4.1.3 {#custom-snapshot-matchers} ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 +## 自定义快照匹配器 4.1.3 {#custom-snapshot-matchers} 可通过 `vitest` 提供的 `Snapshots` 组合式函数构建自定义快照匹配器。这些函数允许你在生成快照前对值进行转换,同时完整保留快照生命周期支持(创建、更新、内联重写)。 @@ -337,11 +329,7 @@ declare module 'vitest' { 更多关于 `expect.extend` 和自定义匹配器约定的内容,请参阅 [扩展匹配器](/guide/extending-matchers)。 ::: -<<<<<<< HEAD -## 自定义领域快照 experimental 4.1.4 {#custom-snapshot-domain} -======= -## Custom Snapshot Domain 4.1.4 {#custom-snapshot-domain} ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 +## 自定义领域快照 4.1.4 {#custom-snapshot-domain} 自定义序列化器控制值如何被 **渲染** 成快照字符串,但比较过程仍然基于字符串相等。**领域快照适配器** 则更进一步:它拥有自定义匹配器的整个比较流水线,包括如何捕获值、渲染值、解析存储的快照,以及如何对它们进行语义匹配。 diff --git a/guide/test-tags.md b/guide/test-tags.md index 939b9afb..d5ed8351 100644 --- a/guide/test-tags.md +++ b/guide/test-tags.md @@ -7,11 +7,6 @@ outline: deep 允许你在测试用例上添加 [`标签`](/config/tags),在必要时可以使用标签进行过滤测试,或覆盖测试配置。 -<<<<<<< HEAD -## 定义标签 {#defining-tags} - -Vitest 并未提供任何的内置标签,标签必须在配置文件中提前进行定义。如果在测试中使用了未在配置文件中定义的标签,测试运行器将会抛出错误。这一行为可以防止因标签名称拼写错误而导致的意外行为。当然你可以修改 [`strictTags`](/config/stricttags) 选项进行禁用。 -======= ## 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. @@ -31,10 +26,9 @@ A tag captures that kind of category: the definition holds the shared options, a You can combine projects and tags. A test that sits in a `Sequential` project can also carry a `flaky` tag, and Vitest applies both. -## Defining Tags +## 定义标签 {#defining-tags} -Tags must be defined in your configuration file. By default, Vitest does not provide any built-in tags. If a test uses a tag that isn't defined in the config, the test runner will throw an error. This prevents unexpected behavior from mistyped tag names. You can disable this check with the [`strictTags`](/config/stricttags) option. ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 +Vitest 并未提供任何的内置标签,在默认情况下,标签必须在配置文件中提前进行定义。如果在测试中使用了未在配置文件中定义的标签,测试运行器将会抛出错误。这一行为可以防止因标签名称拼写错误而导致的意外行为。当然你可以修改 [`strictTags`](/config/stricttags) 选项进行禁用。 在标签定义时至少必须包含 `name` 参数,与此同时你还可以定义其他配置参数如 `timeout` 或 `retry`,这些配置参数将应用于使用该标签的所有测试。完整的可用配置参数,参见 [`tags`](/config/tags)。 @@ -69,30 +63,7 @@ export default defineConfig({ }) ``` -<<<<<<< HEAD -::: warning -如果多个标签具有相同配置项且应用于同一个测试时,将按从上至下的顺序解析,或按优先级排序解析(数值越低,优先级越高)。未定义优先级的标签会先合并,随后被优先级更高的标签覆盖。 - -```ts -test('flaky database test', { tags: ['flaky', 'db'] }) -// { timeout: 30_000, retry: 3 } -``` - -注意此时的 `timeout` 是 30 秒而不是 60 秒,因为 `flaky` 标签的优先级为 `1`,而定义了 60 秒超时的 `db` 标签未设置优先级。 - -如果在当前测试上直接定义,则测试配置项优先级最高: - -```ts -test('flaky database test', { tags: ['flaky', 'db'], timeout: 120_000 }) -// { timeout: 120_000, retry: 3 } -``` - -::: - 如果你正在使用 TypeScript,可以扩展 `TestTags` 类型添加一个包含字符串的联合类型来限定的标签可用范围,请确保该文件被包含在 `tsconfig` 中: -======= -If you are using TypeScript, you can enforce what tags are available by augmenting the `TestTags` type with a property that contains a union of strings (make sure this file is included by your `tsconfig`): ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 ```ts [vitest.shims.ts] import 'vitest' @@ -149,29 +120,25 @@ flaky: Flaky CI tests. } ``` -<<<<<<< HEAD -## 在测试中使用标签 {#using-tags-in-tests} -======= ### Resolving option conflicts -If several tags define the same option and are applied to the same test, they are resolved by `priority` first (lower number wins), then by the order they appear in the test's `tags` array. Tags without a `priority` are merged first and overridden by higher-priority ones: +如果多个标签具有相同配置项且应用于同一个测试时,将按从上至下的顺序解析,或按优先级排序解析(数值越低,优先级越高)。未定义优先级的标签会先合并,随后被优先级更高的标签覆盖。 ```ts test('flaky database test', { tags: ['flaky', 'db'] }) // { timeout: 30_000, retry: 3 } ``` -The `timeout` is 30 seconds (not 60) because `flaky` has priority `1` while `db` has no priority. +注意此时的 `timeout` 是 30 秒而不是 60 秒,因为 `flaky` 标签的优先级为 `1`,而定义了 60 秒超时的 `db` 标签未设置优先级。 -Options defined on the test itself always win: +如果在当前测试上直接定义,则测试配置项优先级最高: ```ts test('flaky database test', { tags: ['flaky', 'db'], timeout: 120_000 }) // { timeout: 120_000, retry: 3 } ``` -## Using Tags in Tests ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 +## 在测试中使用标签 {#using-tags-in-tests} 可以通过 `tags` 参数 为单个测试用例或整个测试套件添加标签 @@ -356,11 +323,7 @@ vitest --tags-filter="unit || e2e" --tags-filter="!slow" ### 运行时检查标签过滤器 {#checking-tags-filter-at-runtime} -<<<<<<< HEAD -自 Vitest 4.1.1 起,你可以使用 `TestRunner.matchesTags` 方法来检查当前标签过滤器是否匹配一组标签。该特性特别适用于按需执行高开销的初始化逻辑,当相关测试的标签被包含时才运行: -======= -You can use `TestRunner.matchesTags` to check whether the current tags filter matches a set of tags. This is useful for conditionally running expensive setup logic only when relevant tests are included: ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789 +你可以使用 `TestRunner.matchesTags` 方法来检查当前标签过滤器是否匹配一组标签。该特性特别适用于按需执行高开销的初始化逻辑,当相关测试的标签被包含时才运行: ```ts import { beforeAll, TestRunner } from 'vitest' @@ -373,14 +336,11 @@ beforeAll(async () => { }) ``` -<<<<<<< HEAD 该方法接收一个标签数组作为参数,如果当前 `--tags-filter` 会包含带有这些标签的测试,则返回 `true`。如果未启用标签过滤器,则始终返回 `true`。 -======= The method accepts an array of tags and returns `true` if the current `--tags-filter` would include a test with those tags. If no tags filter is active, it always returns `true`. - + ## See also - [Per-File Isolation Settings](/guide/recipes/disable-isolation) and [Parallel and Sequential Test Files](/guide/recipes/parallel-sequential) use projects to partition tests by file. Reach for projects when categories need different runner settings rather than different timeouts or retries. - [Test Filtering](/guide/filtering) covers `-t`, `--include`, and the rest of the CLI filters. - [`tags`](/config/tags) and [`strictTags`](/config/stricttags) configuration reference. ->>>>>>> 3a513123224c0041b8cda52ce1f47c912ce05789