Skip to content

浏览器模式 实验性 ​

此页面提供有关 Vitest API 中实验性浏览器模式功能的信息,该功能允许你在浏览器中本地运行测试,提供对窗口和文档等浏览器全局变量的访问。此功能目前正在开发中,API 未来可能会更改。

TIP

如果你正在寻找关于 expect、vi 或任何通用 API(如工作区或类型测试)的文档,请参阅 "入门指南"。

Vitest UIVitest UI

安装 ​

为方便设置,可使用 vitest init browser 命令安装所需的依赖项并创建浏览器配置。

bash
npx vitest init browser
bash
yarn exec vitest init browser
bash
pnpx vitest init browser
bash
bunx vitest init browser

手动安装 ​

我们也可以手动安装软件包。默认情况下,浏览器模式不需要任何额外的 E2E provider 就能在本地运行测试,因为它会复用你现有的浏览器。

bash
npm install -D vitest @vitest/browser
bash
yarn add -D vitest @vitest/browser
bash
pnpm add -D vitest @vitest/browser
bash
bun add -D vitest @vitest/browser

WARNING

不过,要在 CI 中运行测试,我们需要安装 playwright 或 webdriverio 。我们还建议在本地测试时切换到这两个选项中的一个,而不是使用默认的 preview 提供程序,因为它依赖于模拟事件而不是使用 Chrome DevTools 协议。

如果我们尚未使用这些工具中的任何一个,我们建议从 Playwright 开始,因为它支持并行执行,这可以使我们的测试运行得更快。此外,Playwright 使用的是 Chrome DevTools Protocol ,通常比 WebDriver 更快。

Playwright 是一个用于网络测试和自动化的框架。

bash
npm install -D vitest @vitest/browser playwright
bash
yarn add -D vitest @vitest/browser playwright
bash
pnpm add -D vitest @vitest/browser playwright
bash
bun add -D vitest @vitest/browser playwright

配置 ​

要在 Vitest 配置中使用浏览器模式,我们可以使用 --browser=name 标志或在 Vitest 配置文件中将 browser.enabled 字段设置为 true。下面是使用浏览器字段的示例配置:

vitest.config.ts
ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
  test: {
    browser: {
      provider: 'playwright', // or 'webdriverio'
      enabled: true,
      // at least one instance is required
      instances: [
        { browser: 'chromium' },
      ],
    },
  }
})

INFO

Vitest 默认分配端口号 63315 以避免与开发服务器冲突,允许我们同时并行运行两者。我们可以通过 browser.api 选项来更改这个端口号。

自 Vitest 2.1.5 版本起,命令行界面(CLI)不再自动打印 Vite 的 URL。当我们在观察模式下运行时,可以通过按 "b" 键来打印 URL。

如果之前未使用过 Vite,请确保已安装框架插件并在配置中指定。有些框架可能需要额外配置才能运行,请查看其 Vite 相关文档以确定。

ts
import react from '@vitejs/plugin-react'
import { defineConfig } from 'vitest/config'

export default defineConfig({
  plugins: [react()],
  test: {
    browser: {
      enabled: true,
      provider: 'playwright',
      instances: [
        { browser: 'chromium' },
      ],
    }
  }
})
ts
import vue from '@vitejs/plugin-vue'
import { defineConfig } from 'vitest/config'

export default defineConfig({
  plugins: [vue()],
  test: {
    browser: {
      enabled: true,
      provider: 'playwright',
      instances: [
        { browser: 'chromium' },
      ],
    }
  }
})
ts
import { svelte } from '@sveltejs/vite-plugin-svelte'
import { defineConfig } from 'vitest/config'

export default defineConfig({
  plugins: [svelte()],
  test: {
    browser: {
      enabled: true,
      provider: 'playwright',
      instances: [
        { browser: 'chromium' },
      ],
    }
  }
})
ts
import solidPlugin from 'vite-plugin-solid'
import { defineConfig } from 'vitest/config'

export default defineConfig({
  plugins: [solidPlugin()],
  test: {
    browser: {
      enabled: true,
      provider: 'playwright',
      instances: [
        { browser: 'chromium' },
      ],
    }
  }
})
ts
import marko from '@marko/vite'
import { defineConfig } from 'vitest/config'

export default defineConfig({
  plugins: [marko()],
  test: {
    browser: {
      enabled: true,
      provider: 'playwright',
      instances: [
        { browser: 'chromium' },
      ],
    }
  }
})

如果我们需要使用基于 Node 的运行器来运行一些测试,可以定义一个 工作区 文件,其中包含不同测试策略的独立配置:

vitest.workspace.ts
ts
import { defineWorkspace } from 'vitest/config'

export default defineWorkspace([
  {
    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,
        instances: [
          { browser: 'chromium' },
        ],
      },
    },
  },
])

Browser Option Types ​

Vitest 中的浏览器选项取决于provider。如果在配置文件中传递 --browser 且未指定其名称,则 Vitest 将失败。可用选项:

  • webdriverio 支持这些浏览器:
    • firefox
    • chrome
    • edge
    • safari
  • playwright 支持这些浏览器:
    • firefox
    • webkit
    • chromium

TypeScript ​

默认情况下,TypeScript 无法识别 providers 选项和额外的 expect 属性。如果我们不使用任何 providers ,请确保在测试、设置文件 或 配置文件 中引用 @vitest/browser/matchers,以获取额外的 expect 定义。如果我们使用自定义 providers ,请确保在同一文件中添加 @vitest/browser/providers/playwright 或 @vitest/browser/providers/webdriverio,以便 TypeScript 可以获取自定义选项的定义:

ts
/// <reference types="@vitest/browser/matchers" />
ts
/// <reference types="@vitest/browser/providers/playwright" />
ts
/// <reference types="@vitest/browser/providers/webdriverio" />

或者,我们也可以将它们添加到 tsconfig.json 文件中的 compilerOptions.types 字段。请注意,在此字段中指定任何内容将禁用 @types/* 包的自动加载功能。

json
{
  "compilerOptions": {
    "types": ["@vitest/browser/matchers"]
  }
}
json
{
  "compilerOptions": {
    "types": ["@vitest/browser/providers/playwright"]
  }
}
json
{
  "compilerOptions": {
    "types": ["@vitest/browser/providers/webdriverio"]
  }
}

浏览器兼容性 ​

Vitest 使用 Vite dev server 来运行我们的测试,因此我们只支持 esbuild.target选项(默认为 esnext)中指定的功能。

默认情况下,Vite 的目标浏览器支持本地 ES Modules、本地 ESM dynamic import 和 import.meta。此外,我们还利用 BroadcastChannel在 iframe 之间进行通信:

  • Chrome >=87
  • Firefox >=78
  • Safari >=15.4
  • Edge >=88

Running Tests ​

要使用 CLI 指定浏览器,请使用 --browser 标志后跟浏览器名称,如下所示:

sh
npx vitest --browser=chrome

或者你可以使用点符号向 CLI 提供浏览器选项:

sh
npx vitest --browser.headless

默认情况下,Vitest 会自动打开浏览器用户界面进行开发。我们的测试将在中间的 iframe 中运行。我们可以通过选择首选尺寸、在测试中调用 page.viewport 或在 the config 中设置默认值来配置视口。

Headless ​

headless 模式是浏览器模式下可用的另一个选项。在 headless 模式下,浏览器在没有用户界面的情况下在后台运行,这对于运行自动化测试非常有用。Vitest 中的 headless 选项可以设置为布尔值以启用或禁用 headless 模式。

在使用 headless 模式时,Vitest 不会自动打开用户界面。如果我们希望继续使用用户界面,同时让测试以 headless 模式运行,我们可以安装[@vitest/ui](/guide/ui)包,并在运行Vitest时传递--ui标志。

这是启用 headless 模式的示例配置:

vitest.config.ts
ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
  test: {
    browser: {
      provider: 'playwright',
      enabled: true,
      headless: true,
    },
  }
})

你还可以在 CLI 中使用 --browser.headless 标志设置 headless 模式,如下所示:

sh
npx vitest --browser.headless

在这种情况下,Vitest 将使用 Chrome 浏览器以 headless 模式运行。

WARNING

默认情况下Headless模式不可用。我们需要使用 playwright 或 webdriverio 提供程序来启用此功能。

Examples ​

一般情况下,我们不需要任何依赖来使用浏览器模式:

example.test.js
js
import { page } from '@vitest/browser/context'
import { expect, test } from 'vitest'
import { render } from './my-render-function.js'

test('properly handles form inputs', async () => {
  render() // mount DOM elements

  // 断言初始状态。
  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 提供了用于渲染几个流行框架的组件的依赖包:

其他框架也有社区提供的软件包:

如果你的框架没有被包含在内,请随时创建你自己的软件包——它是一个简单的封装,围绕着框架渲染器和 page.elementLocator API。我们会在本页面添加指向它的链接。请确保其名称以 vitest-browser- 开头。

除了渲染组件和定位元素外,你还需要进行断言。Vitest 基于 @testing-library/jest-dom 库提供了一整套开箱即用的 DOM 断言。更多信息请参阅 Assertions API。

ts
import { page } from '@vitest/browser/context'
import { expect } from 'vitest'
// element is rendered correctly
await expect.element(page.getByText('Hello World')).toBeInTheDocument()

Vitest 公开了一个Context API,其中包含一小套在测试中可能有用的实用程序。例如,如果我们需要进行交互,如点击元素或在输入框中输入文本,我们可以使用 @vitest/browser/context 中的 userEvent。更多信息请参阅 Interactivity API。

ts
import { page, userEvent } from '@vitest/browser/context'
await userEvent.fill(page.getByLabelText(/username/i), 'Alice')
// or just locator.fill
await page.getByLabelText(/username/i).fill('Alice')
ts
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()
})
ts
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()
})
tsx
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()
})
ts
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()
})

Vitest 并不支持所有开箱即用的框架,但我们可以使用外部工具来运行这些框架的测试。我们还鼓励社区创建他们自己的 vitest-browser 封装程序,如果我们有这样的封装程序,请随时将其添加到上述示例中。

对于不支持的框架,我们建议使用 testing-library 软件包:

我们还可以在 browser-examples 中查看更多的案例。

WARNING

testing-library 提供了一个软件包 @testing-library/user-event。我们不建议直接使用它,因为它会模拟事件而非实际触发事件--相反,请使用从 @vitest/browser/context导入的 userEvent,它在引擎盖下使用 Chrome DevTools 协议或 Webdriver(取决于provider)。

tsx
// based on @testing-library/preact example
// https://testing-library.com/docs/preact-testing-library/example

import { render } from '@testing-library/preact'
import { page } from '@vitest/browser/context'
import { h } from 'preact'

import HiddenMessage from '../hidden-message'

test('shows the children when the checkbox is checked', async () => {
  const testMessage = 'Test Message'

  const { baseElement } = render(
    <HiddenMessage>{testMessage}</HiddenMessage>,
  )

  const screen = page.elementLocator(baseElement)

  // .query() 将返回找到的元素或在未找到时返回 null。
  // .element() 会返回该元素,如果找不到该元素则会抛出错误。
  expect(screen.getByText(testMessage).query()).not.toBeInTheDocument()

  // 查询可以接受正则表达式,
  // 使选择器更能适应内容的调整和变化。
  await screen.getByLabelText(/show/i).click()

  await expect.element(screen.getByText(testMessage)).toBeInTheDocument()
})
tsx
// baed on @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()
})
ts
// baed on @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 提供默认模拟和默认返回值。这确保如果用户不小心使用了同步弹出式 Web API,执行不会挂起。但是,仍然建议用户模拟这些 Web API 以获得更好的体验。在 Mocking 中阅读更多内容。

Released under the MIT License.