文档
Storybook Docs

Vitest 插件

Storybook 的 Vitest 插件允许您直接在 Storybook 中测试组件。它本身会将您的故事转换为组件测试,这些测试会在真实浏览器环境中测试您的组件的渲染和行为。它还可以计算您的故事提供的项目覆盖率

如果您的项目正在使用其他测试插件,例如视觉测试插件可访问性插件,您可以将这些测试与您的组件测试一起运行。

当为故事运行组件测试时,状态会显示在侧边栏中。可以过滤侧边栏仅显示失败的故事,您可以按住失败故事上的菜单按钮查看调试选项。

您还可以以观察模式运行测试,这将自动在您更改组件或故事时重新运行测试。要激活,请按测试小部件中的观察模式切换(眼睛图标)。

安装和设置

安装前,请确保您的项目满足以下要求

  • 使用 Vite 的 Storybook 框架(例如 vue3-vitereact-vitenextjs-vitesveltekit 等)
  • Vitest ≥ 3.0
    • 如果您还没有使用 Vitest,它将在您安装插件时被安装和配置。
  • (可选) MSW ≥ 2.0
    • 如果安装了 MSW,则必须是 v2.0.0 或更高版本,以免与 Vitest 的依赖项冲突。

与 Next.js 一起使用 — Vitest 插件支持 Next.js ≥ 14.1 项目,但您必须使用@storybook/nextjs-vite 框架。当您运行下面的设置命令时,如果尚未安装,系统会提示您安装并使用该框架。

自动设置

运行以下命令来安装和配置插件,该插件包含使用 Vitest 将您的故事作为测试运行的插件

npx storybook add @storybook/addon-vitest

那个add 命令将安装并注册 Vitest 插件。它还将检查您项目的 Vite 和 Vitest 设置,并在必要时安装和配置它们,提供合理的默认值。您可能需要调整配置以满足项目的需求。完整的配置选项可以在下面的API 部分找到。

手动设置

对于某些项目设置,add 命令可能无法自动设置插件,并会要求您完成额外的设置步骤。请按以下步骤操作:

  1. 确保 Vite 和 Vitest 已在您的项目中配置。
  2. 配置 Vitest 以使用浏览器模式
  3. 在您的项目中安装插件 @storybook/addon-vitest,并在您的 Storybook 配置中注册它
  4. 创建一个测试设置文件 .storybook/vitest.setup.ts。您可以使用示例设置文件作为指导。
  5. 调整您的 Vitest 配置,以包含该插件并引用设置文件。您可以使用示例配置文件作为指导。
    • 对于具有现有 Vitest 测试的项目,我们建议如果使用 Vitest ≥ 4.0,则使用单独的测试项目;如果使用 Vitest 3.x,则使用工作区文件来为您的 Storybook 测试和其他测试定义单独的配置。您可以根据需要单独或一起运行它们。

示例配置文件

当插件自动设置时,它会为您创建或调整 Vitest 的配置文件。如果您是手动设置,可以在配置项目时参考以下示例。

示例 Vitest 设置文件

Storybook 故事包含在 .storybook/preview.js|ts 中定义的配置。为确保您的测试可以访问该配置,您可以在 Vitest 设置文件中应用它。以下是执行此操作的示例:

.storybook/vitest.setup.ts
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import { setProjectAnnotations } from '@storybook/your-framework';
import * as previewAnnotations from './preview';
 
const annotations = setProjectAnnotations([previewAnnotations]);

setProjectAnnotations 函数是可移植故事 API 的一部分,Vitest 插件内部使用它将您的故事转换为测试。

示例 Vitest 配置文件

插件最简单的应用方式是将其包含在您的 Vitest 配置文件中。

vitest.config.ts
import { defineConfig, defineProject, mergeConfig } from 'vitest/config';
import { playwright } from '@vitest/browser-playwright';
 
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
 
import path from 'node:path';
import { fileURLToPath } from 'node:url';
 
const dirname = path.dirname(fileURLToPath(import.meta.url));
 
import viteConfig from './vite.config';
 
export default mergeConfig(
  viteConfig,
  defineConfig({
    test: {
      // Use `workspace` field in Vitest < 3.2
      projects: [
        defineProject({
          extends: true,
          plugins: [
            storybookTest({
              // The location of your Storybook config, main.js|ts
              configDir: path.join(dirname, '.storybook'),
              // This should match your package.json script to run Storybook
              // The --no-open flag will skip the automatic opening of a browser
              storybookScript: 'yarn storybook --no-open',
            }),
          ],
          test: {
            name: 'storybook',
            // Enable browser mode
            browser: {
              enabled: true,
              // Make sure to install Playwright
              provider: playwright({}),
              headless: true,
              instances: [{ browser: 'chromium' }],
            },
            setupFiles: ['./.storybook/vitest.setup.ts'],
          },
        }),
      ],
    },
  }),
);
示例 Vitest 工作区文件(Vitest < 3.2)

如果您正在使用Vitest 工作区,则可以定义一个新的工作区项目。

vitest.workspace.ts
import { defineWorkspace } from 'vitest/config';
import { storybookTest } from '@storybook/addon-vitest/vitest-plugin';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
 
const dirname = path.dirname(fileURLToPath(import.meta.url));
 
export default defineWorkspace([
  // This is the path to your existing Vitest config file
  './vitest.config.ts',
  {
    // This is the path to your existing Vite config file
    extends: './vite.config.ts',
    plugins: [
      storybookTest({
        // The location of your Storybook config, main.js|ts
        configDir: path.join(dirname, '.storybook'),
        // This should match your package.json script to run Storybook
        // The --ci flag will skip prompts and not open a browser
        storybookScript: 'yarn storybook --ci',
      }),
    ],
    test: {
      name: 'storybook',
      // Enable browser mode
      browser: {
        enabled: true,
        // Make sure to install Playwright
        provider: 'playwright',
        headless: true,
        instances: [{ browser: 'chromium' }],
      },
      setupFiles: ['./.storybook/vitest.setup.ts'],
    },
  },
]);

用法

有多种方法可以使用插件运行测试。

我们推荐(并且默认配置)在浏览器模式下运行 Vitest,使用Playwright 的 Chromium 浏览器。浏览器模式可确保您的组件在真实浏览器环境中进行测试,这比 JSDom 或 HappyDom 等模拟更准确。这对于测试依赖浏览器 API 或功能的组件尤其重要。

Storybook UI

运行测试的最简单方法是通过 Storybook UI。只需单击一下,即可为项目中的所有故事、一组故事或单个故事运行多种类型的测试。

要运行整个项目的全部测试,请按侧边栏底部的测试小部件中的“运行测试”按钮。

Screenshot of testing widget, expanded, with the Run tests button highlighted

或者,您可以展开测试小部件以单独运行特定类型的测试。组件测试下方的子类型将一起运行,包括在启用观察模式(带有眼睛图标)时(该模式将在代码更改时自动重新运行相关测试)。

Screenshot of testing widget, expanded, showing test types and watch mode toggle

如果您安装了视觉测试插件,您将看到一个选项,可以与组件测试一起运行视觉测试。

Screenshot of testing widget, expanded, showing Visual tests

其他插件,例如a11y,也可能提供可以从测试小部件运行的测试类型,并影响故事和组件的状态指示器。

要为特定故事或一组故事运行测试,请按鼠标悬停在侧边栏项目上时出现的菜单按钮(三个点)。然后,您可以选择要运行的测试类型。

Screenshot of story sidebar item with open menu

运行测试后,您将在故事和组件上看到它们通过、失败或错误状态的状态指示器。您可以按住鼠标悬停在故事上的菜单按钮来查看该故事的测试结果。在菜单中选择一个结果会将您导航到该故事并打开相应的调试面板。例如,如果交互测试失败,您可以直接跳转到“交互”面板中的失败处。该面板提供了一个交互式调试器,可让您逐步进行每个模拟行为或断言。

测试小部件还会显示运行的总测试数、通过的测试数以及失败或出错的测试数。您可以按失败数过滤侧边栏,只显示失败的故事。

CLI

您还可以使用 Vitest CLI 运行测试。我们建议在您的 package.json 中添加一个脚本,以便更轻松地运行测试。以下是执行此操作的示例:

package.json
{
  "scripts": {
    "test": "vitest",
    "test-storybook": "vitest --project=storybook"
  }
}

在此示例中,我们添加了两个脚本:test 用于运行项目中的所有测试(您可能已经有了),以及 test-storybook 用于仅运行 Storybook 测试。--project=storybook 标志告诉 Vitest 运行 Storybook 项目的测试。

然后,运行此命令以使用 Vitest CLI 运行您的测试(默认情况下处于观察模式):

npm run test-storybook

调试

虽然该插件在测试时不需要运行 Storybook,但您可能仍然希望运行 Storybook 来调试测试。为此,请在插件配置中提供storybookScript 选项。当您在观察模式下运行 Vitest 时,插件将使用此脚本启动 Storybook,并在测试失败时在输出中提供指向故事的链接。这使您可以快速跳转到 Storybook 中的故事来调试问题。

您还可以向插件配置提供storybookUrl 选项。当您不使用观察模式且测试失败时,插件将在输出中提供指向故事的链接。这在使用CI 中运行测试或其他 Storybook 未运行的环境中非常有用。

Screenshot of test failure in the console, showing a failure with a link to the story

编辑器扩展

通过插件将您的故事转换为 Vitest 测试,还可以让您使用 Vitest 的IDE 集成来运行和调试测试。这允许您直接从编辑器(如 VSCode 和 JetBrains IDE)运行测试。

此屏幕截图显示了如何使用Vitest 扩展在 VSCode 中运行 Vitest 测试。故事被注释了测试状态,当测试失败时,会提供指向故事的链接以供调试

Screenshot of test failure in VSCode, showing a failure attached to a story

在 CI 中

在大多数情况下,在 CI 中运行您的 Storybook 测试是通过CLI完成的。

但是,要使测试输出在测试失败时链接到您的已发布 Storybook,您需要在插件配置中提供storybookUrl 选项。请参考“CI 中测试”指南中的详细示例

工作原理

Vitest 插件通过使用 Vitest 插件将您的故事转换为Vitest 测试,使用可移植故事。它还配置 Vitest 在浏览器模式下运行这些测试,使用 Playwright 的 Chromium 浏览器。由于它是建立在 Vitest 之上的,因此该插件需要一个基于 Vite 的 Storybook 框架。

故事通过两种方式进行测试:一种是烟雾测试,以确保其渲染;如果定义了play 函数,则会运行该函数,并验证其中进行的任何断言

当您在Storybook UI中运行测试时,该插件会在后台运行 Vitest,并在侧边栏中报告结果。

配置测试

可以通过两种方式配置插件运行的测试。您可以切换要运行的测试类型,以及包含、排除或跳过故事不被测试。

切换测试类型

除了组件测试之外,Vitest 插件还支持多种测试类型,具体取决于您项目中使用的其他插件。有些测试类型,例如视觉测试,是独立运行的。其他类型,例如可访问性测试,必须与组件测试一起运行。对于这些依赖测试类型,您可以通过选中或取消选中要运行的测试类型来在测试小部件中打开或关闭它们。

Screenshot of testing widget, expanded, everything is checked

请注意,根据您安装的插件,您可能不会看到所有显示的测试类型。

包含、排除或跳过测试

您可以使用标签来包含、排除或跳过故事的测试。包含的故事将被测试,排除的故事不被测试,跳过的故事不被测试但会在测试结果中计算。

默认情况下,插件将运行所有带有test 标签的故事。您可以通过在插件配置中提供tags 选项来调整此行为。这允许您根据标签包含、排除或跳过故事。

在此示例中,我们将stable 标签应用于 Button 组件的所有故事,除了 ExperimentalFeatureStory,它将具有experimental 标签。

Button.stories.ts
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { Button } from './Button';
 
const meta = {
  component: Button,
  // 👇 Applies to all stories in this file
  tags: ['stable'],
} satisfies Meta<typeof Button>;
 
export default meta;
type Story = StoryObj<typeof meta>;
 
export const ExperimentalFeatureStory: Story = {
  //👇 For this particular story, remove the inherited `stable` tag and apply the `experimental` tag
  tags: ['!stable', 'experimental'],
};

为了将这些标签与我们的测试行为联系起来,我们可以调整插件配置以排除experimental 标签。

vitest.config.ts
export default defineConfig({
  // ...
  test: {
    // ...
    projects: [
      {
        plugins: [
          storybookTest({
            // ...
            tags: {
              include: ['test'],
              exclude: ['experimental'],
            },
          }),
        ],
        // ...
      },
    ],
  },
});

如果同一个标签同时存在于 includeexclude 数组中,则 exclude 行为具有优先权。

与测试运行器的比较

测试运行器需要一个正在运行的 Storybook 实例来测试您的故事,因为它会访问每个故事、执行 play 函数并监听结果。然而,Vitest 插件通过 Vite 和可移植故事将您的故事转换为测试,因此它不需要运行 Storybook 来测试您的故事。由于依赖于 Vite,该插件只能与使用 Vite 的 Storybook 框架(以及Next.js)一起使用。另一方面,测试运行器可以与任何 Storybook 框架一起使用。

功能Vitest 插件测试运行器
测试类型
- 交互测试
- 可访问性测试
- 视觉测试
- 快照测试
测试上下文
- Storybook UI
- 编辑器扩展
- CLI
- 在 CI 中
适用于所有 Storybook 框架❌ (需要 Vite)
在真实浏览器环境中运行测试
计算代码覆盖率✅ (使用 addon-coverage)
需要正在运行或已发布的 Storybook
可扩展到其他插件

测试运行器仅是一个 CLI 工具。它没有用于运行测试的 UI,也没有编辑器扩展。但是,该插件在 Storybook 中提供了用于运行测试的 UI,并使您能够使用 Vitest IDE 集成来运行和调试测试。

此外,测试运行器将您的故事作为 Jest 中的协调测试运行,并且该协调带来了一些复杂性。相比之下,此插件将您的故事转换为真实测试,然后使用 Vitest 运行它们,这更简单且更易于配置。

最后,由于更简单的架构和 Vitest 的使用,此插件对于大多数项目来说应该比测试运行器更快。我们将在未来进行更多基准测试以量化这一点。

常见问题解答

如果 Vitest 本身出错怎么办?

有时测试可能会因为 Vitest 本身内部的错误而失败。当这种情况发生时,Storybook UI 中的测试小部件会向您发出错误警报,您可以单击链接查看完整的错误。错误也会记录到控制台。

Screenshot of testing widget, expanded, showing Vitest error

Vitest 提供了常见错误的故障排除帮助

当多个环境中存在不同的测试结果时会发生什么?

当使用此插件运行测试时,这些测试将作为 Vitest 测试运行,并使用您在项目中设置的任何配置。默认情况下,它们将在浏览器模式下运行,使用 Playwright 的 Chromium 浏览器。有时,测试在插件(或 CLI)中运行会失败,但在“交互”面板中查看时会通过(反之亦然)。这可能是因为测试是在不同的环境中运行的,这些环境可能有不同的行为。

如何调试 Storybook 中的 CLI 测试?

为了调试目的,插件将尝试在 CLI 中测试失败时提供指向 Storybook 中故事的链接。

如果在观察模式下运行测试时 URL 不起作用,您应该检查两个配置选项:

  • storybookUrl:确保此 URL 正确且可访问。例如,默认值为 https://:6006,它可能不使用您正在使用的相同端口号。
  • storybookScript:确保此脚本正确启动 Storybook。

如果在 CI 中运行测试时 URL 不起作用,您应该确保 Storybook 在运行测试之前已构建并发布。然后,您可以使用 storybookUrl 选项提供已发布 Storybook 的 URL。请参阅“在 CI 中”部分以获取示例。

如何确保我的测试能找到公共目录中的资源?

如果您的故事使用公共目录中的资源,并且您没有使用默认的公共目录位置(public),则需要调整 Vitest 配置以包含公共目录。您可以通过在 Vitest 配置文件中提供publicDir 选项来实现此目的。

如何隔离 Storybook 测试与其他测试?

某些项目可能在其 Vite 配置中包含一个 test 属性。由于此插件使用的 Vitest 配置会扩展该 Vite 配置,因此 test 属性会被合并。这种缺乏隔离可能会导致 Storybook 测试出现问题。

要将您的 Storybook 测试与其他测试隔离开来,您需要将 test 属性从 Vite 配置移到 Vitest 配置。然后,插件使用的 Vitest 配置可以安全地扩展您的 Vite 配置,而不会合并 test 属性。

此外,我们建议使用测试项目(如果您使用 Vitest ≥ 4.0),或者使用工作区(对于早期版本),来为您的 Storybook 测试和其他测试定义单独的配置。这可确保每个都可以根据您的需要单独或一起运行。

为什么推荐浏览器模式?

Vitest 的浏览器模式在真实浏览器(默认配置中使用 Playwright 的 Chromium)中运行您的测试。替代方案是模拟浏览器环境,如 JSDom 或 HappyDom,它们在行为上可能与真实浏览器存在差异。对于 UI 组件,它们通常依赖于浏览器 API 或功能,因此在真实浏览器中运行测试更准确。

更多信息,请参阅 Vitest 关于有效使用浏览器模式的指南

如何使用 WebDriver 而不是 Playwright?

我们建议使用 Playwright 的浏览器运行测试,但您也可以使用 WebDriverIO。要做到这一点,您需要在 Vitest 配置文件中调整浏览器提供程序

如何使用 Chromium 以外的浏览器?

我们建议使用 Chromium,因为它最有可能最符合大多数用户的体验。但是,您可以通过调整 Vitest 配置文件中的浏览器名称来使用其他浏览器。请注意,Playwright 和 WebDriverIO 支持不同的浏览器

如何自定义测试名称?

默认情况下,故事的导出名称会映射到测试名称。要创建更具描述性的测试描述,您可以为故事提供 name 属性。这允许您包含空格、括号或其他特殊字符。

Example.stories.js|ts
export const Story = {
  name: 'custom, descriptive name'
};

如何修复 m.createRoot is not a function 错误?

在使用插件的非 18 版本 React 的项目上时,可能会出现此错误。为了解决此问题,您可以提供一个别名以确保使用正确的 React 版本。以下是在 Vitest 配置文件中执行此操作的示例:

vitest.config.ts
import { defineConfig } from 'vitest/config';
 
export default defineConfig({
  // ...
  resolve: {
    alias: {
      "@storybook/react-dom-shim": "@storybook/react-dom-shim/dist/react-16",
    },
  },
});

如何修复 Error: Vitest failed to find the current suite 错误?

如果您遇到此错误,通常不是 Vitest 的问题,而是与您的故事如何被转换有关。以下是故障排除步骤:

  1. 检查完整的错误日志以获取额外上下文,特别是关于故事转换的内容。
  2. 注意 Vite 依赖项优化警告(例如,“新优化依赖项:lodash”)
  3. 如果您看到依赖项优化警告,这可能导致测试期间的破坏性重新加载。

最常见的解决方案是预先优化您的依赖项。您可以通过将依赖项添加到 Vite 配置的optimizeDeps.include 数组中来实现。

这可以防止在测试过程中进行依赖项优化,这会干扰 Vitest 的测试套件管理。

API

导出

此插件具有以下导出:

import { storybookTest } from '@storybook/addon-vitest/vitest-plugin'

storybookTest

类型:function

一个Vitest 插件,用于将您的故事转换为测试。它接受一个选项对象进行配置。

选项

插件使用选项对象进行配置。以下是可用属性:

configDir

类型:string

默认值:.storybook

Storybook 配置所在的目录,相对于当前工作目录。

如果您的Storybook 配置不在默认位置,您必须在此处指定位置,以便插件能够正常工作。

storybookScript

类型:string

运行 Storybook 的可选脚本。如果提供,Vitest 将在观察模式下运行时使用此脚本启动 Storybook。仅当 storybookUrl 中的 Storybook 尚未可用时才运行。

storybookUrl

类型:string

默认值:https://:6006

Storybook 托管的 URL。这用于内部检查,并为在测试失败时在测试输出中链接到故事

tags

类型

{
  include: string[];
  exclude: string[];
  skip: string[];
}

Default

{
  include: ['test'],
  exclude: [],
  skip: [],
}

用于包含、排除或跳过标签。这些标签在您的故事、元数据或预览中定义为注释。

  • include:带有这些标签的故事将被测试
  • exclude:带有这些标签的故事将不会被测试,也不会计入测试结果
  • skip:带有这些标签的故事将不会被测试,但会计入测试结果

disableAddonDocs

类型:boolean

默认:true

在运行测试时是否禁用插件文档 MDX 解析。

当预览配置或故事导入 mdx 文件时,它们会被模拟,因为通常它们对于测试来说不是必需的。您可以在仅在故事实际需要读取和解析 MDX 文件作为组件渲染一部分的情况下,将 disableAddonDocs 设置为 false

更多测试资源