文档
Storybook Docs

Test addon

(⚠️ 实验性功能)

虽然此插件是实验性的,但它以 @storybook/experimental-addon-test 包发布,并且 API 在未来版本中可能会更改。我们欢迎反馈和贡献,以帮助改进此功能。

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

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

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

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

安装和设置

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

  • Storybook ≥ 8.5
  • 使用 Vite 的 Storybook 框架(例如 vue3-vite, react-vite, 'sveltekit` 等),或带有 Vite 的 Storybook Next.js 框架
  • Vitest ≥ 2.1
    • 如果您尚未使用 Vitest,则在安装插件时将为您安装和配置它
  • (可选)MSW ≥ 2.0
    • 如果安装了 MSW,则它必须是 v2.0.0 或更高版本,以免与 Vitest 的依赖项冲突

与 Next.js 一起使用 — Test addon 在 Next.js ≥ 14.1 项目中受支持,但您必须使用 @storybook/experimental-nextjs-vite 框架。 当您运行下面的设置命令时,如果您尚未安装和使用该框架,系统将提示您安装和使用。

如果您尚未使用 Storybook 8.5,您可以升级您的 Storybook到预发布版本

npx storybook@next upgrade

自动设置

运行以下命令来安装和配置插件,其中包含使用 Vitest 运行 stories 作为测试的插件

npx storybook add @storybook/experimental-addon-test

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

手动设置

对于某些项目设置,add 命令可能无法自动执行插件和插件设置,并要求您完成其他设置步骤。 以下是操作方法

  1. 确保在您的项目中配置了 Vite 和 Vitest。
  2. 配置 Vitest 以使用 浏览器模式
  3. 在您的项目中安装插件 @storybook/experimental-addon-test,并在您的 Storybook 配置中注册它
  4. 创建一个测试设置文件,.storybook/vitest.setup.ts。 您可以使用示例设置文件作为指南。
  5. 调整您的 Vitest 配置以包含插件并引用设置文件。 您可以使用示例配置文件作为指南。

示例配置文件

当插件自动设置时,它将为您创建或调整 Vitest 配置文件。 如果您要手动设置,则可以在配置项目时使用以下示例作为参考。

Vitest 设置文件示例

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

.storybook/vitest.setup.ts
import { beforeAll } from 'vitest';
// 👇 If you're using Next.js, import from @storybook/nextjs
//   If you're using Next.js with Vite, import from @storybook/experimental-nextjs-vite
import { setProjectAnnotations } from '@storybook/react';
import * as previewAnnotations from './preview';
 
const annotations = setProjectAnnotations([previewAnnotations]);
 
// Run Storybook's beforeAll hook
beforeAll(annotations.beforeAll);

setProjectAnnotations 函数是 portable stories API 的一部分,Vitest 插件在内部使用它将您的 stories 转换为测试。

Vitest 配置文件示例

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

vitest.config.ts
import { defineConfig, mergeConfig } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
 
const dirname =
  typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
 
import viteConfig from './vite.config';
 
export default mergeConfig(
  viteConfig,
  defineConfig({
    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: {
      // Enable browser mode
      browser: {
        enabled: true,
        name: 'chromium',
        // Make sure to install Playwright
        provider: 'playwright',
        headless: true,
      },
      setupFiles: ['./.storybook/vitest.setup.ts'],
    },
  })
);
Vitest 工作区文件示例

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

vitest.workspace.ts
import { defineWorkspace } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
 
const dirname =
  typeof __dirname !== 'undefined' ? __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,
        name: 'chromium',
        // Make sure to install Playwright
        provider: 'playwright',
        headless: true,
      },
      setupFiles: ['./.storybook/vitest.setup.ts'],
    },
  },
]);

用法

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

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

Storybook UI

运行测试的最简单方法是通过 Storybook UI。 单击一下,您可以为项目中的所有 stories、一组 stories 或单个 story 运行多种类型的测试。

要为您整个项目运行所有测试,请按侧边栏底部测试模块中的“运行测试”按钮。

或者,您可以展开测试模块以单独运行特定类型的测试。 对于那些具有观察模式的测试类型(将在代码更改时自动重新运行相关测试),您可以打开或关闭该模式。

Screenshot of test module, expanded, showing test types and watch mode toggle

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

Screenshot of test module, expanded, showing Visual tests

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

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

Screenshot of story sidebar item with open menu

运行测试后,您现在将在 stories 和组件上看到其通过、失败或错误状态的状态指示器。 您可以按悬停 story 时的菜单按钮,以查看该 story 的测试结果。 在菜单中选择结果会将您导航到该 story 并打开相应的调试面板。 例如,如果组件测试失败,您可以直接跳转到组件测试插件面板中的失败处。 该面板为您的组件测试提供了一个交互式调试器,允许您逐步执行每个模拟的行为或断言。

当安装 Test addon 时,组件测试插件面板将替换 Interactions addon 面板。 虽然测试机制不同,但插件面板本身的功能保持不变。

测试模块还将向您显示运行的测试总数、通过的测试数以及失败或出错的测试数。 您可以按失败次数来过滤侧边栏,使其仅显示那些失败的 stories。

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,并在测试失败时在输出中提供指向 story 的链接。 这使您可以快速跳转到 Storybook 中的 story 以调试问题。

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

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

编辑器扩展

使用插件将您的 stories 转换为 Vitest 测试还使您能够使用 Vitest IDE 集成来运行和调试测试。 这允许您直接从编辑器(例如 VSCode 和 JetBrains IDE)运行测试。

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

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

在 CI 中

在大多数情况下,在 CI 中运行 Storybook 测试是通过 CLI 完成的。 但是,为了使测试输出链接到已发布的 Storybook 以解决测试失败问题,您需要在插件配置中提供 storybookUrl 选项

这是一个使用 GitHub Actions 的示例。 这些步骤与其他 CI 提供程序类似,尽管语法或配置中的详细信息可能有所不同。

当 Vercel、Netlify 等服务的操作运行部署作业时,它们遵循发出包含 deployment_status.target_url 下新生成的 URL 的 deployment_status 事件的模式。 这是指向已发布的 Storybook 实例的 URL。 然后,我们使用环境变量 SB_URL 将该 URL 传递给插件配置。 最后,我们更新插件配置以在 storybookUrl 选项中使用该环境变量。

.github/workflows/test-storybook.yml
name: Storybook Tests
on: deployment_status
jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    if: github.event.deployment_status.state == 'success'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '18.x'
      - name: Install dependencies
        run: yarn
      - name: Run Storybook tests
        run: yarn test-storybook
        env:
          SB_URL: '${{ github.event.deployment_status.target_url }}'
vitest.workspace.ts
export default defineWorkspace([
  // ...
  {
    // ...
    {
      plugins: [
        storybookTest({
          // ...
          storybookScript: 'yarn storybook --ci',
          storybookUrl: process.env.SB_URL
        }),
      ],
    },
  },
])

工作原理

Test addon 的工作原理是使用 Vitest 插件将您的 stories 转换为 Vitest 测试,使用 portable stories。 它还将 Vitest 配置为在 浏览器模式下运行这些测试,使用 Playwright 的 Chromium 浏览器。 由于它构建在 Vitest 之上,因此该插件需要基于 Vite 的 Storybook 框架。

Stories 通过两种方式进行测试:冒烟测试以确保它可以渲染,以及是否定义了 play 函数,则运行该函数,并验证其中进行的任何断言

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

配置测试

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

切换测试类型

除了组件测试外,Test addon 还支持多种类型的测试,具体取决于您在项目中使用的其他插件。 某些测试类型(如视觉测试)是独立运行的。 其他测试类型(如可访问性)必须与组件测试一起运行。 对于这些依赖型测试类型,您可以通过按编辑按钮(铅笔图标)并在测试模块中选中或取消选中要运行的测试类型来打开或关闭它们。

Screenshot of test module, expanded, edit mode, everything is checked

请注意,您可能没有所有图片中的测试类型,具体取决于您安装的插件。

您还可以在 story 或 stories 组的侧边栏项目菜单中访问编辑模式

Screenshot of story sidebar item with open menu, edit mode

请注意,在菜单的编辑模式中切换测试类型会影响所有测试,而不仅仅是所选 story 或 stories 组的测试。 它旨在方便快速打开或关闭测试类型。

包含、排除或跳过测试

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

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

在此示例中,我们将 stable 标签应用于 Button 组件的所有 stories,但 ExperimentalFeatureStory 除外,它将具有 experimental 标签

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

要将这些标签连接到我们的测试行为,我们可以调整插件配置以排除 experimental 标签

vitest.workspace.ts
export default defineWorkspace([
  // ...
  {
    // ...
    {
      plugins: [
        storybookTest({
          // ...
          tags: {
            include: ['test'],
            exclude: ['experimental'],
          },
        }),
      ],
    },
  },
])

如果同一个标签同时位于 includeexclude 数组中,则 exclude 行为优先。

与测试运行器的比较

测试运行器 不同,Vitest 插件将你的 stories 转换为使用 Vite 和可移植 stories 的测试,因此它不需要运行 Storybook 来测试你的 stories。由于这种对 Vite 的依赖,该插件只能与使用 Vite 的 Storybook 框架(以及 Next.js)一起使用。另一方面,测试运行器可以与任何 Storybook 框架一起使用。

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

此外,测试运行器在 Jest 中将你的 stories 作为编排测试运行,而这种编排带来了一些复杂性。相比之下,此插件将你的 stories 转换为真正的测试,然后使用 Vitest 运行它们,这更简单且更可配置。

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

常见问题

如果 Vitest 本身发生错误会怎样?

有时,测试可能会由于 Vitest 本身内部的错误而失败。发生这种情况时,Storybook UI 中的测试模块将提醒你注意错误,你可以单击链接以查看完整错误信息。错误也会记录到控制台中。

Screenshot of test module, expanded, showing Vitest error

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

当在多个环境中出现不同的测试结果时会怎样?

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

如何在 Storybook 中调试我的 CLI 测试?

当 CLI 中的测试失败时,插件将尝试提供指向 Storybook 中 story 的链接,以用于调试目的。

如果 URL 在监视模式下运行测试时不起作用,你应该检查两个配置选项

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

如果 URL 在 CI 中运行测试时不起作用,你应该确保在运行测试之前构建和发布 Storybook。然后,你可以使用 storybookUrl 选项提供指向已发布的 Storybook 的 URL。有关示例,请参阅 In CI 部分。

如何确保我的测试可以在 public 目录中找到资源?

如果你的 stories 使用 public 目录中的资源,并且你没有使用默认的 public 目录位置 (public),你需要调整 Vitest 配置以包含 public 目录。你可以通过在 Vitest 配置文件中提供 publicDir 选项来做到这一点。

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

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

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

为什么我们推荐浏览器模式?

Vitest 的浏览器模式在真实的浏览器中(默认配置下通过 Playwright 在 Chromium 中)运行你的测试。另一种选择是模拟浏览器环境,例如 JSDom 或 HappyDom,与真实浏览器相比,它们在行为上可能存在差异。对于 UI 组件,它们通常依赖于浏览器 API 或功能,在真实浏览器中运行测试更准确。

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

如何使用 WebDriver 而不是 Playwright?

我们建议使用 Playwright 在浏览器中运行测试,但你可以改用 WebDriverIO。为此,你需要调整 Vitest 配置文件中的浏览器提供程序

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

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

如何自定义测试名称?

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

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

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

当在使用 React 版本 18 以外版本的项目中使用插件时,可能会发生此错误。要解决此问题,你可以提供别名以确保使用正确的 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",
    },
  },
});

API

导出

此插件具有以下导出

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

storybookTest

类型: function

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

选项

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

configDir

类型: string

默认值: .storybook

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

如果你的 Storybook 配置 不在默认位置,你必须在此处指定位置,以便插件可以正常运行。

storybookScript

类型: string

用于运行 Storybook 的可选脚本。如果提供,Vitest 将在监视模式下运行时使用此脚本启动 Storybook。仅当 storybookUrl 中的 Storybook 尚不可用时运行。

storybookUrl

类型: string

默认值: https://127.0.0.1:6006

Storybook 托管的 URL。这用于内部检查,并提供一个 指向测试失败时 story 的链接

tags

类型

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

默认值

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

标签,用于包含、排除或跳过。这些标签在你的 story、meta 或 preview 中定义为注解。

  • include:具有这些标签的 stories 将被测试
  • exclude:具有这些标签的 stories 将不会被测试,并且不会计入测试结果
  • skip:具有这些标签的 stories 将不会被测试,并且将计入测试结果