Vitest Browser Mode 中测试 Vue 组件:`vitest-browser-vue` 完整指南
2026/9/13 16:50:31 网站建设 项目流程

Vitest Browser Mode 中测试 Vue 组件:vitest-browser-vue完整指南

【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest

vitest-browser-vue是 Vitest 生态中用于在Browser Mode(浏览器模式)下渲染与测试 Vue 为骨架,结合仓库内 Browser Mode 的 locator、断言、交互与 Trace View 能力,系统讲解render的全部选项与返回值、cleanup、查询扩展、Vue Test Utils 配置等实战要点,帮助你写出可在真实浏览器中运行、抗抖动(flaky-resistant)的 Vue 组件测试。

一、vitest-browser-vue是什么

vitest-browser-vue渲染 Vue 组件的方式与@testing-library/vue类似,但它运行在 Vitest 的Browser Mode中——即测试真正跑在浏览器(Playwright / WebdriverIO / preview)里,而非 jsdom 之类的 DOM 模拟环境。它所带来的独特优势包括:

  • 返回的 API 与内置的 locators、user events 和 assertions 深度配合;
  • 配合expect.element时,Vitest 会自动重试元素查询直到断言成功,即使组件在断言之间发生了重新渲染,测试依然稳定;
  • 渲染、重渲染、卸载等动作会记录 trace mark,可在 Trace View 中逐步回放。

如果你已经用过@testing-library/vue,可以继续沿用既有写法;但vitest-browser-vue提供了 Testing Library 在 Browser Mode 下所不具备的上述能力。

最简单的入门用例(完整代码可在仓库脚手架示例 packages/vitest/src/create/browser/examples.ts 中看到,其为HelloWorld.vue生成的测试与此结构一致):

import { render } from 'vitest-browser-vue' import { expect, test } from 'vitest' import Component from './Component.vue' test('counter button increments the count', async () => { const screen = await render(Component, { props: { initialCount: 1, } }) await screen.getByRole('button', { name: 'Increment' }).click() await expect.element(screen.getByText('Count is 2')).toBeVisible() })

两个入口点:vitest-browser-vuevitest-browser-vue/pure

该包暴露两个入口点,二者 API 完全相同,唯一区别在于:

  • vitest-browser-vue:会在下一个测试开始前自动注册清理逻辑(cleanup handler),卸载本次渲染的组件;
  • vitest-browser-vue/pure注册该自动清理 handler,适合需要完全手动控制组件生命周期的场景(例如在beforeEach/afterEach中自行调用cleanup())。

二、render函数

export function render( component: Component, options?: ComponentRenderOptions, ): Promise<RenderResult>

render是渲染 Vue 组件的核心函数,返回一个Promise<RenderResult>,因此需要await。每次调用render都会在 Trace View 中记录一个vue.rendertrace mark,方便在回放时定位渲染发生的时刻。

2.1 Options

render支持@vue/test-utilsmount全部选项(唯一的例外是attachTo——请改用container)。在此基础之上,还额外增加了两个选项:containerbaseElement

container

默认情况下,Vitest 会创建一个div,将其追加到document.body,然后把组件渲染进去。如果你传入自己的HTMLElement作为container,它不会被自动追加——你必须在调用render之前手动调用document.body.appendChild(container)

典型场景:当你要单测tbody元素时,tbody不能是div的子元素,此时可以指定table作为渲染容器:

const table = document.createElement('table') const { container } = await render(TableBody, { props, // ⚠️ 在渲染前手动把元素追加到 body container: document.body.appendChild(table), })
baseElement

如果指定了containerbaseElement默认等于它;否则默认等于document.bodybaseElement有两个用途:

  • 作为查询(query)的基准元素——render返回的所有 locator 都相对baseElement进行查找;
  • 作为调用debug()时打印的内容范围。

2.2 Render Result

除了文档列出的返回值外,render还会返回相对于baseElement的全部可用 locator,包括自定义 locator(见 Custom Locators)。这意味着你可以把返回值当作一个"作用域化"的page使用:

const screen = await render(TableBody, { props }) await screen.getByRole('link', { name: 'Expand' }).click()
container

组件实际渲染到的 DOM 节点。它是一个普通 DOM 节点,技术上你也可以调用container.querySelector之类的原生方法去检查子元素。

::: danger 如果你发现自己依赖container去查询渲染出来的元素,请重新考虑!locators 的设计目标是对组件的变动更具韧性(例如结构调整、类名变化),应避免使用container直接查询元素。 :::

baseElement

组件在container内渲染时,baseElement是更外层的容器 DOM 节点。如果你没有在 options 中指定baseElement,它默认是document.body

这个属性对于测试渲染到容器 div 之外的组件非常有用。例如,你要做快照测试一个 portal 组件——它直接把 HTML 渲染到body里,此时就可以用baseElement来捕获。

::: tiprender返回的查询(queries)会查找baseElement内部,所以你可以直接用查询来测试 portal 组件,无需额外处理baseElement。 :::

locator

container对应的 locator。当你想把查询作用域限制在组件内部,或者把它传给其他断言时,这个 locator 非常有用:

import { render } from 'vitest-browser-vue' const { locator } = await render(NumberDisplay, { props: { number: 2 } }) await locator.getByRole('button').click() await expect.element(locator).toHaveTextContent('Hello World')
debug
function debug( el?: HTMLElement | HTMLElement[] | Locator | Locator[], maxLength?: number, options?: PrettyDOMOptions, ): void

这是console.log(prettyDOM(baseElement))的快捷方式,会把容器或指定元素的 DOM 内容打印到控制台,用于排查"为什么查不到元素"等问题。可选地传入元素/locator 数组、最大长度与格式化选项来控制输出。

rerender
function rerender(props: Partial<Props>): Promise<void>

以新的 props 重新渲染同一个组件,同样会在 Trace View 中记录vue.rerendertrace mark。

从最佳实践角度,更好的做法是测试真正负责更新 props 的父组件,从而验证 props 被正确传递,避免测试依赖实现细节。但如果你确实想在测试里更新已渲染组件的 props,可以这样用:

import { render } from 'vitest-browser-vue' const { rerender } = await render(NumberDisplay, { props: { number: 1 } }) // 用不同的 props 重渲染同一组件 await rerender({ number: 2 })
unmount
function unmount(): Promise<void>

卸载已渲染的组件,并记录vue.unmounttrace mark。它适合测试组件从页面移除时的行为——例如验证没有遗留的事件处理器(避免内存泄漏):

const { unmount } = await render(Component) await unmount() // 此时可断言全局状态、事件监听等已被正确清理
emitted
function emitted<T = unknown>(): Record<string, T[]> function emitted<T = unknown[]>(eventName: string): undefined | T[]

返回组件发出的(emitted)事件。不带参数时返回所有事件名到事件参数数组的映射;传入事件名时只返回该事件对应的参数数组(未触发则为undefined)。

::: warning Emitted 的值属于不直接暴露给用户的实现细节,因此更推荐用 locators 来验证"发出的值如何改变界面上显示的内容",而不是直接断言事件参数本身。 :::

三、cleanup

export function cleanup(): void

移除所有通过render渲染的组件。如前文所述,vitest-browser-vue主入口会在下一个测试开始前自动执行清理;vitest-browser-vue/pure不会,需要你在合适的时机(通常是afterEach)手动调用cleanup()

四、扩展查询:让render返回自定义 locator

要扩展 locator 查询,使用 “Custom Locators” 中介绍的locators.extendAPI。例如,要让render返回一个新的自定义 locator,可以这样定义:

import { locators } from 'vitest/browser' import { render } from 'vitest-browser-vue' locators.extend({ getByArticleTitle(title) { return `[data-title="${title}"]` }, }) const screen = await render(Component) await expect.element( screen.getByArticleTitle('Hello World') ).toBeVisible()

locators.extend定义的是选择器生成器——它返回一个 CSS 选择器字符串。扩展之后,所有通过 locator API 创建的对象(包括pagerender的返回值)都会拥有这个新查询方法。

五、配置 Vue Test Utils

你可以通过给config导出(在vitest-browser-vuevitest-browser-vue/pure中都可用)赋值的方式来配置 Vue Test Utils 的选项:

import { config } from 'vitest-browser-vue/pure' config.global.stubs.CustomComponent = { template: '<div></div>', }

config.global支持 Vue Test Utils 的全局配置项,例如stubs(组件桩)、pluginsmocksprovide等,从而在渲染每个组件时统一生效。

六、与 Browser Mode 能力深度协同:从查询到断言再到交互

vitest-browser-vue的价值在于它把 Vue 组件渲染接入了一整套 Browser Mode 基础设施。理解下面几个核心机制,可以写出更稳定、更贴近真实用户的测试。

6.1 locator:惰性查询与自动重试

render返回的getBy*方法返回的是locator 对象,而不是 DOM 元素。locator 是惰性的——它只是一个由选择器字符串定义的元素(或一组元素)的抽象,真正解析发生在你调用其方法(clickelement等)或传给断言时。这使得 locator 查询可组合、可保存,也让 Vitest 可以在必要时重试交互与断言。

常用查询包括getByRole(按 ARIA role 与 accessible name)、getByTextgetByLabelTextgetByPlaceholdergetByAltTextgetByTitlegetByTestId等,详见 Locators API。例如:

const screen = await render(LoginForm) // 按可访问名称定位(推荐:贴近真实用户的使用方式) await screen.getByRole('textbox', { name: 'Login' }).fill('admin') await screen.getByRole('button', { name: /submit/i }).click()

locator 还支持链式操作(.filter.nth.and.or)与转义舱(.element().query().elements().all())等能力,其底层实现位于 packages/browser/src/client/tester/locators.ts。

6.2expect.element:内置重试的 DOM 断言

浏览器测试因异步性质(超时、网络请求、动画等)可能不稳定,因此 Vitest 通过expect.pollexpect.element提供开箱即用的可重试断言。断言 API 文档见 Assertion API,其 DOM 断言实现(toBeVisibletoBeEnabledtoHaveTextContent等,均 fork 自@testing-library/jest-dom)位于 packages/browser/src/client/tester/expect/。

test('error banner is rendered', async () => { triggerError() // 创建 locator:此时并不检查元素是否存在 const banner = screen.getByRole('alert', { name: /error/i }) // expect.element 会反复检查:元素存在于 DOM 中,且 textContent 等于 "Error!" // 直到条件满足或超时 await expect.element(banner).toMatchTextContent('Error!') })

expect.element接受可选的第二个参数用于控制重试行为:

interface ExpectPollOptions { // 重试间隔(毫秒),默认取 "expect.poll.interval" 配置 interval?: number // 重试总时长(毫秒),默认取 "expect.poll.timeout" 配置 timeout?: number // 断言失败时打印的消息 message?: string }

当传入 locator 时,Vitest 会先通过locator.findElement()解析元素再执行 DOM 断言——findElement自身使用递增的重试间隔(0、20、50、100、100、500ms),随后才应用断言层面的interval。注意,toMatchTextContent等断言在普通expect上也可用,只是没有内置重试:

// 如果 .textContent 不是 'Error!',会立即失败 expect(banner).toMatchTextContent('Error!')

推荐做法:只要使用page.getBy*/screen.getBy*locator,就始终配合expect.element来降低测试抖动。

6.3 userEvent 与 locator 方法:真实浏览器交互

vitest-browser-vue渲染出的组件可以接受userEvent(从vitest/browser导入)与 locator 方法(clickfillhoverselectOptionsuploaddragAndDropkeyboardtabcopy/cut/paste等)的真实交互。与@testing-library/user-event用合成事件模拟不同,Vitest 通过 Chrome DevTools Protocol 或 WebDriver 执行交互,行为与真实用户一致(详见 Interactivity API)。

import { userEvent } from 'vitest/browser' const screen = await render(ContactForm) await screen.getByRole('textbox', { name: /email/i }).fill('john@example.com') await userEvent.keyboard('{Tab}') // 真实按键,可用于焦点管理测试 await screen.getByRole('button', { name: /submit/i }).click()

仓库中的组件测试指南 docs/guide/browser/component-testing.md 提供了大量组合示例,例如用expect.element自动重试等待异步数据渲染、用userEvent.keyboard测试模态框的 Escape 关闭与焦点陷阱等。

七、Trace View 集成:vue.render/vue.rerender/vue.unmount

renderrerenderunmount分别记录vue.rendervue.rerendervue.unmounttrace mark,这些标记会出现在 Vitest 的 Trace View(实验特性,自 5.0.0 起)时间线中。启用方式:

// vitest.config.ts import { defineConfig } from 'vitest/config' export default defineConfig({ test: { browser: { traceView: true, }, }, })
# 或通过 CLI vitest --browser.traceView

开启后,在 Browser UI、Vitest UI 与 HTML reporter 中都可以打开 trace viewer:左侧是步骤列表(每个动作、断言、mark、生命周期条目,含名称、时机、选择器与源码位置,失败项标红),右侧是该步骤时刻的 DOM 快照(被交互的元素高亮为蓝色)。vue.render等标记能帮你快速定位"组件是在哪一步被渲染/更新/卸载的",再配合locator.mark()(仅当browser.trace启用时有效)可进一步标注关键时间点。

八、仓库中的配套示例

  • 脚手架模板:vitest create browser生成的 Vue 项目模板定义在 packages/vitest/src/create/browser/examples.ts,其中HelloWorld.vue的测试即为render(HelloWorld, { props: { name: 'Vitest' } })+expect.element(getByText('Hello Vitest!'))的组合;
  • 组件测试策略:仓库的 docs/guide/browser/component-testing.md 覆盖了隔离策略、集成策略、表单校验、错误边界、可访问性测试与调试技巧;
  • 底层能力文档:Locators API、Interactivity API、Assertion API、Trace View。

总结

vitest-browser-vue把 Vue 组件渲染融入 Vitest Browser Mode 的完整能力栈:render负责挂载组件并暴露作用域化的 locator 查询,expect.element提供自动重试的 DOM 断言,userEvent与 locator 方法提供真实的浏览器交互,而 Trace View 让render/rerender/unmount等生命周期动作可被回放与调试。遵循"通过 locator 查询、通过可访问名称定位、优先expect.element、避免container.querySelectoremitted实现细节"等原则,即可写出既贴近真实用户行为又足够稳定的 Vue 组件测试。

【免费下载链接】vitestNext generation testing framework powered by Vite.项目地址: https://gitcode.com/GitHub_Trending/vi/vitest

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询