diff --git a/.vitepress/config.ts b/.vitepress/config.ts index 16617faa..e4236f3f 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 }) => { }, }, }, +======= +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 }, // 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/.vitepress/sponsors.ts b/.vitepress/sponsors.ts index 0c9ca091..da653f41 100644 --- a/.vitepress/sponsors.ts +++ b/.vitepress/sponsors.ts @@ -31,22 +31,12 @@ export const sponsors: SponsorTier[] = [ url: 'https://bolt.new', img: '/bolt.svg', }, - { - name: 'Latitude', - url: 'https://latitude.so/', - img: '/latitude.svg', - }, ], }, { tier: '金牌赞助商', size: 'medium', items: [ - { - name: 'vital', - url: 'https://vital.io/', - img: '/vital.svg', - }, { name: 'Mailmeteor', url: 'https://mailmeteor.com/', @@ -62,11 +52,6 @@ export const sponsors: SponsorTier[] = [ url: 'https://kraken.tech/', img: '/kraken.svg', }, - { - name: 'Aerius Ventilation', - url: 'https://aerius.se/', - img: '/aerius.png', - }, { name: 'TestMu AI', url: 'https://www.testmuai.com/?utm_medium=sponsor&utm_source=vitest-dev', diff --git a/.vitepress/theme/styles.css b/.vitepress/theme/styles.css index 776336c2..a7ed3738 100644 --- a/.vitepress/theme/styles.css +++ b/.vitepress/theme/styles.css @@ -25,6 +25,17 @@ --vp-code-color: #007d38; } +/* .ansi code blocks color the background of inverse and background-colored text */ +html:not(.dark) .shiki span[style*='--shiki-light-bg'], +html[data-theme='light'] .shiki span[style*='--shiki-light-bg'] { + background-color: var(--shiki-light-bg); +} + +html.dark:not([data-theme]) .shiki span[style*='--shiki-dark-bg'], +html[data-theme='dark'] .shiki span[style*='--shiki-dark-bg'] { + background-color: var(--shiki-dark-bg); +} + .highlighted-word { background-color: var(--vp-code-line-highlight-color); transition: background-color 0.5s; 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..8eb29bb4 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ::: ## 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..025bb4ba 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). +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ### 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..6d0af0fe 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 `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..054678e5 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ```ts import { expectTypeOf } from 'vitest' diff --git a/api/expect.md b/api/expect.md index fea1e48c..47e582ed 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. The message also names the step in the [trace timeline](/config/experimental#experimental-tracetimeline). + +```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..398de672 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: +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ```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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 如果需要模拟类的构造实例,考虑改用 `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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 如果 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 注意:如果 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 当 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 */ 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`. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 在结果出来前,对应的 `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). +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ```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). +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ```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: +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ```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..85566678 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ```ts import { assert, test } from 'vitest' diff --git a/api/vi.md b/api/vi.md index 87b8a891..430243da 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`. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 例如,我们有这样的文件结构: ``` - __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' +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ```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). +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ::: 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 **示例:** @@ -1342,7 +1381,18 @@ test('Element exists in a DOM', async () => { }) ``` +<<<<<<< HEAD 一旦通过 `vi.useFakeTimers` 启用假计时器,`vi.waitFor` 将在每次轮询时自动调用 `vi.advanceTimersByTime(interval)` 推进时间。 +======= +The `message` option describes what the callback waits for. If the wait fails, Vitest adds the message to the start of the error. The message also names the step in the [trace timeline](/config/experimental#experimental-tracetimeline). + +```ts +await vi.waitFor(() => server.ping(), { message: 'server started' }) +// Error: server started: connect ECONNREFUSED +``` + +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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ### vi.waitUntil {#vi-waituntil} @@ -1369,7 +1419,18 @@ test('Element render correctly', async () => { }) ``` +<<<<<<< HEAD 如果使用了 `vi.useFakeTimers`, `vi.waitFor` 会在每次检查回调中自动调用 `vi.advanceTimersByTime(interval)`。 +======= +Like `vi.waitFor`, `vi.waitUntil` accepts a `message` option that prefixes the error and names the step in the [trace timeline](/config/experimental#experimental-tracetimeline). + +```ts +await vi.waitUntil(() => db.users.findByName('alice'), { message: 'user saved' }) +// Error: user saved: Timed out in waitUntil! +``` + +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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ### vi.hoisted {#vi-hoisted} diff --git a/blog/vitest-5-1.md b/blog/vitest-5-1.md new file mode 100644 index 00000000..2df6c1da --- /dev/null +++ b/blog/vitest-5-1.md @@ -0,0 +1,302 @@ +--- +title: Vitest 5.1 is out! +author: + name: The Vitest Team +date: 2026-11-02 +sidebar: false +head: + - - meta + - property: og:type + content: website + - - meta + - property: og:title + content: Announcing Vitest 5.1 + - - meta + - property: og:image + content: https://vitest.dev/og-vitest-5-1.jpg + - - meta + - property: og:url + content: https://vitest.dev/blog/vitest-5-1 + - - meta + - property: og:description + content: Vitest 5.1 Release Announcement + - - meta + - name: twitter:card + content: summary_large_image +--- + +# Vitest 5.1 is out! + +_November 2, 2026_ + +![Vitest 5.1 Announcement Cover Image](/og-vitest-5-1.jpg) + +## The next Vitest minor is here + +Today, we are thrilled to announce Vitest 5.1! + +Quick links: + +- [Docs](/) +- Translations: [简体中文](https://cn.vitest.dev/) +- [GitHub Changelog](https://github.com/vitest-dev/vitest/releases/tag/v5.1.0) + +If you've not used Vitest before, we suggest reading the [Getting Started](/guide/) and [Features](/guide/features) guides first. + +We extend our gratitude to the over [840 contributors to Vitest Core](https://github.com/vitest-dev/vitest/graphs/contributors) and to the maintainers and contributors of Vitest integrations, tools, and translations who have helped us develop this new release. We encourage you to get involved and help us improve Vitest for the entire ecosystem. Learn more at our [Contributing Guide](https://github.com/vitest-dev/vitest/blob/main/CONTRIBUTING.md). + +To get started, we suggest helping [triage issues](https://github.com/vitest-dev/vitest/issues), [review PRs](https://github.com/vitest-dev/vitest/pulls), send failing tests PRs based on open issues, and support others in [Discussions](https://github.com/vitest-dev/vitest/discussions) and Vitest Land's [help forum](https://discord.com/channels/917386801235247114/1057959614160851024). If you'd like to talk to us, join our [Discord community](http://chat.vitest.dev/) and say hi on the [#contributing channel](https://discord.com/channels/917386801235247114/1057959614160851024). + +For the latest news about the Vitest ecosystem and Vitest core, follow us on [Bluesky](https://bsky.app/profile/vitest.dev) or [Mastodon](https://webtoo.ls/@vitest). + +To stay updated, keep an eye on the [VoidZero blog](https://voidzero.dev/blog) and subscribe to the [newsletter](https://voidzero.dev/newsletter). + +## Run Only Affected Tests with `--since-last-run` + +The [`--changed`](/config/changed) flag compares your files with a Git reference. It needs a repository, and a change stops being "changed" as soon as you commit it. The new experimental [`--since-last-run`](/guide/cli#since-last-run) flag compares the files with the last run of each test instead: + +```sh +vitest run --since-last-run +``` + +The first run with the flag runs every test and records the files each test loaded, including dynamic imports, mocks, setup files, and snapshot files. Every later run checks the modification time of those files and runs only the tests whose files changed or were deleted since their last run. A test file that failed in its last run also runs again until it passes. + +Each run updates the records, so the next run selects only the tests affected by the edits made in between. Git is not involved, so the flag works the same in a fresh container, in a temporary worktree, or in a directory that is not a repository. This makes it a good fit for AI coding agents that run tests after every edit: + +```md [AGENTS.md] +- After a change, run `vitest run --since-last-run` to test only what the change affects. +- Before you finish, run `vitest run` once for the whole suite. +``` + +The flag is built on [`experimental.recordDependencies`](/config/experimental#experimental-recorddependencies) and the new `mtime` [VCS provider](/config/experimental#experimental-vcsprovider). Custom VCS providers now also receive a `resolver` that returns every file the test files of the run depend on. See [Running Only Affected Tests](/guide/recipes/affected-tests) to choose between `--changed` and `--since-last-run`. + +## Interactive Snapshot Review + +When snapshots fail in watch mode, the `u` key updates all of them at once. Vitest 5.1 adds an interactive review, inspired by [Insta's `review` command](https://insta.rs/docs/cli/#review), so you can check each change before you accept it. Press `i`, and Vitest shows the failed snapshots one at a time: + +<<< ../guide/snippets/snapshot-review.ansi + +Press `a` to accept the new snapshot or `r` to keep the old one. After the last snapshot, Vitest writes the accepted snapshots and reruns the test files that contain them. The review covers inline snapshots, `.snap` files, and [file snapshots](/guide/snapshot#file-snapshots), including [ARIA snapshots](/guide/snapshot#aria-snapshots) in Browser Mode. + +## Trace View on Failure by Default + +[Trace View](/guide/browser/trace-view), introduced in Vitest 5.0 as an experimental feature, is now stable and enabled by default in a new [`'failure-only'`](/config/browser/traceview#traceview-mode) mode. In this mode, Vitest keeps the trace only for tests that fail. A passing test records its steps in the browser, but it never takes a DOM snapshot or sends anything to the server, so the mode adds no measurable cost to passing tests. + +When a browser test fails, the error report now ends with the steps that led to the failure: + +<<< ../guide/browser/snippets/trace-failure.ansi [Error report] + +The same trace, with a snapshot of the page at the moment of failure, is available in the browser UI, in Vitest UI, and in the [HTML reporter](/guide/reporters#html-reporter). To record every test with a snapshot at each step, set the mode to `'on'`: + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + browser: { + traceView: 'on', // 'failure-only' (default) | 'on' | 'off' + }, + }, +}) +``` + +`browser.traceView.enabled` is deprecated in favor of `browser.traceView.mode`. + +The trace viewer also captures the [accessibility tree](/guide/browser/trace-view#accessibility-tree) of the page with each snapshot. A selector in the viewer header switches between the **DOM** and **Aria** views. The Aria view shows the roles and accessible names that locators like `getByRole()` match against, in the same format as [ARIA snapshots](/guide/browser/aria-snapshots). + +## Trace Timeline in Node Tests + +The step timeline is not limited to Browser Mode. With the new experimental [`experimental.traceTimeline`](/config/experimental#experimental-tracetimeline) option, Vitest records the `expect.poll`, `vi.waitFor`, and `vi.waitUntil` steps of each test and prints them under the error when the test fails. This shows what the test waited on and how long each wait took, which helps with flaky tests that wait on servers, queues, or file watchers. + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + experimental: { + traceTimeline: true, + }, + }, +}) +``` + +<<< ../config/snippets/trace-timeline-failure.ansi [Error report] + +Vitest keeps the steps only for failed attempts, so the option adds almost no cost to passing tests. + +`vi.waitFor` and `vi.waitUntil` now accept a `message` option, like `expect.poll` already does. Vitest adds the message to the start of the error, and the message names the step in the timeline, as with `user saved (vi.waitUntil)` in the report above: + +```ts +await vi.waitFor(() => server.ping(), { message: 'server started' }) +// Error: server started: connect ECONNREFUSED +``` + +## Rerun Failures with `--last-failed` + +Watch mode can already rerun failed tests with the `f` key. The new [`--last-failed`](/guide/cli#last-failed) flag does the same across separate processes: it reads the failures of the last run from the [results cache](/config/cache) and runs only those tests. + +```sh +vitest run --last-failed + +# combine with other filters +vitest run --last-failed -t login +``` + +Vitest selects the tests by their full names, so adding or moving other tests in the file does not change the selection. If a file failed outside of a test, for example in a `beforeAll` hook or during collection, the whole file runs. Custom integrations can use the same filter through the new [`TestSpecification.testNames`](/api/advanced/test-specification#testnames) property. + +## Faster Module Mocking in Browser Mode + +To mock a module in Browser Mode, Vitest intercepted the network requests of the page (with Playwright's `page.route`, or a Vite plugin for the other providers) and served the mocked code instead. Every request in the page paid for the interception, and the cost grew with the number of modules a test file imports. + +Vitest 5.1 serves mocks through an [import map](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/script/type/importmap) instead. When a test file calls `vi.mock`, Vitest adds an import map rule that points the module URL to its mocked version. The browser loads the mocked module itself, so no request is intercepted, and the HTTP cache stays enabled. + +On a [public benchmark](https://github.com/ubugeeei-reproductions/js-test-runner-competition) with 500 test files, 962 modules, and 1,883 mocks per suite (Chromium, 9 workers, median of 3 runs): + +| Suite | Vitest 5.0.3 | Vitest 5.1 | Change | +| -------- | -----------: | ---------: | -----: | +| No mocks | 31.7s | 26.6s | −16% | +| Mocks | 56.4s | 27.2s | −52% | +| Mixed | 44.0s | 27.1s | −38% | + +The new [`browser.importMapMocks`](/config/browser/importmapmocks) option is experimental. It is enabled when the browser supports multiple import maps per document (Chromium 133+, Safari 18.4+, and Firefox 150+ with the Playwright provider). In other browsers, and when [`isolate`](/config/isolate) is disabled, Vitest intercepts requests as before. + +An import map cannot change a module that the page already imported, so Vitest now reports an error when a module was imported before `vi.mock` registered it, instead of silently using the original module. A mock factory can also only provide the exports that the original module declares. If this breaks your tests, set `browser.importMapMocks` to `false` and [let us know](https://github.com/vitest-dev/vitest/issues). + +## Pointer API + +The new experimental [`userEvent.pointer`](/api/browser/interactivity#userevent-pointer) method performs a sequence of low-level pointer actions. It uses the [user-event `pointer` syntax](https://testing-library.com/docs/user-event/pointer), so you can press, move, and release the pointer separately, for example to drag an element to an exact position: + +```ts +import { test } from 'vitest' +import { page, userEvent } from 'vitest/browser' + +test('drags the card into the column', async () => { + const card = page.getByRole('listitem', { name: 'Fix the login form' }) + const column = page.getByRole('list', { name: 'Done' }) + + await userEvent.pointer([ + { target: card, keys: '[MouseLeft>]' }, + { target: column, coords: { x: 10, y: 10 }, keys: '[/MouseLeft]' }, + ]) +}) +``` + +## Fake Timers for `node:timers` + +Fake timers replace the global `setTimeout` and similar functions, but code that imports them from `node:timers` or `node:timers/promises` kept the real timers. With the new [`fakeNodeBuiltins`](/config/faketimers#faketimers-fakenodebuiltins) option, Vitest also fakes these modules, including named imports and `require` calls: + +```ts +import { setTimeout as sleep } from 'node:timers/promises' +import { expect, test, vi } from 'vitest' + +test('resolves after the delay', async () => { + vi.useFakeTimers({ fakeNodeBuiltins: true }) + + const resolved = vi.fn() + sleep(1000).then(resolved) + + await vi.advanceTimersByTimeAsync(1000) + expect(resolved).toHaveBeenCalled() +}) +``` + +::: tip +`fakeNodeBuiltins` is disabled by default. We plan to enable it by default in the next major version. To prepare for it, set [`fakeTimers.fakeNodeBuiltins`](/config/faketimers#faketimers-fakenodebuiltins) to `true` in your config now. +::: + +[`vi.setSystemTime`](/api/vi#vi-setsystemtime) now also accepts a `Temporal.Instant` or a `Temporal.ZonedDateTime`. A `ZonedDateTime` sets the mocked time, but it does not change the time zone of the environment: + +```ts +vi.setSystemTime(Temporal.Instant.from('2026-11-02T09:00:00Z')) +``` + +In Browser Mode, locator actions like `click` and `fill`, and `expect.element`, now advance fake timers while they wait for the element, the same way `expect.poll` and `vi.waitFor` do. Before, an action timed out if the element appeared only after a faked `setTimeout`. + +## Results of Each Attempt + +A test runs more than once when it has [`retry`](/config/retry) or [`repeats`](/api/test#repeats). Until now, `TestCase.result()` reported only the final state and merged the errors of all runs. The new [`TestCase.attempts()`](/api/advanced/test-case#attempts) method returns the state, errors, and timing of each run separately, so a reporter can find tests that passed only after a retry: + +```ts [flaky-reporter.ts] +import type { Reporter } from 'vitest/node' + +export default { + onTestCaseResult(testCase) { + const attempts = testCase.attempts() + + if (testCase.result().state === 'passed' && attempts.length > 1) { + console.warn(`${testCase.fullName} passed after ${attempts.length} attempts`) + } + }, +} satisfies Reporter +``` + +The default reporter also uses this information. A test that fails in several attempts now prints one header with the attempts, instead of one identical header per attempt: + +``` + FAIL basic.test.js > always fails (initial run, 1st retry, 2nd retry) +AssertionError: expected 1 to be 2 // Object.is equality +``` + +## Coverage Improvements + +The HTML coverage report has new light and dark themes that match the Vitest style. The report follows your OS color preference, and the theme picker in the report keeps your choice. Inside Vitest UI, the report uses the theme of the UI. To keep the original Istanbul style, set `theme: 'legacy'` in the [reporter options](/guide/coverage#html-report-theme): + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + coverage: { + reporter: [['html', { theme: 'legacy' }]], // 'light' | 'dark' | 'legacy' + }, + }, +}) +``` + +
+ HTML coverage report in light theme + HTML coverage report in dark theme + +The HTML coverage report with the new theme. +
+ +The `istanbul` provider is much faster. The [`@vitest/istanbuljs`](https://github.com/vitest-dev/istanbuljs) packages no longer use Babel: they parse the code with [Oxc](https://oxc.rs) through Rolldown and insert the coverage counters with `magic-string`. This makes instrumentation 5 to 10 times faster and uses less memory. For example, the instrumentation of the 479 source files of the Vitest repository went from 3.7s to 0.38s. The instrumented code also no longer includes the input source maps, which reduces the code sent to the workers by about 26%. + +The `v8` provider has a new [`coverage.ignoreNode`](/config/coverage#coverage-ignorenode) option. It ignores code by its AST node, which is useful for code that a compiler or a plugin generates and that cannot have an [ignore hint](/guide/coverage#ignoring-code), like the cache branches that the React Compiler adds to every component: + +```ts [vitest.config.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', + }, + }, +}) +``` + +## Performance Improvements + +- **Reused workers with `isolate: false`.** Workers now cache the resolution of setup files and their mocks, and they no longer request a new snapshot of warm modules for every test file. On a [benchmark](https://github.com/vitest-dev/benchmarks) with 800 test files, the resolution cache saves about 10%, and the warm modules change saves up to 18% when every test file imports a large shared module tree. +- **Typed arrays in `toEqual`.** Vitest compares typed arrays in one loop. A `toEqual` of two 2 MiB arrays went from about 1.5s to 125ms. +- **Trace View in Firefox.** Vitest resolves only the first usable stack frame of each step. With the `'on'` mode, a test with 200 `expect.element` calls went from 5.7s to 0.33s in Firefox. +- **Browser Mode under load.** The WebSocket server now handles messages of all browsers in turn, so a busy browser no longer delays the actions of other browsers until they time out. + +## Other Changes + +- The default port in Browser Mode is now `23315` instead of `63315`. Windows can reserve the old port when Hyper-V, WSL2, or Docker Desktop runs, and Vitest then failed to start. If your setup depends on the old port, set [`api.port`](/config/api). +- [`api.port: 0`](/config/api) now lets the OS choose a free port. This requires Vite 8.2.1 or newer. +- If a browser test file cannot connect to the server, the run now fails with an error instead of waiting forever. + +The complete list of changes is at the [Vitest 5.1 Changelog](https://github.com/vitest-dev/vitest/releases/tag/v5.1.0). + +## Acknowledgments + +Vitest 5.1 is the result of countless hours of work by the [Vitest team](/team) and our contributors. None of it would be possible without the individuals and companies that sponsor Vitest. [Vladimir](https://github.com/sheremet-va) and [Hiroshi](https://github.com/hi-ogawa) work on Vite and Vitest full-time at [VoidZero](https://voidzero.dev), and [Chromatic](https://www.chromatic.com/) gives [Ari](https://github.com/ariperkkio) the time to keep pushing Vitest forward. Vitest also sponsors [Raul](https://github.com/macarie)'s work as a member of the Vitest team through the [Cloudflare](https://opencollective.com/cloudflare) fund. A big thank you to everyone supporting us through [GitHub Sponsors](https://github.com/sponsors/vitest-dev) and [Open Collective](https://opencollective.com/vitest). diff --git a/config/api.md b/config/api.md index b3a0194e..964a4e58 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 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..4741f6be 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: +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 - `browser` (浏览器名称) - [`headless`](/config/browser/headless) diff --git a/config/browser/traceview.md b/config/browser/traceview.md index 3d5a2e95..2c0880da 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'`: +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ```ts export default defineConfig({ @@ -21,35 +31,52 @@ export default defineConfig({ }) ``` +<<<<<<< HEAD 使用对象形式可启用额外的快照保真度选项: +======= +To combine a mode with the options below, use an object: +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ```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} +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 -## 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ## 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ## 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 diff --git a/config/browser/ui.md b/config/browser/ui.md index af1fc3d7..67d8f821 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 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..bda12287 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=` +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 处理代码覆盖率结果时使用的并发限制。 diff --git a/config/deps.md b/config/deps.md index 70e7c073..43f2980a 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ```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..76eb74fa 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` +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ```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: +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ```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 +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 return [] }, }, @@ -375,6 +432,82 @@ 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.traceTimeline 5.1.0 {#experimental-tracetimeline} + +- **Type:** `boolean` +- **Default:** `false` + +Records the [`expect.poll`](/api/expect#poll), [`vi.waitFor`](/api/vi#vi-waitfor), and [`vi.waitUntil`](/api/vi#vi-waituntil) steps of each test, and prints them as a timeline under the error when the test fails. This shows what the test waited on before it failed and how long each wait took, which helps with flaky tests that wait on servers, queues, or file watchers. + +```ts [vitest.config.ts] +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + experimental: { + traceTimeline: true, + }, + }, +}) +``` + +<<< ./snippets/trace-timeline-failure.ansi [Error report] + +Steps are reported only for failed attempts, so the option adds almost no overhead to passing tests. + +Step locations are resolved like error stacks, so a step in a helper file points at that file, and a step in a [`vi.defineHelper`](/api/vi#vi-definehelper) helper points at the line that called the helper. + +The `message` option of `expect.poll`, `vi.waitFor`, and `vi.waitUntil` names the step, as with `user saved (vi.waitUntil)` in the report above. + +This option has no effect in browser mode, which prints the same timeline with [`browser.traceView`](/config/browser/traceview). + +::: warning +`vi.waitFor`, `vi.waitUntil`, and polls from the global `expect` are attributed to the test that is currently running. When several tests run at the same time with [`test.concurrent`](/api/test#test-concurrent), `vi.waitFor` and `vi.waitUntil` are not recorded, and polls from the global `expect` can appear in another test's timeline. Use `expect` from the [test context](/guide/test-context) in concurrent tests to record polls for the right test. +::: + ## experimental.nodeLoader 4.1.0 {#experimental-nodeloader} - **类型:** `boolean` diff --git a/config/faketimers.md b/config/faketimers.md index d784ba0c..71311824 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ## 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.1.0 {#faketimers-fakenodebuiltins} + +- **Type:** `boolean` +- **Default:** `false` + +Mock timer APIs imported from `node:timers` and `node:timers/promises`. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 diff --git a/config/maxworkers.md b/config/maxworkers.md index 8dade68e..d5f1f6ad 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 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..662b0a5e 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 使用此选项时,需要注意这些问题。Vitest 团队无法解决上诉问题。 ::: diff --git a/config/root.md b/config/root.md index 2dc0e9d9..8f73fd85 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 diff --git a/config/server.md b/config/server.md index 3956947c..f9d176c2 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) +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 指定不应由 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`. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 如果提供 `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`. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 如果提供 `RegExp` 正则表达式参数,则会直接与完整文件路径进行匹配。 ::: diff --git a/config/snippets/trace-timeline-failure.ansi b/config/snippets/trace-timeline-failure.ansi new file mode 100644 index 00000000..2c5a6366 --- /dev/null +++ b/config/snippets/trace-timeline-failure.ansi @@ -0,0 +1,23 @@ + FAIL src/search.test.ts > finds new user in search +AssertionError: expected [] to have a length of 1 but got +0 + +- Expected ++ Received + +- 1 ++ 0 + + ❯ src/search.test.ts:8:50 +  6| await api.post('/users', { name: 'alice' }) +  7| await vi.waitUntil(() => db.users.findByName('alice'), { message: 'user saved' }) +  8| await expect.poll(() => search.query('alice')).toHaveLength(1) +  | ^ +  9| }) + +Caused by: Error: Matcher did not succeed in time. + +Trace: + step start duration location + vi.waitFor() +2ms 120ms src/search.test.ts:5:12 + user saved (vi.waitUntil) +171ms 42ms src/search.test.ts:7:12 + expect.poll().toHaveLength() +214ms 1001ms src/search.test.ts:8:50 diff --git a/config/typecheck.md b/config/typecheck.md index 56121ca9..8aeb949b 100644 --- a/config/typecheck.md +++ b/config/typecheck.md @@ -15,6 +15,14 @@ outline: deep 在常规测试的同时启用类型检查。 +::: tip +When typechecking is enabled, Vitest prints a warning that this is an experimental feature. Set the `VITEST_DISABLE_EXPERIMENTAL_LOG` environment variable to hide it: + +```bash +VITEST_DISABLE_EXPERIMENTAL_LOG=true vitest --typecheck +``` +::: + ## typecheck.only {#typecheck-only} - **类型:** `boolean` diff --git a/config/ui.md b/config/ui.md index f0712892..2c55afc2 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 }`. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ::: 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..ff06ecee 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 ::: 如果之前未使用过 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 这是启用无头模式的示例配置: @@ -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 +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 一般情况下,我们不需要任何依赖来使用浏览器模式: 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..1fb0ab16 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. +>>>>>>> 88678699d9e33ecbfb4173873cba54a44cba4255 -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). - -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. - -The viewer has two resizable panes: +## Failure Output -- **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. +When a test fails, the error in the terminal is followed by a timeline of its recorded steps: -Selecting a step also opens its source location in the Editor tab when that location is available. +<<< ./snippets/trace-failure.ansi [Error report] -Vitest UI trace viewer showing step list and DOM snapshot -Vitest UI trace viewer showing step list and DOM snapshot +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. -Example replay uses [Vuetify's](https://github.com/vuetifyjs/vuetify) `VDateInput` component. +## Opening Traces -## Common Setups +The trace viewer is available wherever you already look at test results: - - -`browser.traceView` records traces. The browser mode, UI, and reporter options determine where you inspect them. +With the HTML reporter enabled, the [Vitest VS Code extension](https://github.com/vitest-dev/vscode#trace-view) adds an **Open Trace View** action to tests in the Testing view and editor gutter. -| Goal | Configuration | Result | -| --------------------------------------------------------- | ----------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------- | -| Add trace replay to the normal local browser UI | `vitest --browser.traceView` | Uses the default local headed browser UI and adds trace replay for 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. | +## Trace Viewer -## Relation to Playwright Traces +The viewer has two resizable panes: -`browser.traceView` and [`browser.trace`](/config/browser/trace) are independent features: +- **Step list** (left) shows every recorded action, assertion, mark, and lifecycle entry, with its name, timing, selector, and source location. Failed actions and assertions are highlighted in red. +- **DOM snapshot** (right) shows the page at the selected step, with the interacted element highlighted in blue. When only failed tests are recorded, the snapshot is taken at the end of the test. -| | `browser.traceView` | `browser.trace` | -| ---------------------- | -------------------------------------------------------- | ---------------------------------------------- | -| Provider support | All providers (playwright, webdriverio, preview) | Playwright only | -| Viewer | Browser UI / Vitest UI / HTML reporter | Playwright Trace Viewer / trace.playwright.dev | -| Format | [rrweb](https://github.com/rrweb-io/rrweb) DOM snapshots | Playwright `.trace.zip` | -| Requires external tool | No | Yes (`npx playwright show-trace`) | +Selecting a step also opens its source location in the Code tab when that location is available. -You can enable both at the same time. See [Playwright Traces](./playwright-traces) for the `browser.trace` workflow. +HTML report trace viewer showing step list and DOM snapshot +HTML report trace viewer showing step list and DOM snapshot ## 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) +- 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 -Each entry captures the DOM state at that point, along with timing information, the selector, and the source location that triggered it. +Each step records its timing, selector, and the source location that triggered it. -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. +Tests outside browser mode can print the same timeline for their `expect.poll`, `vi.waitFor`, and `vi.waitUntil` steps with [`experimental.traceTimeline`](/config/experimental#experimental-tracetimeline). ## 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,22 @@ test('shows button', async () => { ## Retries and Repeats -Each attempt — retry or repeat — is recorded as a separate trace. When a test has multiple attempts, the viewer opens the most recent one by default. You can switch between attempts in the Report tab. +Each retry or repeat of a test is recorded as a separate trace. The viewer opens the most recent attempt, and you can switch between attempts in the Report tab. + +## Accessibility Tree 5.1.0 {#accessibility-tree} + +Trace view also captures the page's accessibility tree with each snapshot. The view selector in the trace viewer header switches the snapshot pane between **DOM** and **Aria**. The Aria view lists the roles, accessible names, and states that role-based locators such as `getByRole()` see, in the same format as [aria snapshots](/guide/browser/aria-snapshots). Set [`traceView.ariaSnapshot`](/config/browser/traceview#traceview-ariasnapshot) to `false` to disable it. -## Snapshot Fidelity +HTML report trace viewer showing step list and Aria snapshot +HTML report trace viewer showing step list and Aria snapshot -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 `