| title | 浏览器模式 | 指南 |
|---|---|
| outline | deep |
本章介绍 Vitest API 中浏览器模式功能,该功能允许你在浏览器中运行测试,从而可直接访问 window 和 document 等浏览器全局对象。
::: tip
如果你需要 expect 、vi ,或者像测试项目、类型测试等通用 API 的文档,请查看 “快速起步” 指南。
:::
为方便设置,可使用 vitest init browser 命令安装所需的依赖项并创建浏览器配置。
::: code-group
npx vitest init browseryarn exec vitest init browserpnpx vitest init browserbunx vitest init browser:::
你也可以手动安装依赖包。Vitest 明确要求定义一个 provider。你可以选择 preview、playwright 或 webdriverio。
如果你仅需预览测试运行效果,可以使用 preview 提供程序:
::: code-group
npm install -D vitest @vitest/browser-previewyarn add -D vitest @vitest/browser-previewpnpm add -D vitest @vitest/browser-previewbun add -D vitest @vitest/browser-preview:::
::: warning
不过,要在 CI 中运行测试,我们需要安装 playwright 或 webdriverio 。我们还建议在本地测试时切换到这两个选项中的一个,而不是使用默认的 preview 提供程序,因为它依赖于模拟事件而不是使用 Chrome DevTools 协议。
如果你还没有使用这些工具中的任何一个,我们建议从 Playwright 开始,因为它支持并行执行,可显著提升测试速度。
::: tabs key:provider == Playwright Playwright 是一个用于网络测试和自动化的框架。
::: code-group
npm install -D vitest @vitest/browser-playwrightyarn add -D vitest @vitest/browser-playwrightpnpm add -D vitest @vitest/browser-playwrightbun add -D vitest @vitest/browser-playwright== WebdriverIO
WebdriverIO 允许我们使用 WebDriver 协议在本地运行测试。
::: code-group
npm install -D vitest @vitest/browser-webdriverioyarn add -D vitest @vitest/browser-webdriveriopnpm add -D vitest @vitest/browser-webdriveriobun add -D vitest @vitest/browser-webdriverio:::
想要在 Vitest 中启用浏览器模式,只需在配置文件中将 browser.enabled 设置为 true。下面是一个使用 browser 配置的示例:
import { defineConfig } from 'vitest/config'
import { playwright } from '@vitest/browser-playwright'
export default defineConfig({
test: {
browser: {
provider: playwright(),
enabled: true,
// 至少需要一个实例
instances: [
{ browser: 'chromium' },
],
},
}
})::: info
Vitest 默认分配端口号 63315 以避免与开发服务器冲突,允许我们同时并行运行两者。我们可以通过 browser.api 选项来更改这个端口号。
CLI 不会自动打印 Vite 服务器 URL。在观察模式下运行时,你可以按 "b" 键来打印 URL。 :::
如果之前未使用过 Vite,请确保已安装框架插件并在配置中指定。有些框架可能需要额外配置才能运行,请查看其 Vite 相关文档以确定。
::: code-group
import react from '@vitejs/plugin-react'
import { playwright } from '@vitest/browser-playwright'
export default defineConfig({
plugins: [react()],
test: {
browser: {
enabled: true,
provider: playwright(),
instances: [
{ browser: 'chromium' },
],
}
}
})import { defineConfig } from 'vitest/config'
import { playwright } from '@vitest/browser-playwright'
import vue from '@vitejs/plugin-vue'
export default defineConfig({
plugins: [vue()],
test: {
browser: {
enabled: true,
provider: playwright(),
instances: [
{ browser: 'chromium' },
],
}
}
})import { svelte } from '@sveltejs/vite-plugin-svelte'
import { playwright } from '@vitest/browser-playwright'
export default defineConfig({
plugins: [svelte()],
test: {
browser: {
enabled: true,
provider: playwright(),
instances: [
{ browser: 'chromium' },
],
}
}
})import solidPlugin from 'vite-plugin-solid'
import { playwright } from '@vitest/browser-playwright'
export default defineConfig({
plugins: [solidPlugin()],
test: {
browser: {
enabled: true,
provider: playwright(),
instances: [
{ browser: 'chromium' },
],
}
}
})import marko from '@marko/vite'
import { playwright } from '@vitest/browser-playwright'
export default defineConfig({
plugins: [marko()],
test: {
browser: {
enabled: true,
provider: playwright(),
instances: [
{ browser: 'chromium' },
],
}
}
})import { qwikVite } from '@builder.io/qwik/optimizer'
import { playwright } from '@vitest/browser-playwright'
// 可选项,在 SSR 模式下运行测试
import { testSSR } from 'vitest-browser-qwik/ssr-plugin'
import { defineConfig } from 'vitest/config'
export default defineConfig({
plugins: [testSSR(), qwikVite()],
test: {
browser: {
enabled: true,
provider: playwright(),
instances: [{ browser: 'chromium' }]
},
},
}):::
如果你想让部分测试通过基于 Node 的运行器执行,可以在配置中使用 projects 选项,并为不同的测试策略提供独立的配置:
{#projects-config}
import { defineConfig } from 'vitest/config'
import { playwright } from '@vitest/browser-playwright'
export default defineConfig({
test: {
projects: [
{
test: {
// 基于文件命名约定的示例
// 非强制要求
include: [
'tests/unit/**/*.{test,spec}.ts',
'tests/**/*.unit.{test,spec}.ts',
],
name: 'unit',
environment: 'node',
},
},
{
test: {
// 基于文件命名约定的示例
// 非强制要求
include: [
'tests/browser/**/*.{test,spec}.ts',
'tests/**/*.browser.{test,spec}.ts',
],
name: 'browser',
browser: {
enabled: true,
provider: playwright(),
instances: [
{ browser: 'chromium' },
],
},
},
},
],
},
})Vitest 中的浏览器选项取决于 provider。如果在配置文件中传递 --browser 且未指定其名称,则 Vitest 将失败。可用选项:
webdriverio支持这些浏览器:firefoxchromeedgesafari
playwright支持这些浏览器:firefoxwebkitchromium
Vitest 使用 Vite dev server 来运行我们的测试,因此我们只支持 esbuild.target选项(默认为 esnext)中指定的功能。
默认情况下,Vite 的目标浏览器支持原生 ES Modules、原生 ESM 动态导入 和 import.meta。此外,我们还利用 BroadcastChannel在 iframe 之间进行通信:
- Chrome >=87
- Firefox >=78
- Safari >=15.4
- Edge >=88
要使用 CLI 指定浏览器,请使用 --browser 参数后跟浏览器名称,如下所示:
npx vitest --browser=chromium或者你可以使用点符号向 CLI 提供浏览器选项:
npx vitest --browser.headless::: warning
自 Vitest 3.2 起,如果你在配置文件中没有设置 browser 选项,却在命令行中使用了 --browser 参数, Vitest 会直接报错,因为它无法确定当前配置是为浏览器测试准备的还是用于 Node.js 测试。
:::
Vitest 默认会在开发模式下自动打开浏览器界面,测试会在页面中央的 iframe 中执行。你可以通过选择界面中的预设尺寸、在测试中调用 page.viewport 方法,或者在 配置文件 中设置默认值来调整视口大小。
如需采用捕获每个测试的 DOM 快照,而非实时显示 iframe的替代调试方案,请参阅 追踪视图。
无头模式是浏览器模式下可用的另一个选项。在无头模式下,浏览器在没有用户界面的情况下在后台运行,这对于运行自动化测试非常有用。Vitest 中的 headless 选项可以设置为布尔值以启用或禁用无头模式。
在使用无头模式时,Vitest 不会自动打开用户界面。如果我们希望继续使用用户界面,同时让测试以 无头模式运行,我们可以安装 @vitest/ui 包,并在运行Vitest时传递 --ui 参数。
这是启用无头模式的示例配置:
import { defineConfig } from 'vitest/config'
import { playwright } from '@vitest/browser-playwright'
export default defineConfig({
test: {
browser: {
provider: playwright(),
enabled: true,
headless: true,
},
}
})你还可以在 CLI 中使用 --browser.headless 参数设置无头模式,如下所示:
npx vitest --browser.headless在这种情况下,Vitest 将使用 Chrome 浏览器以无头模式运行。
::: warning
默认情况下无头模式不可用。我们需要使用 playwright 或 webdriverio 提供程序来启用此功能。
:::
一般情况下,我们不需要任何依赖来使用浏览器模式:
import { expect, test } from 'vitest'
import { page } from 'vitest/browser'
import { render } from './my-render-function.js'
test('properly handles form inputs', async () => {
render() // 挂载 DOM 元素
// 断言初始状态。
await expect.element(page.getByText('Hi, my name is Alice')).toBeInTheDocument()
// 通过查询关联的标签获取输入的 DOM 节点。
const usernameInput = page.getByLabelText(/username/i)
// 将名称输入到输入框中。
// 这已经验证了输入框中的值是正确的,无需手动检查其值。
await usernameInput.fill('Bob')
await expect.element(page.getByText('Hi, my name is Bob')).toBeInTheDocument()
})但是,Vitest 提供了用于渲染几个流行框架的组件的依赖包:
vitest-browser-vue渲染 vue 组件vitest-browser-svelte渲染 svelte 组件vitest-browser-react渲染 react 组件vitest-browser-angular渲染 Angular 组件
其他框架也有社区提供的软件包:
vitest-browser-lit渲染 lit 组件vitest-browser-preact渲染 preact 组件vitest-browser-qwik渲染 qwik 组件
如果你的框架没有被包含在内,请随时创建你自己的软件包——它是一个简单的封装,围绕着框架渲染器和 page.elementLocator API。我们会在本页面添加指向它的链接。请确保其名称以 vitest-browser- 开头。
除了渲染组件和定位元素外,你还需要进行断言。Vitest 基于 @testing-library/jest-dom 库提供了一整套开箱即用的 DOM 断言。更多信息请参阅 Assertions API。
import { expect } from 'vitest'
import { page } from 'vitest/browser'
// 元素是正确渲染
await expect.element(page.getByText('Hello World')).toBeInTheDocument()Vitest 暴露了一个 上下文 API,其中包含一组在测试中可能对你有用的实用程序。例如,如果你需要进行交互操作,比如点击元素或在输入框中输入文本,你可以使用来自 vitest/browser 的 userEvent。更多内容请参阅 交互性 API。
import { page, userEvent } from 'vitest/browser'
await userEvent.fill(page.getByLabelText(/username/i), 'Alice')
// 或只用 locator.fill
await page.getByLabelText(/username/i).fill('Alice')::: code-group
import { render } from 'vitest-browser-vue'
import Component from './Component.vue'
test('properly handles v-model', async () => {
const screen = render(Component)
// 断言初始状态。
await expect.element(screen.getByText('Hi, my name is Alice')).toBeInTheDocument()
// 通过查询关联的标签获取输入的 DOM 节点。
const usernameInput = screen.getByLabelText(/username/i)
// 将名称输入到输入框中。
// 这已经验证了输入框中的值是正确的,无需手动检查其值。
await usernameInput.fill('Bob')
await expect.element(screen.getByText('Hi, my name is Bob')).toBeInTheDocument()
})import { expect, test } from 'vitest'
import { render } from 'vitest-browser-svelte'
import Greeter from './greeter.svelte'
test('greeting appears on click', async () => {
const screen = render(Greeter, { name: 'World' })
const button = screen.getByRole('button')
await button.click()
const greeting = screen.getByText(/hello world/iu)
await expect.element(greeting).toBeInTheDocument()
})import { render } from 'vitest-browser-react'
import Fetch from './fetch'
test('loads and displays greeting', async () => {
// 将一个 React 元素渲染到 DOM 中。
const screen = render(<Fetch url="/greeting" />)
await screen.getByText('Load Greeting').click()
// 如果找不到元素,则等待一段时间后再抛出错误。
const heading = screen.getByRole('heading')
// 断言警告消息是正确的。
await expect.element(heading).toHaveTextContent('hello there')
await expect.element(screen.getByRole('button')).toBeDisabled()
})import { html } from 'lit'
import { render } from 'vitest-browser-lit'
import './greeter-button'
test('greeting appears on click', async () => {
const screen = render(html`<greeter-button name="World"></greeter-button>`)
const button = screen.getByRole('button')
await button.click()
const greeting = screen.getByText(/hello world/iu)
await expect.element(greeting).toBeInTheDocument()
})import Greeting from '.Greeting'
import { createElement } from 'preact'
import { render } from 'vitest-browser-preact'
test('greeting appears on click', async () => {
const screen = render(<Greeting />)
const button = screen.getByRole('button')
await button.click()
const greeting = screen.getByText(/hello world/iu)
await expect.element(greeting).toBeInTheDocument()
})import { render } from 'vitest-browser-qwik'
import Greeting from './greeting'
test('greeting appears on click', async () => {
// renderSSR 和 renderHook 也是可用
const screen = render(<Greeting />)
const button = screen.getByRole('button')
await button.click()
const greeting = screen.getByText(/hello world/iu)
await expect.element(greeting).toBeInTheDocument()
}):::
Vitest 并不支持所有开箱即用的框架,但我们可以使用外部工具来运行这些框架的测试。我们还鼓励社区创建他们自己的 vitest-browser 封装程序,如果我们有这样的封装程序,请随时将其添加到上述示例中。
对于不支持的框架,我们建议使用 testing-library 软件包:
@solidjs/testing-library渲染 solid 组件@marko/testing-library渲染 marko 组件
我们还可以在 browser-examples 中查看更多的案例。
::: warning
testing-library 提供了一个软件包 @testing-library/user-event。我们不建议直接使用它,因为它会模拟事件而非实际触发事件--相反,请使用从 vitest/browser导入的 userEvent,它在引擎盖下使用 Chrome DevTools 协议或 Webdriver(取决于provider)。
:::
::: code-group
// 基于 @testing-library/solid API
// https://testing-library.com/docs/solid-testing-library/api
import { render } from '@testing-library/solid'
it('uses params', async () => {
const App = () => (
<>
<Route
path="/ids/:id"
component={() => (
<p>
Id:
{useParams()?.id}
</p>
)}
/>
<Route path="/" component={() => <p>Start</p>} />
</>
)
const { baseElement } = render(() => <App />, { location: 'ids/1234' })
const screen = page.elementLocator(baseElement)
await expect.screen(screen.getByText('Id: 1234')).toBeInTheDocument()
})// 基于 @testing-library/marko API
// https://testing-library.com/docs/marko-testing-library/api
import { render, screen } from '@marko/testing-library'
import Greeting from './greeting.marko'
test('renders a message', async () => {
const { baseElement } = await render(Greeting, { name: 'Marko' })
const screen = page.elementLocator(baseElement)
await expect.element(screen.getByText(/Marko/)).toBeInTheDocument()
expect(container.firstChild).toMatchInlineSnapshot(`
<h1>Hello, Marko!</h1>
`)
}):::
使用 Vitest 浏览器时,需要注意的是像 alert 或 confirm 这样的线程阻塞对话框不能在本地使用。这是因为它们阻塞了网页,这意味着 Vitest 无法继续与该页面通信,导致执行挂起。
在这类情况下,Vitest 会为相关 API 提供带有默认返回值的内置 mock,从而避免用户不小心使用同步弹窗等 Web API 时导致程序卡死。不过,仍然强烈建议用户自行对这些 Web API 进行 mock,以获得更稳定、可控的测试体验。更多内容可参考 模拟 章节。
在浏览器模式下,Vitest 依赖浏览器自身对 ESM 模块的原生支持来加载模块。此时,模块的命名空间对象是不可修改的,这与 Node.js 测试中 Vitest 能够对模块执行打补丁不同。因此,你不能对通过 import 导入的对象使用 vi.spyOn :
import { vi } from 'vitest'
import * as module from './module.js'
vi.spyOn(module, 'method') // ❌ 抛出错误为了解决这个限制,Vitest 在 vi.mock('./module.js') 中提供了 { spy: true } 选项。启用后,它会自动对模块里所有的导出进行监听,而不会像普通 mock 那样将它们替换成假的实现。
import { vi } from 'vitest'
import * as module from './module.js'
vi.mock('./module.js', { spy: true })
vi.mocked(module.method).mockImplementation(() => {
// ...
})不过,如果你想模拟导出的 变量 ,唯一可行的方式是让模块额外导出一个能修改该变量内部值的方法:
::: code-group
export let MODE = 'test'
export function changeMode(newMode) {
MODE = newMode
}import { expect } from 'vitest'
import { changeMode, MODE } from './module.js'
changeMode('production')
expect(MODE).toBe('production'):::

