diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 16617faa..0cfc7dfa 100644 --- a/.vitepress/config.ts +++ b/.vitepress/config.ts @@ -152,6 +152,7 @@ export default ({ mode }: { mode: string }) => { search: { provider: 'local', +<<<<<<< HEAD options: { translations: { button: { @@ -168,6 +169,8 @@ export default ({ mode }: { mode: string }) => { }, }, }, +======= +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b }, // banner: { @@ -731,6 +734,10 @@ export default ({ mode }: { mode: string }) => { text: 'browser.trackUnhandledErrors', link: '/config/browser/trackunhandlederrors', }, + { + text: 'browser.importMapMocks', + link: '/config/browser/importmapmocks', + }, { text: 'browser.expect', link: '/config/browser/expect', @@ -1107,6 +1114,10 @@ export default ({ mode }: { mode: string }) => { text: '并行和串行测试文件', link: '/guide/recipes/parallel-sequential', }, + { + text: 'Running Only Affected Tests', + link: '/guide/recipes/affected-tests', + }, ], }, { diff --git a/.vitepress/scripts/cli-generator.ts b/.vitepress/scripts/cli-generator.ts index 717b037b..b0931fb6 100644 --- a/.vitepress/scripts/cli-generator.ts +++ b/.vitepress/scripts/cli-generator.ts @@ -9,7 +9,7 @@ const cliTablePath = resolve(docsDir, './guide/cli-generated.md') const nonNullable = (value: T): value is NonNullable => value !== null && value !== undefined -const skipCli = new Set(['mergeReports', 'shard']) +const skipCli = new Set(['mergeReports', 'shard', 'sinceLastRun']) const skipConfig = new Set([ 'config', @@ -25,6 +25,7 @@ const skipConfig = new Set([ 'coverage.thresholds.functions', 'coverage.thresholds.lines', 'standalone', + 'lastFailed', 'clearScreen', 'configLoader', 'color', diff --git a/api/advanced/runner.md b/api/advanced/runner.md index ee7f6944..86f4be42 100644 --- a/api/advanced/runner.md +++ b/api/advanced/runner.md @@ -271,6 +271,37 @@ export interface TaskResult { * The zero-based index of the current repeat. */ repeatCount?: number + /** + * Individual results for every retry and repeat attempt. + */ + attempts?: TaskResultAttempt[] +} + +export interface TaskResultAttempt { + /** + * The state after applying the test's expected failure option. + */ + state: 'pass' | 'fail' | 'skip' + /** + * Errors produced by this attempt. + */ + errors?: TestError[] + /** + * How long in milliseconds the attempt took to run. + */ + duration: number + /** + * Time in milliseconds when the attempt started running. + */ + startTime: number + /** + * The zero-based retry index within the repeat. + */ + retryIndex: number + /** + * The zero-based repeat index. + */ + repeatIndex: number } ``` diff --git a/api/advanced/test-case.md b/api/advanced/test-case.md index 740532be..b9b67b44 100644 --- a/api/advanced/test-case.md +++ b/api/advanced/test-case.md @@ -228,7 +228,11 @@ interface TestResultPassed { ``` ::: warning +<<<<<<< HEAD 请注意,状态为 `passed` 的测试仍可能附带有错误——如果 `retry` 至少触发了一次,这种情况就可能发生。 +======= +Note that the test with `passed` state can still have errors attached - this can happen if `retry` was triggered at least once. Use [`attempts()`](#attempts) to see which run produced each error. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ::: ## diagnostic @@ -278,6 +282,58 @@ interface TestDiagnostic { 如果测试尚未被安排运行,`diagnostic()` 将返回 `undefined`。 ::: +## attempts 5.1.0 {#attempts} + +```ts +function attempts(): ReadonlyArray +``` + +Results of every run of the test, in execution order. A test runs more than once when [`retry`](/config/retry) or [`repeats`](/api/test#repeats) is configured, and each run produces one entry. Unlike `result()`, which only reports the final outcome and merges errors from all runs, each attempt keeps its own state, errors and timing. + +```ts +interface TestAttempt { + /** + * The state of this attempt. `fails` tests are already inverted, + * so an expected failure is reported as `passed`. + */ + readonly state: 'passed' | 'failed' | 'skipped' + /** + * Errors thrown during this attempt only. + */ + readonly errors: ReadonlyArray | undefined + /** + * How long in milliseconds the attempt took to run. + */ + readonly duration: number + /** + * Time in milliseconds when the attempt started. + */ + readonly startTime: number + /** + * The zero-based retry index within the current repeat. + */ + readonly retryIndex: number + /** + * The zero-based repeat index. + */ + readonly repeatIndex: number +} +``` + +For example, a test with `retry: 2` that passes on its third run returns: + +```ts +[ + { state: 'failed', retryIndex: 0, repeatIndex: 0, errors: [/* ... */] }, + { state: 'failed', retryIndex: 1, repeatIndex: 0, errors: [/* ... */] }, + { state: 'passed', retryIndex: 2, repeatIndex: 0, errors: undefined }, +] +``` + +::: info +`attempts()` returns an empty array if the test has not run yet. +::: + ## annotations ```ts diff --git a/api/advanced/test-specification.md b/api/advanced/test-specification.md index d69b80e2..ee69c94a 100644 --- a/api/advanced/test-specification.md +++ b/api/advanced/test-specification.md @@ -86,6 +86,10 @@ describe('a group of tests', () => { // [!code error] 当前 TestSpecification 中需要运行的任务 ID 集合。 +## testNames 5.1.0 {#testnames} + +The full names of the tests inside of this specification to run, including the parent suites separated with `>`. + ## testTagsFilter 4.1.0 {#testtagsfilter} 测试必须通过的 [标签过滤器](/guide/test-tags#syntax) 才能被纳入运行范围。多个过滤器将按 `AND` 逻辑处理。 diff --git a/api/advanced/vitest.md b/api/advanced/vitest.md index dc32cfa3..4c8cb61b 100644 --- a/api/advanced/vitest.md +++ b/api/advanced/vitest.md @@ -44,9 +44,13 @@ const testCase = vitest.state.getReportedEntity(task) // 新 API ## cache +<<<<<<< HEAD Cache manager that stores the results of the previous test runs. In Vitest itself this is only used by the default sequencer to sort tests. +======= +Cache manager that stores the results of the previous test runs. Vitest uses it to sort tests in the default sequencer and to select tests with [`--last-failed`](/guide/cli#last-failed). +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ### cache.getTestSpecificationResult 5.0.4 {#cache-gettestspecificationresult} @@ -60,7 +64,7 @@ function getTestSpecificationResult( interface CachedTestFileResult { /** * The file has a known failure. - * Only a complete run of the file clears it. + * A complete run of the file clears it, or a run in which every known failed test passes. */ failed: boolean /** @@ -72,6 +76,12 @@ interface CachedTestFileResult { * It is not set if the file never ran completely. */ lastRun?: number + /** + * Full names of the tests that failed in the last run of the file. + * `--last-failed` runs only these tests. + * It is not set if the failure happened outside of tests, for example in a hook or during collection. + */ + failedTests?: string[] } ``` diff --git a/api/browser/assertions.md b/api/browser/assertions.md index c56f944a..2740c8ad 100644 --- a/api/browser/assertions.md +++ b/api/browser/assertions.md @@ -55,7 +55,11 @@ interface ExpectPollOptions { ``` ::: tip +<<<<<<< HEAD 与 [expect.poll](/api/expect#poll) 类似,`expect.element` 会反复重试 DOM 断言,直到断言通过或达到超时时间。当接收到定位器时,Vitest 会先通过 [locator.findElement()](/api/browser/locators#findelement) 解析该定位器,再执行 DOM 断言。`timeout` 参数适用于整个重试过程。`interval` 参数控制失败 DOM 断言的重试频率,但定位器解析使用 `findElement` 自身递增的重试间隔。 +======= +Like [`expect.poll`](/api/expect#poll), `expect.element` retries DOM assertions until they pass or the timeout is reached. When it receives a locator, Vitest resolves it with [`locator.findElement()`](/api/browser/locators#findelement) before running the DOM assertion. The `timeout` option applies to the whole retry operation. The `interval` option controls how often failed DOM assertions are retried, but locator resolution uses `findElement`'s own increasing retry intervals. If [fake timers](/api/vi#vi-usefaketimers) are enabled, `expect.element` advances them while it retries. See [Automatic Advancing](/guide/mocking/timers#automatic-advancing) for details. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b `toMatchTextContent` 以及其他所有断言在常规的 `expect` 中仍然可用,但没有内置的重试机制: diff --git a/api/browser/interactivity.md b/api/browser/interactivity.md index 009c9052..083a6174 100644 --- a/api/browser/interactivity.md +++ b/api/browser/interactivity.md @@ -235,6 +235,58 @@ await page.getByRole('tablist').wheel({ direction: 'right' }) 此方法用于测试显式监听 `wheel` 事件的 UI(例如自定义缩放控制、水平滚动标签页或 canvas 交互)。如果需要滚动页面以使元素进入视口,请改用其他 `userEvent` 方法或 [定位器操作](/api/browser/locators#methods) 提供的内置自动滚动功能。 ::: +## userEvent.pointer 5.1.0 {#userevent-pointer} + +```ts +function pointer(options: UserEventPointerInput): Promise +``` + +Performs one or more pointer actions. Pass a key string, an action object, or an array of strings and objects. Key strings use [user-event `pointer` syntax](https://testing-library.com/docs/user-event/pointer). For example, `[MouseLeft]` presses and releases the left mouse button, `[MouseLeft>]` keeps it pressed, and `[/MouseLeft]` releases it. + +::: tip +The `pointer` method is a low-level API for composing complex pointer interactions and performing actions that dedicated methods do not support, such as dragging an element to a precise position. + +For one-off actions, prefer the equivalent dedicated method because in most cases they are simpler to use. For example, if you just want to click an element once, use [`userEvent.click`](#userevent-click). +::: + +An action object accepts `keys`, `target`, and `coords`. An object without `keys` only moves the pointer. + +The pointer position persists between `userEvent.pointer` calls on the same `userEvent` instance. This means that if an action omits both `target` and `coords`, it uses the position from the previous action. + +::: warning +When the [Browser UI](/config/browser/ui) is enabled, the test iframe can be scaled to fit the available space. Vitest adjusts pointer coordinates to account for the scaling, but browser pixel rounding can cause the resulting event coordinates to differ by several _iframe pixels_. **Avoid relying on exact coordinates in this mode**. +::: + +```ts +import { page, userEvent } from 'vitest/browser' + +test('drags an element with pointer actions', async () => { + const source = page.getByTestId('source') + const target = page.getByTestId('target') + + await userEvent.pointer([ + { target: source, keys: '[MouseLeft>]' }, + { target, coords: { x: 10, y: 10 }, keys: '[/MouseLeft]' }, + ]) +}) +``` + +::: warning Provider support and differences from user-event +This API is not consistently supported across providers, and its behavior can differ from the `user-event` library. + +- The `playwright` provider does not support touch pointer keys such as `TouchA`. +- The `webdriverio` provider does not support this method at all. +- The `preview` provider simulates events using `@testing-library/user-event`. + +Vitest does not support the `node` and `offset` fields used for text selection and only accepts `x` and `y` values in `coords`. + +Without `target`, `coords` values are considered coordinates relative to the viewport. With `target`, they are offsets from its top-left corner. Unlike user-event, where coordinates paired with a target describe the final pointer position rather than an offset. When `coords` is provided, an omitted axis defaults to `0`. +::: + +References: + +- [testing-library `pointer` API](https://testing-library.com/docs/user-event/pointer) + ## userEvent.fill ```ts diff --git a/api/browser/locators.md b/api/browser/locators.md index c5c46cf1..21bb8e9e 100644 --- a/api/browser/locators.md +++ b/api/browser/locators.md @@ -632,6 +632,10 @@ page.getByRole('article').filter({ hasText: 'Vite' }) // ✅ 所有方法都是异步的,必须使用 `await`。自 Vitest 3 起,如果方法没有被 `await`,测试将会失败。 +::: tip +If [fake timers](/api/vi#vi-usefaketimers) are enabled, these methods advance them while they run. See [Automatic Advancing](/guide/mocking/timers#automatic-advancing) for details. +::: + ### click ```ts diff --git a/api/expect-typeof.md b/api/expect-typeof.md index 7e303533..02d86a57 100644 --- a/api/expect-typeof.md +++ b/api/expect-typeof.md @@ -35,7 +35,11 @@ expectTypeOf({ a: 1, b: 1 }).not.toEqualTypeOf<{ a: number }>() 自 expect-type v1.2.0 版本起,此匹配器已被弃用。请改用 [`toExtend`](#toextend)。 ::: +<<<<<<< HEAD 此匹配器检查期望类型是否扩展了提供的类型。它不同于 `toEqual`,更类似于 [expect's](/api/expect) `toMatchObject()`。使用此匹配器,你可以检查对象是否“匹配”类型。 +======= +This matcher checks if expect type extends provided type. It is different from `toEqual` and is more similar to [expect's](/api/expect) `toMatchObject()`. With this matcher, you can check if an object “matches” a type. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ```ts import { expectTypeOf } from 'vitest' diff --git a/api/expect.md b/api/expect.md index fea1e48c..3f5cb86a 100644 --- a/api/expect.md +++ b/api/expect.md @@ -119,6 +119,15 @@ interface ExpectPoll extends ExpectStatic { 如果在 `expect.poll` 回调中抛出错误,Vitest 将重试直到超时为止。 +The `message` option describes what the poll waits for. If the poll fails, Vitest adds the message to the start of the error. + +```ts +await expect.poll(() => queue.count(), { message: 'queue drained' }).toBe(0) +// AssertionError: queue drained: expected 3 to be 0 // Object.is equality +``` + +If `vi.useFakeTimers` is used, `expect.poll` automatically calls `vi.advanceTimersByTime(interval)` after every failed attempt. See [Automatic Advancing](/guide/mocking/timers#automatic-advancing) for details. + ```ts import { expect, test } from 'vitest' diff --git a/api/mock.md b/api/mock.md index 46a77977..3aeaaac3 100644 --- a/api/mock.md +++ b/api/mock.md @@ -53,8 +53,13 @@ fn.length // == 2 以下类型中的自定义函数实现使用泛型 `` 进行标记。 ::: +<<<<<<< HEAD ::: warning 类支持 {#class-support} 像 `mockReturnValue`、`mockReturnValueOnce`、`mockResolvedValue` 这样的简写方法不能用于模拟类。类构造函数在返回值方面具有 [反直觉的行为](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor): +======= +::: warning Class Support {#class-support} +The `mockReturnValue`, `mockReturnValueOnce`, `mockResolvedValue`, `mockResolvedValueOnce`, `mockRejectedValue` and `mockRejectedValueOnce` methods cannot be used when the mock is called with the `new` keyword. Class constructors have [unintuitive behaviour](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/constructor) regarding the return value: +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ```ts {2,7} const CorrectDogClass = vi.fn(class { @@ -74,7 +79,11 @@ Marti instanceof CorrectDogClass // ✅ true Newt instanceof IncorrectDogClass // ❌ false! ``` +<<<<<<< HEAD 尽管接口规范相同,但构造函数的 _返回值_ 被赋给了 `Newt`,这是一个普通对象,而非模拟类的实例。Vitest 会在简写方法中(但不会在 `mockImplementation` 中!)防止这种行为,转而抛出错误。 +======= +Even though the shapes are the same, the _return value_ from the constructor is assigned to `Newt`, which is a plain object, not an instance of a mock. Vitest guards you against this behaviour in these methods (but not in `mockImplementation`!) and throws an error instead. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b 如果需要模拟类的构造实例,考虑改用 `class` 语法配合 `mockImplementation`: @@ -101,7 +110,11 @@ mock.mockImplementation(class { function getMockImplementation(): T | undefined ``` +<<<<<<< HEAD 若存在 mock 实现,则返回其当前版本;否则返回空值。 +======= +Returns the current mock implementation if there is one. If an implementation was queued with [`mockImplementationOnce`](#mockimplementationonce), it returns the first queued implementation instead. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b 如果 mock 对象是使用 [`vi.fn`](/api/vi#vi-fn) 创建的,它将使用提供的方法作为模拟实现。 @@ -233,7 +246,7 @@ myMockFn() // 'original' 可与异步回调一起使用。该方法必须等待,之后才能使用原始实现。 ```ts -test('async callback', () => { +test('async callback', async () => { const myMockFn = vi.fn(() => 'original') // 由于回调是异步的,我们要等待这个调用 @@ -288,7 +301,11 @@ await asyncMock() // 抛出 Error<'Async error'> function mockReset(): Mock ``` +<<<<<<< HEAD 该方法会先执行与 [`mockClear`](#mockClear) 相同的清理,再重置 mock 的实现,并一并清除所有一次性(once)设定。 +======= +Does what [`mockClear`](#mockclear) does and resets the mock implementation. This also resets all "once" implementations. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b 注意:如果 mock 由 `vi.fn()` 创建,重置后其函数体将变为空实现,默认返回 `undefined`。如果由 `vi.fn(impl)` 创建,重置后实现会恢复为传入的 `impl`。 @@ -317,7 +334,7 @@ expect(spy.mock.calls).toEqual([['Bob']]) ## mockRestore ```ts -function mockRestore(): Mock +function mockRestore(): void ``` 该方法先完成 [`mockReset`](#mockreset) 的全部工作;若 mock 通过 [`vi.spyOn`](/api/vi#vi-spyon) 创建,还会进一步恢复被监视对象的原始属性描述符。 @@ -413,7 +430,11 @@ mock() // 43 function mockReturnValueOnce(value: ReturnType): Mock ``` +<<<<<<< HEAD 接受一个值,该值将在每次 mock 函数被调用时返回。TypeScript 仅接受与原始函数返回类型相匹配的值。 +======= +Accepts a value that will be returned during the next function call. TypeScript will only accept values that match the return type of the original function. If chained, each consecutive call will return the specified value. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b 当 mock 函数用尽所有实现后,如果之前调用过 `vi.fn(() => defaultValue)` 或 `.mockImplementation(() => defaultValue)`,它将调用设置的默认实现: @@ -529,8 +550,13 @@ const lastCall: Parameters | undefined interface MockResultReturn { type: 'return' /** +<<<<<<< HEAD * 函数返回的值。 * 如果函数返回的是一个 Promise,那么这将是一个已解析的值。 +======= + * The value that was returned from the function. + * If the function returned a Promise, then this will be the Promise, not its resolved value. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b */ value: T } @@ -622,7 +648,11 @@ const settledResults: MockSettledResult>>[] 该数组按顺序记录了函数每次被调用后最终兑现或拒绝的值。 +<<<<<<< HEAD 若函数返回的是非 Promise ,实际值会原封不动地保留,但状态仍被标记为 `fulfilled` 或 `rejected`。 +======= +If the function returned non-promise values, the `value` will be kept as is, but the `type` will still say `fulfilled` or `rejected`. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b 在结果出来前,对应的 `settledResult` 类型始终为 `incomplete`。 @@ -671,10 +701,18 @@ fn2.mock.invocationCallOrder === [2] ## mock.contexts ```ts -const contexts: ThisParameterType[] +type MockProcedureContext = T extends new (...args: any[]) => any + ? InstanceType + : ThisParameterType + +const contexts: MockProcedureContext[] ``` +<<<<<<< HEAD 这个属性是一个数组,包含了在每次调用模拟函数时使用的 `this` 值。 +======= +This property is an array of `this` values used during each call to the mock function. If the mock was called with the `new` keyword, the value is the same as in [`mock.instances`](#mock-instances). +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ```js const fn = vi.fn() @@ -690,22 +728,34 @@ fn.mock.contexts[1] === context ## mock.instances ```ts -const instances: ReturnType[] +const instances: MockProcedureContext[] ``` +<<<<<<< HEAD 该数组按顺序保存了每次用 `new` 调用 mock 时生成的实例。请注意,这里存储的是函数运行时的实际 `this` 上下文,而非函数的返回值。 ::: warning 若用 `new MyClass()` 实例化该 mock ,则 `mock.instances` 将是一个仅包含单个元素的数组: +======= +This property is an array with one value for each call to the mock function. If the mock was called with the `new` keyword, the value is the object that `new` returned, or `undefined` if the constructor threw an error. Otherwise, the value is the context (`this`) of the call, the same as in [`mock.contexts`](#mock-contexts). +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ```js const MyClass = vi.fn() +const context = {} + const a = new MyClass() +MyClass.call(context) MyClass.mock.instances[0] === a +MyClass.mock.instances[1] === context ``` +<<<<<<< HEAD 若构造函数显式返回值,该值不会存入 `instances`,而会出现在 `results` 中: +======= +If the implementation returns an object from the constructor, `mock.instances` stores that object: +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ```js const Spy = vi.fn(function () { @@ -713,8 +763,6 @@ const Spy = vi.fn(function () { }) const a = new Spy() -Spy.mock.instances[0] !== a -Spy.mock.results[0] === a +Spy.mock.instances[0] === a +Spy.mock.results[0].value === a ``` - -::: diff --git a/api/test.md b/api/test.md index 7c25a4f8..8fb6d1f8 100644 --- a/api/test.md +++ b/api/test.md @@ -75,6 +75,8 @@ test('skipped concurrent test', { skip: true, concurrent: true }, () => { 两种方式完全等价,使用哪种纯粹是风格偏好。 +Options set to `undefined` are ignored. They don't override values from the chain, the parent suite, or [tags](/guide/test-tags). + ### timeout - **类型:** `number` @@ -372,11 +374,17 @@ test.skipIf(isDev)('prod only test', () => { }) ``` +Unlike `test.skip`, `test.skipIf` always sets the [`skip`](#skip) option: `true` if the condition is truthy, `false` otherwise. A falsy condition overrides `skip` inherited from [tags](/guide/test-tags), the same as `test(name, { skip: false })`. + ## test.runIf - **别名:** `it.runIf` +<<<<<<< HEAD 于 [test.skipIf](#test-skipif) 相反。 +======= +Opposite of [test.skipIf](#test-skipif). It always sets the [`skip`](#skip) option: `false` if the condition is truthy, `true` otherwise. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ```ts import { assert, test } from 'vitest' diff --git a/api/vi.md b/api/vi.md index 87b8a891..915bcf6d 100644 --- a/api/vi.md +++ b/api/vi.md @@ -128,15 +128,26 @@ vi.mock('./path/to/module.js', () => { ::: +<<<<<<< HEAD 如果要模拟的文件旁边有一个 `__mocks__` 文件夹,且没有提供工厂,Vitest 将尝试在 `__mocks__` 子文件夹中找到一个同名文件,并将其作为实际模块使用。如果模拟的是依赖关系,Vitest 会尝试在项目的 [root](/config/root)(默认为 `process.cwd()`)中找到 `__mocks__` 文件夹。我们可以通过 [`deps.moduleDirectories`](/config/#deps-moduledirectories) 配置选项告诉 Vitest 依赖项的位置。 +======= +If the factory is not provided, Vitest looks for a file with the same name in a `__mocks__` folder and uses it instead of the original module: + +- If the path is a package name, like `axios` or `@company/utils/format`, Vitest looks in the `__mocks__` folder in the [root](/config/root) of the project (default is `process.cwd()`) for a file with this path and any extension, for example, `__mocks__/@company/utils/format.ts`. This also applies to a package that is linked from outside of `node_modules`, like a workspace package in a monorepo. +- If the module resolves to a file outside of `node_modules`, Vitest looks in the `__mocks__` folder next to that file for a file with the same name and extension, for example, `src/__mocks__/increment.js` for `src/increment.js`. For a package name, Vitest does this only if the root `__mocks__` folder doesn't have the file. + +Vitest treats files in other [`deps.moduleDirectories`](/config/deps#deps-moduledirectories) the same as files in `node_modules`. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b 例如,我们有这样的文件结构: ``` - __mocks__ + - @company + - utils.js - axios.js - src - __mocks__ + - __mocks__ - increment.js - increment.js - tests @@ -149,15 +160,25 @@ vi.mock('./path/to/module.js', () => { // axios 是 `__mocks__/axios.js` 默认导出项 import axios from 'axios' +<<<<<<< HEAD import { vi } from 'vitest' // increment 是 `src/__mocks__/increment.js` 具名导出 import { increment } from '../increment.js' +======= +// format is a named export from `__mocks__/@company/utils.js`, +// even if `@company/utils` is a workspace package +import { format } from '@company/utils' + +// increment is a named export from `src/__mocks__/increment.js` +import { increment } from '../src/increment.js' +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b vi.mock('axios') -vi.mock('../increment.js') +vi.mock('@company/utils') +vi.mock('../src/increment.js') -axios.get(`/apples/${increment(1)}`) +axios.get(`/apples/${format(increment(1))}`) ``` ::: warning @@ -522,6 +543,10 @@ expect(spied.simple.mock.results[0]).toEqual({ }) ``` +::: warning +With `spy: true`, `vi.mockObject` doesn't copy objects. It returns the same `original` object and replaces its methods, including the methods of nested objects and class prototypes, with spies in place. +::: + ### vi.isMockFunction ```ts @@ -1167,14 +1192,18 @@ await vi.runOnlyPendingTimersAsync() ### vi.setSystemTime ```ts -function setSystemTime(date: string | number | Date): Vitest +function setSystemTime(date: string | number | Date | Temporal.Instant | Temporal.ZonedDateTime): Vitest ``` 如果启用了伪计时器,此方法将模拟用户更改系统时钟(将影响与日期相关的 API,如 `hrtime`、`performance.now` 或 `new Date()`),但不会触发任何计时器。如果未启用假定时器,该方法将仅模拟 `Date.*` 和 `Temporal.Now.*` 调用。 适用于需要测试依赖当前日期的场景,例如代码中的 [Luxon](https://github.com/moment/luxon/) 库调用。 +<<<<<<< HEAD 接受与 `Date` 相同的字符串和数字参数。 +======= +Accepts the same string and number arguments as the `Date`, or a `Temporal.Instant` or `Temporal.ZonedDateTime`. Vitest uses the moment represented by a `ZonedDateTime` as the mocked time and leaves the environment's time zone unchanged. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ```ts const date = new Date(1998, 11, 19) @@ -1197,7 +1226,13 @@ function useFakeTimers(config?: FakeTimersConfig): Vitest 在 `node:child_process` 中使用 `--pool=forks` 运行 Vitest 时,不支持模拟 `nextTick`。NodeJS 在 `node:child_process` 中内部使用了 `process.nextTick`,当模拟它时会挂起。使用 `--pool=threads` 运行 Vitest 时支持模拟 `nextTick`。 +<<<<<<< HEAD 内部实现基于 [`@sinonjs/fake-timers`](https://github.com/sinonjs/fake-timers)。 +======= +The `now` option accepts a `Date`, number, `Temporal.Instant`, or `Temporal.ZonedDateTime`. Vitest uses the moment represented by a `ZonedDateTime` as the mocked time and leaves the environment's time zone unchanged. + +The implementation is based internally on [`@sinonjs/fake-timers`](https://github.com/sinonjs/fake-timers). +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ::: tip `vi.useFakeTimers()` 不再自动模拟 `process.nextTick`。 @@ -1224,7 +1259,11 @@ vi.useFakeTimers({ toNotFake: ['setInterval'] }) - `nextTimerAsync`:每个宏任务执行后自动推进到下一个可用计时器 - `interval`:按指定间隔自动推进计时器 +<<<<<<< HEAD 当 `mode` 为 `'interval'` 时,可额外传入毫秒级 `interval` 参数。 +======= +When `mode` is `'interval'`, you can also provide an `interval` in milliseconds. See [Tick Modes](/guide/mocking/timers#tick-modes) for when to use each mode. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b **示例:** @@ -1342,7 +1381,11 @@ test('Element exists in a DOM', async () => { }) ``` +<<<<<<< HEAD 一旦通过 `vi.useFakeTimers` 启用假计时器,`vi.waitFor` 将在每次轮询时自动调用 `vi.advanceTimersByTime(interval)` 推进时间。 +======= +If `vi.useFakeTimers` is used, `vi.waitFor` automatically calls `vi.advanceTimersByTime(interval)` in every check callback. See [Automatic Advancing](/guide/mocking/timers#automatic-advancing) for details. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ### vi.waitUntil {#vi-waituntil} @@ -1369,7 +1412,11 @@ test('Element render correctly', async () => { }) ``` +<<<<<<< HEAD 如果使用了 `vi.useFakeTimers`, `vi.waitFor` 会在每次检查回调中自动调用 `vi.advanceTimersByTime(interval)`。 +======= +If `vi.useFakeTimers` is used, `vi.waitUntil` automatically calls `vi.advanceTimersByTime(interval)` in every check callback. See [Automatic Advancing](/guide/mocking/timers#automatic-advancing) for details. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ### vi.hoisted {#vi-hoisted} diff --git a/config/api.md b/config/api.md index b3a0194e..18679d9a 100644 --- a/config/api.md +++ b/config/api.md @@ -9,10 +9,16 @@ outline: deep - **默认值:** `false` - **命令行终端:** `--api`, `--api.port`, `--api.host`, `--api.strictPort` +<<<<<<< HEAD 监听端口并提供 API 服务,用于 [UI 模式](/guide/ui) 或 [浏览器服务](/guide/browser/)。设为 `true` 时,默认端口为 `51204`,如果在浏览器模式下运行,则为 `63315`。 +======= +Listen to port and serve API for [the UI](/guide/ui) or [browser server](/guide/browser/). When set to `true`, the default port is `51204` or `23315` if running in Browser Mode. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b In Browser Mode the server binds its port on the first browser launch, unless `api` or `ui` is enabled. +Set `port` to `0` to let the operating system pick a free port. + ## api.allowWrite 4.1.0 {#api-allowwrite} - **类型:** `boolean` diff --git a/config/browser/importmapmocks.md b/config/browser/importmapmocks.md new file mode 100644 index 00000000..7498ec95 --- /dev/null +++ b/config/browser/importmapmocks.md @@ -0,0 +1,32 @@ +--- +title: browser.importMapMocks | Config +outline: deep +--- + +# browser.importMapMocks 5.1.0 + +- **Type:** `boolean` +- **Default:** enabled when the browser supports it +- **CLI:** `--browser.importMapMocks`, `--browser.importMapMocks=false` + +Serve module mocks through an [import map](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type/importmap) in the tester document instead of intercepting network requests. + +By default, Vitest checks once per browser whether it accepts an import map after a module has loaded. If it does not, Vitest intercepts requests instead. + +An import map rule cannot be removed from the document. When [`isolate`](/config/isolate) is `false`, all test files run in the same document, so Vitest also intercepts requests by default. + +Set the option to `true` to always use import maps, or to `false` to always intercept requests. + +When a test file calls `vi.mock`, Vitest adds an import map rule that points the module URL to its mocked version: the automocked module, the file in the `__mocks__` folder, or a module that exports every name of the original and reads the values from the factory. The browser loads the mocked module itself, so the provider does not intercept requests (for example, with Playwright's `page.route`) and the mock stays local to the document of the test file. + +Request interception adds a cost to every request in the page, which grows with the number of modules a test file imports. An import map has no per-request cost. + +::: warning +The browser must support multiple import maps per document: Chromium 133+, Safari 18.4+, and Firefox 150+ with the `dom.multiple_import_maps.enabled` preference. The `playwright` provider sets this preference. Other providers cannot, so Firefox falls back to request interception there. + +An import map rule cannot change a module that the document already imported. If a module was imported before `vi.mock` registered it, for example by a setup file that does not mock it, Vitest reports an error instead of silently using the original module. + +An import map rule cannot be removed either. When a test file calls `vi.unmock` with a static path, the setup files do not add a rule for that module, so both the setup files and the test file load the original module. `vi.doUnmock` cannot restore the original module. + +A factory can only provide the exports that the original module declares. Vitest reads the export names from the original module, so a key that the factory adds cannot be imported, and an export that the factory omits is `undefined`. +::: diff --git a/config/browser/instances.md b/config/browser/instances.md index 900a226c..6d4f0348 100644 --- a/config/browser/instances.md +++ b/config/browser/instances.md @@ -38,7 +38,13 @@ export default defineConfig({ 更多示例请参阅 [“多环境配置” 指南](/guide/browser/multiple-setups)。 ::: +<<<<<<< HEAD 可用的 `browser` 选项列表: +======= +The [`globalSetup`](/config/globalsetup) files of the config that defines `instances` run once for all of them, not once per instance, and receive the project of that config. Values they provide with `project.provide` are available in every instance. Global setup files defined in an instance run only for that instance. + +List of available `browser` options: +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b - `browser` (浏览器名称) - [`headless`](/config/browser/headless) diff --git a/config/browser/traceview.md b/config/browser/traceview.md index 3d5a2e95..4c40a76a 100644 --- a/config/browser/traceview.md +++ b/config/browser/traceview.md @@ -3,13 +3,23 @@ title: browser.traceView | 配置 outline: deep --- -# browser.traceView 5.0.0 +# browser.traceView 5.0.0 +<<<<<<< HEAD - **类型:** `boolean | { enabled?: boolean; recordCanvas?: boolean; inlineImages?: boolean }` - **命令行终端:** `--browser.traceView` - **默认值:** `false` 启用浏览器测试的 trace-view(追踪视图)收集功能。Vitest 会捕获浏览器交互的 DOM 快照,当启用浏览器 UI 模式、UI 模式 或 HTML 报告器时,可在这些界面中无需外部工具直接展示。 +======= +- **Type:** `boolean | 'off' | 'on' | 'failure-only' | { mode?: 'off' | 'on' | 'failure-only'; recordCanvas?: boolean; inlineImages?: boolean; ariaSnapshot?: boolean }` +- **CLI:** `--browser.traceView` +- **Default:** `'failure-only'` + +Records the actions, assertions, and marks of browser tests with DOM snapshots, so you can replay them in the [trace viewer](/guide/browser/trace-view). + +Set it to a [mode](#traceview-mode) such as `'failure-only'`, or use `true` and `false` as shorthands for `'on'` and `'off'`: +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ```ts export default defineConfig({ @@ -21,35 +31,52 @@ export default defineConfig({ }) ``` +<<<<<<< HEAD 使用对象形式可启用额外的快照保真度选项: +======= +To combine a mode with the options below, use an object: +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ```ts export default defineConfig({ test: { browser: { traceView: { - enabled: true, + mode: 'on', inlineImages: true, - recordCanvas: true, }, }, }, }) ``` +<<<<<<< HEAD | 选项 | 默认值 | 详情 | | -------------- | ------- | ----------------------------------------------------------------------------- | | `enabled` | `false` | 启用 Vitest trace-view 收集产物。 | | `inlineImages` | `false` | 将加载的 `` 像素内联到快照中,实现更便捷的回放功能,适用于 HTML 报告器。 | | `recordCanvas` | `false` | 在快照中捕获 canvas 像素。 | +======= +## browser.traceView.mode 5.1.0 {#traceview-mode} +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b -## browser.traceView.enabled {#traceview-enabled} +- **Type:** `'off' | 'on' | 'failure-only'` +- **Default:** `'failure-only'` +- **CLI:** `--browser.traceView.mode ` +<<<<<<< HEAD - **类型:** `boolean` - **默认值:** `false` - **命令行终端:** `--browser.traceView.enabled` 启用 Vitest 追踪视图构建产物收集。 +======= +Sets which tests trace view records and what each trace keeps: + +- `'failure-only'` records only failed tests. Each trace keeps every step and a snapshot of the page when the test failed. +- `'on'` records every test with a snapshot at each step, and Vitest UI shows the steps live as tests run. +- `'off'` records nothing. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ## browser.traceView.inlineImages {#traceview-inlineimages} @@ -57,7 +84,11 @@ export default defineConfig({ - **默认值:** `false` - **命令行终端:** `--browser.traceView.inlineImages` +<<<<<<< HEAD 将加载的 `` 像素内联到快照中,实现更便捷的回放功能,适用于 HTML 报告器。 +======= +Inlines loaded `` pixels into snapshots, so the HTML report renders them without loading the original URLs. See [What Snapshots Capture](/guide/browser/trace-view#what-snapshots-capture) for details. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ## browser.traceView.recordCanvas {#traceview-recordcanvas} @@ -65,6 +96,25 @@ export default defineConfig({ - **默认值:** `false` - **命令行终端:** `--browser.traceView.recordCanvas` +<<<<<<< HEAD 在快照中捕获 canvas 像素。这会启用较弱的回放 iframe 沙盒,因为 rrweb 需要脚本重绘 canvas数据。 完整文档请参阅 [追踪视图](/guide/browser/trace-view)。 +======= +Captures canvas pixels in snapshots. The trace viewer then allows scripts in its replay iframe for these traces. See the canvas replay sandbox warning in [What Snapshots Capture](/guide/browser/trace-view#what-snapshots-capture). + +## browser.traceView.ariaSnapshot 5.1.0 {#traceview-ariasnapshot} + +- **Type:** `boolean` +- **Default:** `true` +- **CLI:** `--browser.traceView.ariaSnapshot` + +Captures the accessibility tree of the page alongside each snapshot, so you can switch the trace viewer's snapshot pane between **DOM** and **Aria** views. The Aria view uses the same format as [aria snapshots](/guide/browser/aria-snapshots). + +## browser.traceView.enabled {#traceview-enabled} + +- **Type:** `boolean` +- **CLI:** `--browser.traceView.enabled` + +Use [`mode`](#traceview-mode) instead. `true` is the same as `mode: 'on'`, and `false` is the same as `mode: 'off'`. When both are set, `mode` wins. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b diff --git a/config/browser/ui.md b/config/browser/ui.md index af1fc3d7..4988795f 100644 --- a/config/browser/ui.md +++ b/config/browser/ui.md @@ -5,8 +5,16 @@ outline: deep # browser.ui +<<<<<<< HEAD - **类型:** `boolean` - **默认值:** `!isCI` - **命令行终端:** `--browser.ui=false` 是否注入 UI 模式界面。默认情况下,在开发阶段会注入 UI iframe。 +======= +- **Type:** `boolean` +- **Default:** `!browser.headless && !isCI` +- **CLI:** `--browser.ui=false` + +Should the browser UI be injected into the page. By default, injects UI iframe during development. See [Headless Mode and UI](/guide/browser/#headless-mode-and-ui) for how this option relates to [`browser.headless`](/config/browser/headless) and the top-level [`--ui`](/guide/ui) flag. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b diff --git a/config/cache.md b/config/cache.md index 80ee1302..20a09b1f 100644 --- a/config/cache.md +++ b/config/cache.md @@ -9,9 +9,9 @@ outline: deep - **默认值:** `true` - **命令行终端:** `--cache`, `--no-cache` -Store the results of test runs on the file system. Vitest uses them to run failed and longer test files first. +Store the results of test runs on the file system. Vitest uses them to run failed and longer test files first and to select tests with [`--last-failed`](/guide/cli#last-failed). -For every test file, Vitest stores whether the file failed, how long it ran and when it ran last. A run that did not execute the whole file (for example, a run filtered with [`--testNamePattern`](/config/testnamepattern) or cancelled by [`bail`](/config/bail)) can mark the file as failed, but it cannot mark it as passed. +For every test file, Vitest stores whether the file failed, which tests failed, how long it ran and when it ran last. A run that did not execute the whole file (for example, a run filtered with [`--testNamePattern`](/config/testnamepattern) or cancelled by [`bail`](/config/bail)) can mark the file as failed, but it cannot mark it as passed. You can delete the cache by running [`vitest --clearCache`](/guide/cli#clearcache). diff --git a/config/coverage.md b/config/coverage.md index c07f1613..9976e83a 100644 --- a/config/coverage.md +++ b/config/coverage.md @@ -408,6 +408,35 @@ Vitest 会将所有文件(包括匹配 glob 模式的文件)计入全局覆 设置为要忽略覆盖率的类方法名称数组。 更多信息请参阅 [istanbul 文档](https://github.com/istanbuljs/nyc#ignoring-methods)。 +## coverage.ignoreNode 5.1.0 {#coverage-ignorenode} + +- **Type:** `(node: Node, type: 'function' | 'statement' | 'branch') => boolean | 'ignore-this-and-nested-nodes' | void` +- **Available for providers:** `'v8'` + +Filter to ignore code from coverage based on its AST node. Vitest calls it for each node of the transformed code, together with the kind of coverage the node counts for. Return `true` to ignore the node, or `'ignore-this-and-nested-nodes'` to also ignore all nodes inside it. + +This is useful for code that doesn't exist in the source file and can't carry an [ignore hint](/guide/coverage#ignoring-code), like code that compilers or other plugins generate. Since the nodes come from the code after Vite's transforms, the filter sees exactly what the plugins generated. + +Vitest's built-in filters run first, so nodes that Vitest already ignores, like its own transform artifacts, are never passed to this function. + +```ts +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + coverage: { + provider: 'v8', + ignoreNode: (node, type) => + type === 'statement' + && node.type === 'ExpressionStatement' + && node.expression.type === 'CallExpression' + && node.expression.callee.type === 'Identifier' + && node.expression.callee.name === '__generatedHelper', + }, + }, +}) +``` + ## coverage.watermarks - **类型:** @@ -439,10 +468,17 @@ Vitest 会将所有文件(包括匹配 glob 模式的文件)计入全局覆 ## coverage.processingConcurrency +<<<<<<< HEAD - **类型:** `boolean` - **默认值:** `Math.min(20, os.availableParallelism?.() ?? os.cpus().length)` - **可用的测试提供者:** `'v8' | 'istanbul'` - **命令行终端:** `--coverage.processingConcurrency=` +======= +- **Type:** `boolean` +- **Default:** `Math.min(20, os.availableParallelism())` +- **Available for providers:** `'v8' | 'istanbul'` +- **CLI:** `--coverage.processingConcurrency=` +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b 处理代码覆盖率结果时使用的并发限制。 diff --git a/config/deps.md b/config/deps.md index 70e7c073..584b42ec 100644 --- a/config/deps.md +++ b/config/deps.md @@ -113,11 +113,21 @@ TypeError: default is not a function - **类型:** `string[]` - **默认值:** `['node_modules']` +<<<<<<< HEAD 配置一个视为模块目录的目录列表。此配置选项会影响 [`vi.mock`](/api/vi#vi-mock) 的行为:当未提供工厂并且你正在模拟的路径与 `moduleDirectories` 值之一匹配时,Vitest 将尝试 通过在项目的 [root](/config/root) 中查找 `__mocks__` 文件夹来解析 mock。 此选项还将影响在外部化依赖项时是否应将文件视为模块。默认情况下,Vitest 绕过 Vite 转换步骤导入带有原生 Node.js 的外部模块。 设置此选项将 _覆盖_ 默认值,如果你仍希望搜索 `node_modules` 包包括它连同任何其他选项: +======= +A list of directories that contain dependencies. Vitest adds a slash before and after each value and checks if the path of a file includes it: `'vendor'` matches every file inside a `vendor` folder, and an absolute path matches only the files inside that folder. Vitest always includes `node_modules`. + +Vitest treats a file inside one of these directories as a dependency: + +- [`vi.mock`](/api/vi#vi-mock) without a factory looks for the mock only in the `__mocks__` folder in the [root](/config/root) of the project. +- Vitest [externalizes](/config/server#server-deps-external) JavaScript files that Node.js can import directly, for example, a `.js` file in a package with `"type": "module"`. Vitest imports these files with native Node.js, Vite plugins don't transform them, and `vi.mock` doesn't apply to the modules that they import. +- Vitest prefixes string values of [`server.deps.inline`](/config/server#server-deps-inline) and [`server.deps.external`](/config/server#server-deps-external) with these directories. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ```ts import { defineConfig } from 'vitest/config' @@ -125,8 +135,16 @@ import { defineConfig } from 'vitest/config' export default defineConfig({ test: { deps: { - moduleDirectories: ['node_modules', path.resolve('../../packages')], - } + moduleDirectories: ['vendor'], + }, }, }) ``` + +::: tip +You don't need this option to mock a workspace package in a monorepo. If you call `vi.mock` with a package name, Vitest looks in the root `__mocks__` folder even if the package resolves outside of `node_modules`. +::: + +::: warning +Don't add a directory that contains the source files of your project. For example, if the project is in `packages/app`, the value `packages` makes Vitest treat the files in `packages/app` as dependencies too. +::: diff --git a/config/experimental.md b/config/experimental.md index e9a8acdb..dbafcc66 100644 --- a/config/experimental.md +++ b/config/experimental.md @@ -316,7 +316,11 @@ export default defineConfig({ ## experimental.vcsProvider 4.1.1 {#experimental-vcsprovider} +<<<<<<< HEAD - **类型:** `VCSProvider | string` +======= +- **Type:** `'git' | 'mtime' | VCSProvider | string` +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ```ts interface VCSProvider { @@ -326,14 +330,61 @@ interface VCSProvider { interface VCSProviderOptions { root: string changedSince?: string | boolean + /** Resolves the dependencies of the test files of the run. */ + resolver: ModulesResolver +} + +interface ModulesResolver { + /** + * Every file whose change can affect the test files of the run. + */ + getDependencies(): Promise +} + +interface ModuleDependency { + /** Absolute path of the file. */ + file: string + /** + * Start time of the oldest run that recorded the file, in milliseconds. + * Not set when the dependency comes from the static module graph. + */ + recordedAt?: number } ``` - **默认值:** `'git'` +<<<<<<< HEAD 用于检测更改文件的自定义驱动。与 [`--changed`](/guide/cli#changed) 参数配合使用,用于确定哪些文件已被修改。 默认情况下,Vitest 使用 Git 检测更改的文件。你可以提供 `VCSProvider` 接口的自定义实现以使用不同的版本控制系统: +======= +Provider for detecting changed files. Used with the [`--changed`](/guide/cli#changed) flag to determine which files have been modified. + +Vitest has two built-in providers: + +- `'git'` returns the files that differ from the `--changed` reference, plus staged and untracked files. +- `'mtime'` 5.1.0 returns the recorded dependencies whose modification time is newer than the run that recorded them, and deleted dependencies. It needs [`experimental.recordDependencies`](#experimental-recorddependencies): a dependency without a record is always reported as changed, so every test runs until the records exist. It does not need Git and ignores the `--changed` reference. A `--changed` run updates the records of the tests it ran, so the next `--changed` run selects only the tests whose dependencies changed since, and the test files that failed in their last run. + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + experimental: { + recordDependencies: true, + vcsProvider: 'mtime', + }, + }, +}) +``` + +::: warning +The `mtime` provider compares file times with the clock of the machine that ran the tests. A checkout or a copy that sets file times in the past is not detected. +::: + +You can provide a custom implementation of the `VCSProvider` interface to use a different version control system. `options.resolver.getDependencies()` returns every file the test files of the run depend on, so a provider can limit its work to these files: +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ```ts [vitest.config.ts] import { defineConfig } from 'vitest/config' @@ -342,8 +393,14 @@ export default defineConfig({ test: { experimental: { vcsProvider: { +<<<<<<< HEAD async findChangedFiles({ root, changedSince }) { // 返回已变更的文件路径 +======= + async findChangedFiles({ root, changedSince, resolver }) { + const dependencies = await resolver.getDependencies() + // return the paths of the changed files +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b return [] }, }, @@ -375,6 +432,49 @@ export default { } ``` +## experimental.recordDependencies 5.1.0 {#experimental-recorddependencies} + +- **Type:** `boolean` +- **Default:** `false` + +Records every file that a test file loads while it runs and uses these records to select tests for [`--changed`](/guide/cli#changed) and [`vitest related`](/guide/cli#vitest-related). Without this option, Vitest transforms the module graph of every test file before the run to find the affected tests. With it, Vitest reads the records from the previous run instead, so the selection does not transform anything. + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + experimental: { + recordDependencies: true, + }, + }, +}) +``` + +The records are stored in `dependencies.json` next to the [results cache](/config/cache) in Vite's `cacheDir`, so this option has no effect if `cache` is disabled. Every `vitest run` with the option enabled updates the records of the test files it ran. A run that skips some tests of a file, for example with [`-t`](/config/testnamepattern) or `.only`, can miss their dynamic imports, so it only adds files to an existing record and never creates one. A test file without a record, for example a new test file or a file that ran in a browser or [typecheck](/guide/testing-types) pool, always runs. + +A record contains: + +- the test file and every module it imported, including dynamic imports that ran +- [setup files](/config/setupfiles), the [custom environment](/config/environment), the [custom runner](/config/runner), [snapshot serializers](/config/snapshotserializers) and their imports +- mock files from the `__mocks__` folder and modules imported by a mock factory +- files that plugins watch with `this.addWatchFile` +- the snapshot file and every file used by [`toMatchFileSnapshot`](/api/expect#tomatchfilesnapshot) + +A change in a [global setup](/config/globalsetup) file or its imports, in the config file and its imports, or in an `.env` file runs every test of the project. + +Because the records come from a real run, they are exact where the static module graph is not. A module replaced with `vi.mock` and a factory does not load its imports, so they are not recorded. A dynamic import inside a function that the test never calls is not recorded either. + +::: warning +The records describe the files that a test loaded during its last run. If a change that adds an import is committed before the test runs again, a later change in the newly imported file does not select the test, because the commit is no longer part of the `--changed` diff. Run the tests once after you change imports to update the records. + +Vitest records only the files it loads itself. Files read with `fs` (for example, fixtures) and the imports of [externalized](/config/server#server-deps-external) modules are not recorded. + +If [`isolate`](/config/isolate) is disabled, test files that run in the same worker share the module graph. A record can then contain modules that a previous test file loaded dynamically, so `--changed` can select more tests than necessary, but never fewer. + +If [`experimental.viteModuleRunner`](#experimental-vitemodulerunner) is disabled, Vitest records the imports with a resolve hook of [`module.registerHooks`](https://nodejs.org/api/module.html#moduleregisterhooksoptions). Node.js versions without it do not write records, so every test runs. +::: + ## experimental.nodeLoader 4.1.0 {#experimental-nodeloader} - **类型:** `boolean` diff --git a/config/faketimers.md b/config/faketimers.md index d784ba0c..4531c029 100644 --- a/config/faketimers.md +++ b/config/faketimers.md @@ -7,7 +7,11 @@ outline: deep - **类型:** `FakeTimerConfig` +<<<<<<< HEAD 当使用 [`vi.useFakeTimers()`](/api/vi#vi-usefaketimers) 时,Vitest 会将此选项传递给 [`@sinon/fake-timers`](https://npmx.dev/package/@sinonjs/fake-timers)。 +======= +Options that configure fake timers when using [`vi.useFakeTimers()`](/api/vi#vi-usefaketimers). Vitest's fake timer implementation is based on [`@sinon/fake-timers`](https://npmx.dev/package/@sinonjs/fake-timers), with additional options and behavior tailored to Vitest. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ## fakeTimers.now @@ -66,4 +70,15 @@ An array of global methods and APIs to exclude from fake timers. Vitest always e - **类型:** `boolean` - **默认值:** `true` +<<<<<<< HEAD 指示假计时器通过委托给它们各自的处理函数来清除 “原生”(即非假)计时器。如果禁用此功能,当伪计时器会话启动前已存在计时器时,可能导致意外行为。 +======= +Tells fake timers to clear "native" (i.e. not fake) timers by delegating to their respective handlers. When disabled, it can lead to potentially unexpected behavior if timers existed prior to starting fake timers session. + +## fakeTimers.fakeNodeBuiltins 5.0.0 {#faketimers-fakenodebuiltins} + +- **Type:** `boolean` +- **Default:** `false` + +Mock timer APIs imported from `node:timers` and `node:timers/promises`. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b diff --git a/config/maxworkers.md b/config/maxworkers.md index 8dade68e..6f4c6f67 100644 --- a/config/maxworkers.md +++ b/config/maxworkers.md @@ -57,4 +57,14 @@ vitest --maxWorkers=50% ::: +<<<<<<< HEAD Vitest 通过 [`os.availableParallelism`](https://nodejs.org/api/os.html#osavailableparallelism) 接口获取系统可用的最大并发数量。 +======= +Vitest uses [`os.availableParallelism`](https://nodejs.org/api/os.html#osavailableparallelism) to know the maximum amount of parallelism available. + +## Browser Mode + +In [Browser Mode](/guide/browser/), every browser instance (a [project](/guide/projects) with `browser.enabled`, or an entry in [`browser.instances`](/config/browser/instances)) is a worker, so at most `maxWorkers` instances run at the same time. When another instance needs a slot, an idle headless instance is closed and opens again on its next run. An instance that is not headless stays open for the whole session, so its window is not closed between runs. The same number is the budget of pages shared by the open instances: a single instance runs up to `maxWorkers` test files in parallel in its own pages, and several instances split the budget between them, with at least one page each. + +An instance runs a single test file at a time when the browser is not headless, or when the provider does not support parallelism. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b diff --git a/config/name.md b/config/name.md index 7b969c94..49d277b4 100644 --- a/config/name.md +++ b/config/name.md @@ -67,12 +67,16 @@ export default defineConfig({ test: { projects: [ { - name: 'unit', - include: ['./test/*.unit.test.js'], + test: { + name: "unit", + include: ["./test/*.unit.test.js"], + }, }, { - name: 'e2e', - include: ['./test/*.e2e.test.js'], + test: { + name: "e2e", + include: ["./test/*.e2e.test.js"], + }, }, ], }, diff --git a/config/pool.md b/config/pool.md index bd99ceb0..62abf7dc 100644 --- a/config/pool.md +++ b/config/pool.md @@ -48,8 +48,24 @@ catch (err) { } ``` +<<<<<<< HEAD - 导入 ES 模块会导致其被永久缓存,当存在大量上下文(测试文件)时,将引发内存泄漏问题。Node.js 目前未提供清除该缓存的 API 接口。 - 在沙箱环境中访问全局变量 [耗时更长](https://github.com/nodejs/node/issues/31658)。 +======= +- Objects and arrays created by Node.js APIs and by the test environment come from outside of the sandbox too. This includes the result of `await response.json()` on a `Response` or a `Request`, `structuredClone()`, `path.parse()` and DOM methods like `element.getAttributeNames()`. Their prototype is not the `Object.prototype` or `Array.prototype` of your tests, so [`toStrictEqual`](/api/expect#tostrictequal) does not consider them equal to the same literal, and libraries that detect plain objects by their prototype treat them as class instances. If your tests depend on these values, run them in the `forks` or `threads` pool, or parse fetch responses inside the sandbox in a [setup file](/config/setupfiles): + +```ts [vitest.setup.ts] +Response.prototype.json = async function () { + return JSON.parse(await this.text()) +} +Request.prototype.json = async function () { + return JSON.parse(await this.text()) +} +``` + +- Importing ES modules caches them indefinitely which introduces memory leaks if you have a lot of contexts (test files). There is no API in Node.js that clears that cache. +- Accessing globals [takes longer](https://github.com/nodejs/node/issues/31658) in a sandbox environment. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b 使用此选项时,需要注意这些问题。Vitest 团队无法解决上诉问题。 ::: diff --git a/config/root.md b/config/root.md index 2dc0e9d9..77d0c838 100644 --- a/config/root.md +++ b/config/root.md @@ -8,4 +8,8 @@ outline: deep - **类型:** `string` - **命令行终端:** `-r `, `--root=` +<<<<<<< HEAD 项目的根目录 +======= +Project root. A relative path is resolved against the current working directory, except in [projects](/guide/projects): a project config file resolves it against its own directory, and an inline project resolves it against the root of the config that declares it. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b diff --git a/config/server.md b/config/server.md index 3956947c..cb88ce77 100644 --- a/config/server.md +++ b/config/server.md @@ -19,8 +19,13 @@ outline: deep ### server.deps.external +<<<<<<< HEAD - **类型:** `(string | RegExp)[]` - **默认值:** [`moduleDirectories`](/config/deps#moduledirectories) 目录内的文件 +======= +- **Type:** `(string | RegExp)[]` +- **Default:** files inside [`moduleDirectories`](/config/deps#deps-moduledirectories) +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b 指定不应由 Vite 转换而应直接由引擎处理的模块。这些模块通过原生动态 `import` 导入,会跳过转换和解析阶段。 @@ -43,7 +48,11 @@ export default defineConfig({ 通常,`node_modules` 下的包会被外置化。 ::: tip +<<<<<<< HEAD 如果提供字符串参数,系统会先通过添加 `/node_modules/` 或其他 [`moduleDirectories`](/config/deps#moduledirectories) 路径段进行标准化处理(例如 `'react'` 会被转换为 `/node_modules/react/`),随后将生成的字符串与完整文件路径进行匹配。例如,位于 `packages/some-name` 的 `@company/some-name` 包应指定为 `some-name`,且需将 `packages` 包含在 `deps.moduleDirectories` 配置中。 +======= +If a string is provided, it is first normalized by prefixing the `/node_modules/` or other [`moduleDirectories`](/config/deps#deps-moduledirectories) segments (for example, `'react'` becomes `/node_modules/react/`), and the resulting string is then matched against the full file path. For example, package `@company/some-name` located inside `packages/some-name` should be specified as `some-name`, and `packages` should be included in `deps.moduleDirectories`. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b 如果提供 `RegExp` 正则表达式参数,则会直接与完整文件路径进行匹配。 ::: @@ -58,7 +67,11 @@ export default defineConfig({ 通常情况下,源代码文件会被自动内联处理。 ::: tip +<<<<<<< HEAD 如果提供字符串参数,系统会先通过添加 `/node_modules/` 或其他 [`moduleDirectories`](/config/deps#moduledirectories) 路径段进行标准化处理(例如 `'react'` 会被转换为 `/node_modules/react/`),随后将生成的字符串与完整文件路径进行匹配。例如,位于 `packages/some-name` 的 `@company/some-name` 包应指定为 `some-name`,且需将 `packages` 包含在 `deps.moduleDirectories` 配置中。 +======= +If a string is provided, it is first normalized by prefixing the `/node_modules/` or other [`moduleDirectories`](/config/deps#deps-moduledirectories) segments (for example, `'react'` becomes `/node_modules/react/`), and the resulting string is then matched against the full file path. For example, package `@company/some-name` located inside `packages/some-name` should be specified as `some-name`, and `packages` should be included in `deps.moduleDirectories`. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b 如果提供 `RegExp` 正则表达式参数,则会直接与完整文件路径进行匹配。 ::: diff --git a/config/ui.md b/config/ui.md index f0712892..32d3c28a 100644 --- a/config/ui.md +++ b/config/ui.md @@ -5,11 +5,19 @@ outline: deep # ui +<<<<<<< HEAD - **类型:** `boolean` - **默认值:** `false` - **命令行终端:** `--ui`, `--ui=false` 启用 [UI 模式](/guide/ui). +======= +- **Type:** `boolean | object` +- **Default:** `false` +- **CLI:** `--ui`, `--ui=false` + +Enable [Vitest UI](/guide/ui). Setting `ui: true` is the same as `ui: { enabled: true }`. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ::: warning 此功能需要安装 [`@vitest/ui`](https://npmx.dev/package/@vitest/ui) 包。若尚未安装,Vitest 会在首次运行测试命令时自动安装。 @@ -18,3 +26,37 @@ outline: deep ::: danger 安全警告 请确保你的 UI 模式的服务不暴露在公共网络中。自 Vitest 4.1 起,出于安全考虑,若将 [`api.host`](/config/api) 设置为非 `localhost` 地址,将自动禁用代码保存和测试运行按钮,使 UI 模式变为只读模式。 ::: + +## ui.enabled 5.0.4 {#ui-enabled} + +- **Type:** `boolean` +- **Default:** `false` + +Enable [Vitest UI](/guide/ui). + +## ui.theme 5.0.4 {#ui-theme} + +- **Type:** `'auto' | 'light' | 'dark'` +- **Default:** `'auto'` + +The color theme of Vitest UI and of the [Browser Mode UI](/config/browser/ui). With `'auto'`, the UI follows the system preference and remembers the theme you toggle in the UI. With `'light'` or `'dark'`, the UI opens with this theme on every page load, and the toggle changes it only until the page reloads. + +This option does not enable Vitest UI, so you can set it together with [`browser.ui`](/config/browser/ui): + +```ts +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + ui: { + theme: 'dark', + }, + browser: { + enabled: true, + instances: [ + { browser: 'chromium' }, + ], + } + }, +}) +``` diff --git a/guide/browser/index.md b/guide/browser/index.md index 5009fdc1..2343e95c 100644 --- a/guide/browser/index.md +++ b/guide/browser/index.md @@ -138,7 +138,11 @@ export default defineConfig({ ``` ::: info +<<<<<<< HEAD Vitest 默认分配端口号 `63315` 以避免与开发服务器冲突,允许我们同时并行运行两者。我们可以通过 [`api`](/config/api) 选项来更改这个端口号。 +======= +Vitest assigns port `23315` to avoid conflicts with the development server, allowing you to run both in parallel. You can change that with the [`api`](/config/api) option. The port is bound when the first browser is launched, or on startup if `api` or `ui` is enabled. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b ::: 如果之前未使用过 Vite,请确保已安装框架插件并在配置中指定。有些框架可能需要额外配置才能运行,请查看其 Vite 相关文档以确定。 @@ -356,7 +360,11 @@ Vitest 默认会在开发模式下自动打开浏览器界面,测试会在页 无头模式是浏览器模式下可用的另一个选项。在无头模式下,浏览器在没有用户界面的情况下在后台运行,这对于运行自动化测试非常有用。Vitest 中的 headless 选项可以设置为布尔值以启用或禁用无头模式。 +<<<<<<< HEAD 在使用无头模式时,Vitest 不会自动打开用户界面。如果我们希望继续使用用户界面,同时让测试以 无头模式运行,我们可以安装 [`@vitest/ui`](/guide/ui) 包,并在运行Vitest时传递 `--ui` 参数。 +======= +When using headless mode, Vitest won't open the browser UI automatically. If you want to continue using a UI but have tests run headlessly, you can install the [`@vitest/ui`](/guide/ui) package and pass the `--ui` flag when running Vitest. See [Headless Mode and UI](#headless-mode-and-ui) for what each option controls and how their defaults interact. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b 这是启用无头模式的示例配置: @@ -387,7 +395,31 @@ npx vitest --browser.headless 默认情况下无头模式不可用。我们需要使用 [`playwright`](https://npmx.dev/package/playwright) 或 [`webdriverio`](https://npmx.dev/package/webdriverio) 提供程序来启用此功能。 ::: +<<<<<<< HEAD ## 示例 {#examples} +======= +### Headless Mode and UI {#headless-mode-and-ui} + +Three options decide what you see during a browser mode run: + +- [`browser.headless`](/config/browser/headless) controls whether the browser that runs the tests has a visible window. +- [`browser.ui`](/config/browser/ui) controls whether the browser UI is rendered inside that browser, with the test iframe in the center. +- The top-level `--ui` flag controls whether [Vitest UI](/guide/ui) is served by Vitest and opened in your default browser, separately from the browser that runs the tests. + +`browser.headless` defaults to `true` in CI, and `browser.ui` defaults to `true` when the browser is headed and Vitest is not running in CI. The following table lists common setups: + +| Goal | Settings | Result | +| ------------------------------------------- | ------------------------- | ------------------------------------------------------------------------------------------------------ | +| Develop locally | defaults | The browser window shows the browser UI. | +| Watch tests in a plain browser window | `--browser.ui=false` | The browser window shows only the test iframe at its configured viewport size, without the browser UI. | +| Run in CI | defaults | No window is shown, and results are reported in the terminal. | +| Use Vitest UI with a headless browser | `--ui --browser.headless` | Vitest UI shows the results, and tests run in a headless browser. | +| Use Vitest UI with a visible browser window | `--ui --browser.ui=false` | Vitest UI shows the results, and tests run in a separate browser window without the browser UI. | + +Passing `--ui` alone keeps the browser UI in the headed browser window, so set `--browser.ui=false` to use only Vitest UI. + +## Examples +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b 一般情况下,我们不需要任何依赖来使用浏览器模式: diff --git a/guide/browser/snippets/trace-failure.ansi b/guide/browser/snippets/trace-failure.ansi new file mode 100644 index 00000000..eebc87e5 --- /dev/null +++ b/guide/browser/snippets/trace-failure.ansi @@ -0,0 +1,24 @@ + FAIL src/sign-in.test.ts > signs in +Error: expect(element).toHaveTextContent() + +Expected element to have text content: + Welcome +Received: + Sign in + + ❯ src/sign-in.test.ts:11:51 +  9| }) +  10| await page.getByRole('button', { name: 'Sign in' }).click() +  11| await expect.element(page.getByRole('heading')).toHaveTextContent('Welcome') +  | ^ +  12| }) + +Caused by: Error: Matcher did not succeed in time. + +Trace: + step locator start duration location + fill form -  +1ms 58ms src/sign-in.test.ts:6:14 + ├── fill getByLabel('Email')  +2ms 31ms src/sign-in.test.ts:7:40 + └── fill getByLabel('Password')  +34ms 24ms src/sign-in.test.ts:8:43 + click getByRole('button', { name: 'Sign in' }) +61ms 18ms src/sign-in.test.ts:10:55 + toHaveTextContent [ERROR] getByRole('heading')  +80ms 1003ms src/sign-in.test.ts:11:16 diff --git a/guide/browser/trace-view.md b/guide/browser/trace-view.md index 8a6dc675..69037e99 100644 --- a/guide/browser/trace-view.md +++ b/guide/browser/trace-view.md @@ -1,30 +1,28 @@ -# Trace View 5.0.0 +# Trace View 5.0.0 +<<<<<<< HEAD `browser.traceView` records browser interactions as DOM snapshots and lets you replay them step by step in Vitest's built-in trace viewer. It is useful when the live browser view is not enough: you can inspect earlier tests, failed retries, screenshots, assertions, and user actions after the browser has already moved on. +======= +Trace view records the steps of a browser test, such as actions, assertions, and marks, together with DOM snapshots of the page. After the test finishes, you can replay them in Vitest's trace viewer to see what happened and what the page looked like. +>>>>>>> cc54eaf52775a7aaede1f9affab7dec045c6c71b -Trace view is additive to the current browser testing workflow. Enabling it does not force a single debugging mode. You can use it with the normal local browser UI, with a headless browser and Vitest UI, or with the HTML reporter in CI. - -::: tip Trace view, browser UI, and HTML reports - -The normal local browser mode opens the [browser UI](/config/browser/ui), where tests run in a visible iframe. This is useful while developing, but the iframe only shows the current browser state. When another test runs, the previous rendered state is gone. - -`browser.traceView` keeps a replayable record for each test. In local browser UI mode, the trace viewer appears alongside the existing live view so you can keep using the browser UI while also inspecting recorded steps. - -For static output, add the [HTML reporter](/guide/reporters#html-reporter). The same trace viewer can then be opened from the generated report, which is useful for run-mode and CI failures. +By default, Vitest records only failed tests, prints their steps in the terminal, and keeps a snapshot of the page when each test failed. +::: tip +For Playwright traces recorded with [`browser.trace`](/config/browser/trace), see [Playwright Traces](./playwright-traces). ::: -::: details Looking for Playwright traces? - -This page now documents Vitest's built-in `browser.traceView` feature. The previous `browser.trace` guide for Playwright traces moved to [Playwright Traces](./playwright-traces). +## Recording Modes -::: +[`browser.traceView`](/config/browser/traceview) decides which tests Vitest records: -## Quick Start +- `'failure-only'` records only failed tests. This is the default. Each trace keeps every step and a snapshot of the page when the test failed. +- `true` or `'on'` records every test with a snapshot at each step. Vitest UI also streams steps as the test runs, so you can inspect them before the test finishes. Long-running actions, `expect.element(...)` assertions, and callback `page.mark()` entries first appear as in-progress steps, then update with their final status and duration. +- `false` or `'off'` turns trace view off. -Enable trace view with the [`browser.traceView`](/config/browser/traceview) option: +For example, to record every test: ::: code-group @@ -46,68 +44,52 @@ vitest --browser.traceView ::: -When `browser.traceView` is enabled, tests with recorded traces can be opened in the trace viewer from the [browser UI](/config/browser/ui), [Vitest UI](/guide/ui), and [HTML reporter](/guide/reporters#html-reporter). +## Failure Output -With the HTML reporter enabled, the [Vitest VS Code extension](https://github.com/vitest-dev/vscode#trace-view) adds an **Open Trace View** action to tests in the Testing view and editor gutter. +When a test fails, the error in the terminal is followed by a timeline of its recorded steps: -The viewer has two resizable panes: +<<< ./snippets/trace-failure.ansi [Error report] -- **Step list** (left) — every recorded action, assertion, mark, and lifecycle entry, with name, timing, selector, and source location. Failed actions and assertions are highlighted in red. -- **DOM snapshot** (right) — a reconstruction of the page at the selected step. The interacted element is highlighted in blue. +The timeline lists the same steps as the trace viewer, truncated for long tests and without DOM snapshots. The trace viewer shows every step and the page when the test failed. -Selecting a step also opens its source location in the Editor tab when that location is available. +## Opening Traces -Vitest UI trace viewer showing step list and DOM snapshot -Vitest UI trace viewer showing step list and DOM snapshot +The trace viewer is available wherever you already look at test results: -Example replay uses [Vuetify's](https://github.com/vuetifyjs/vuetify) `VDateInput` component. +| Where | Command | What you see | +| ----------------------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------ | +| [Browser UI](/config/browser/ui) | `vitest` | The trace viewer next to the live view, which only shows the current page | +| [Vitest UI](/guide/ui) | `vitest --ui --browser.headless` | The trace viewer in each test's report, with tests running in a headless browser | +| [HTML reporter](/guide/reporters#html-reporter) | `vitest run --reporter=html` | The trace viewer in the generated report, which suits CI | +| Terminal | `vitest run` | The step timeline of each failure, as described in [Failure Output](#failure-output) | -## Common Setups +To use Vitest UI with a visible browser window instead, see [Headless Mode and UI](/guide/browser/#headless-mode-and-ui). - +With the HTML reporter enabled, the [Vitest VS Code extension](https://github.com/vitest-dev/vscode#trace-view) adds an **Open Trace View** action to tests in the Testing view and editor gutter. -`browser.traceView` records traces. The browser mode, UI, and reporter options determine where you inspect them. +## Trace Viewer -| Goal | Configuration | Result | -| --------------------------------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -| Add trace replay to the normal local browser UI | `vitest --browser.traceView` | Uses the default local headed browser UI and adds trace replay for recorded tests. | -| Debug locally with a headless browser | `vitest --browser.traceView --browser.headless --ui` | The browser runs headless, while Vitest UI shows recorded trace steps and snapshots. | -| Debug locally with a visible browser window and Vitest UI | `vitest --browser.traceView --browser.headless=false --browser.ui=false --ui` | Vitest UI shows recorded trace steps and snapshots, while tests run in a separate headed browser window. | -| Generate a static report for CI or run mode | `vitest run --browser.traceView --reporter=html` | The HTML report includes the trace viewer for recorded tests. | +The viewer has two resizable panes: -## Relation to Playwright Traces +- **Step list** (left) shows every recorded action, assertion, mark, and lifecycle entry, with its name, timing, selector, and source location. Failed actions and assertions are highlighted in red. +- **DOM snapshot** (right) shows the page at the selected step, with the interacted element highlighted in blue. When only failed tests are recorded, the snapshot is taken at the end of the test. -`browser.traceView` and [`browser.trace`](/config/browser/trace) are independent features: +Selecting a step also opens its source location in the Editor tab when that location is available. -| | `browser.traceView` | `browser.trace` | -| ---------------------- | -------------------------------------------------------- | ---------------------------------------------- | -| Provider support | All providers (playwright, webdriverio, preview) | Playwright only | -| Viewer | Browser UI / Vitest UI / HTML reporter | Playwright Trace Viewer / trace.playwright.dev | -| Format | [rrweb](https://github.com/rrweb-io/rrweb) DOM snapshots | Playwright `.trace.zip` | -| Requires external tool | No | Yes (`npx playwright show-trace`) | +Vitest UI trace viewer showing step list and DOM snapshot +Vitest UI trace viewer showing step list and DOM snapshot -You can enable both at the same time. See [Playwright Traces](./playwright-traces) for the `browser.trace` workflow. +Example replay uses [Vuetify's](https://github.com/vuetifyjs/vuetify) `VDateInput` component. ## Recorded Steps -Trace entries are recorded automatically for: +Vitest records a step for: - `expect.element(...)` assertions -- Interactive actions like `click`, `dblClick`, `tripleClick`, `fill`, `clear`, `type`, `hover`, `selectOptions`, `upload`, `dragAndDrop`, `tab`, `keyboard`, `wheel`, and screenshots -- Test runner lifecycle event (e.g. `vitest:onAfterRetryTask` is recorded after each test and retry run) - -Each entry captures the DOM state at that point, along with timing information, the selector, and the source location that triggered it. +- Interactive actions such as `click`, `dblClick`, `tripleClick`, `fill`, `clear`, `type`, `hover`, `selectOptions`, `upload`, `dragAndDrop`, `tab`, `keyboard`, `wheel`, and screenshots +- Test runner lifecycle events, such as `vitest:onAfterRetryTask` after each test and retry run -In Vitest UI, trace entries are streamed as the test runs, so you can inspect recorded steps before the test finishes. Long-running actions, `expect.element(...)` assertions, and callback `page.mark()` entries appear as in-progress steps first, then update with their final status and duration. +Each step records its timing, selector, and the source location that triggered it. ## Custom Trace Entries @@ -121,7 +103,7 @@ await page.mark('content rendered') await page.getByRole('button', { name: 'Sign in' }).mark('sign in button') ``` -You can also pass a callback to `page.mark()`. Note that grouping is not currently supported — each inner action is recorded individually, and the mark entry appears at the end: +You can also pass a callback to `page.mark()` to group steps. The mark entry spans the callback, and the actions inside it are nested under the mark in the step list: ```ts await page.mark('sign in flow', async () => { @@ -149,15 +131,19 @@ test('shows button', async () => { ## Retries and Repeats -Each attempt — retry or repeat — is recorded as a separate trace. When a test has multiple attempts, the viewer opens the most recent one by default. You can switch between attempts in the Report tab. +Each retry or repeat of a test is recorded as a separate trace. The viewer opens the most recent attempt, and you can switch between attempts in the Report tab. + +## Accessibility Tree 5.1.0 {#accessibility-tree} -## Snapshot Fidelity +Trace view also captures the page's accessibility tree with each snapshot. The view selector in the trace viewer header switches the snapshot pane between **DOM** and **Aria**. The Aria view lists the roles, accessible names, and states that role-based locators such as `getByRole()` see, in the same format as [aria snapshots](/guide/browser/aria-snapshots). Set [`traceView.ariaSnapshot`](/config/browser/traceview#traceview-ariasnapshot) to `false` to disable it. -By default, trace view captures the DOM tree, attributes, form values, same-origin readable CSS, element scroll positions, viewport size, and window scroll position. Images and canvas pixels are not inlined by default. +## What Snapshots Capture -Stylesheets are captured through the browser's CSSOM. Readable `