参加直播会话:美国东部时间周四上午 11 点,Storybook 9 版本发布及问答
文档
Storybook Docs

测试运行器

测试运行器已被 Vitest 插件取代,后者提供相同功能,由更快、更现代的 Vitest 浏览器模式驱动。它还启用完整的 Storybook 测试体验,允许您从 Storybook 应用程序运行交互、无障碍和可视化测试。

如果您正在使用基于 Vite 的 Storybook 框架,建议使用 Vitest 插件而不是测试运行器。

Storybook 测试运行器将您所有的 Story 转换为可执行测试。它由 JestPlaywright 提供支持。

  • 对于没有 play 函数的 Story:它验证 Story 是否渲染无误。
  • 对于带有 play 函数的 Story:它还会检查 play 函数中的错误,并确保所有断言都通过。

这些测试在实时浏览器中运行,可以通过命令行或您的CI 服务器执行。

设置

测试运行器是一个独立的、框架无关的实用工具,与 Storybook 并行运行。您需要采取一些额外步骤来正确设置它。下面详细介绍了我们的配置和执行建议。

运行以下命令来安装它。

npm install @storybook/test-runner --save-dev

更新您的 package.json 脚本并启用测试运行器。

package.json
{
  "scripts": {
    "test-storybook": "test-storybook"
  }
}

启动您的 Storybook:

npm run storybook

Storybook 的测试运行器需要本地运行的 Storybook 实例或已发布的 Storybook 才能运行所有现有测试。

最后,打开一个新的终端窗口并运行测试运行器:

npm run test-storybook

配置

测试运行器为 Storybook 提供零配置支持。但是,您可以运行 test-storybook --eject 以获得更精细的控制。它会在项目根目录生成一个 test-runner-jest.config.js 文件,您可以对其进行修改。此外,您可以扩展生成的配置文件并提供 testEnvironmentOptions,因为测试运行器底层也使用了 jest-playwright

CLI 选项

测试运行器由 Jest 提供支持,并接受其一部分CLI 选项(例如,--watch--maxWorkers)。如果您已经在项目中使用任何这些标志,应该可以毫无问题地将它们迁移到 Storybook 的测试运行器中。下面列出了所有可用的标志及其使用示例。

选项描述
--help输出使用信息
test-storybook --help
-s, --index-json在 index json 模式下运行。自动检测(需要兼容的 Storybook)
test-storybook --index-json
--no-index-json禁用 index json 模式
test-storybook --no-index-json
-c, --config-dir [dir-name]加载 Storybook 配置的目录
test-storybook -c .storybook
--watch在监视模式下运行
test-storybook --watch
--watchAll监视文件更改,并在更改时重新运行所有测试。
test-storybook --watchAll
--coverage对您的 stories 和组件运行覆盖率测试
test-storybook --coverage
--coverageDirectory写入覆盖率报告输出的目录
test-storybook --coverage --coverageDirectory coverage/ui/storybook
--url定义运行测试的 URL。对于自定义 Storybook URL 很有用
test-storybook --url http://the-storybook-url-here.com
--browsers定义运行测试的浏览器。一个或多个:chromium, firefox, webkit
test-storybook --browsers firefox chromium
--maxWorkers [数量]指定工作池为运行测试将生成的最大工作进程数
test-storybook --maxWorkers=2
--testTimeout [数量]定义一个测试在被自动标记为失败之前可以运行的最大毫秒数。对于长时间运行的测试很有用
test-storybook --testTimeout=60000
--no-cache禁用缓存
test-storybook --no-cache
--clearCache删除 Jest 缓存目录,然后退出而不运行测试
test-storybook --clearCache
--verbose显示带有测试套件层级的单个测试结果
test-storybook --verbose
-u, --updateSnapshot使用此标志重新记录在此次测试运行中失败的每个快照
test-storybook -u
--eject创建本地配置文件以覆盖测试运行器的默认设置
test-storybook --eject
--json以 JSON 格式打印测试结果。此模式会将所有其他测试输出和用户消息发送到标准错误流。
test-storybook --json
--outputFile当同时指定 --json 选项时,将测试结果写入文件。
test-storybook --json --outputFile results.json
--junit指示应将测试信息报告到 junit 文件中。
test-storybook --**junit**
--ci与自动存储新快照的常规行为不同,它会使测试失败,并要求使用 --updateSnapshot 运行 Jest。
test-storybook --ci
--shard [索引/总数]需要 CI。将测试套件执行拆分到多台机器上
test-storybook --shard=1/8
--failOnConsole使测试在浏览器控制台出错时失败
test-storybook --failOnConsole
--includeTags实验性功能
如果故事与启用的标签匹配,则定义要测试的故事子集。
test-storybook --includeTags="test-only, pages"
--excludeTags实验性功能
如果故事与提供的标签匹配,则阻止对其进行测试。
test-storybook --excludeTags="no-tests, tokens"
--skipTags实验性功能
配置测试运行器跳过对与提供的标签匹配的故事运行测试。
test-storybook --skipTags="skip-test, layout"
npm run test-storybook -- --watch

针对已部署的 Storybook 运行测试

默认情况下,测试运行器假定您针对本地运行在端口 6006 上的 Storybook 运行测试。如果您想定义一个目标 URL 来针对已部署的 Storybook 运行,可以使用 --url 标志

npm run test-storybook -- --url https://the-storybook-url-here.com

或者,您可以设置 TARGET_URL 环境变量并运行测试运行器

TARGET_URL=https://the-storybook-url-here.com yarn test-storybook

运行无障碍测试

当您安装了 无障碍插件 后,可以使用测试运行器与您的交互测试一起运行无障碍测试。

更多详情,包括配置选项,请参阅无障碍测试文档

运行快照测试

快照测试是验证边缘情况(如错误)是否正确处理的有用工具。它还可以用于验证组件的渲染输出在不同测试运行中是否一致。

设置

要使用测试运行器启用快照测试,您需要采取额外步骤来正确设置它。

在您的 Storybook 目录中添加一个新的配置文件,并在其中包含以下内容

.storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';
 
const config: TestRunnerConfig = {
  async postVisit(page, context) {
    // the #storybook-root element wraps the story. In Storybook 6.x, the selector is #root
    const elementHandler = await page.$('#storybook-root');
    const innerHTML = await elementHandler.innerHTML();
    expect(innerHTML).toMatchSnapshot();
  },
};
 
export default config;

postVisit 钩子允许您扩展测试运行器的默认配置。在此了解更多

当您执行测试运行器时(例如,使用 yarn test-storybook),它将遍历所有 Story 并运行快照测试,在项目的 __snapshots__ 目录中为每个 Story 生成一个快照文件。

配置

开箱即用,测试运行器提供内置的快照测试配置,涵盖大多数用例。您还可以通过 test-storybook --eject 或在项目根目录创建 test-runner-jest.config.js 文件来微调配置以满足您的需求。

覆盖默认快照目录

默认情况下,测试运行器使用特定的命名约定和路径来生成快照文件。如果您需要自定义快照目录,可以定义一个自定义快照解析器来指定快照的存储目录。

创建一个 snapshot-resolver.js 文件来实现自定义快照解析器

./snapshot-resolver.js
import path from 'path';
 
export default {
  resolveSnapshotPath: (testPath) => {
    const fileName = path.basename(testPath);
    const fileNameWithoutExtension = fileName.replace(/\.[^/.]+$/, '');
    // Defines the file extension for the snapshot file
    const modifiedFileName = `${fileNameWithoutExtension}.snap`;
 
    // Configure Jest to generate snapshot files using the following convention (./src/test/__snapshots__/Button.stories.snap)
    return path.join('./src/test/__snapshots__', modifiedFileName);
  },
  resolveTestPath: (snapshotFilePath, snapshotExtension) =>
    path.basename(snapshotFilePath, snapshotExtension),
  testPathForConsistencyCheck: 'example',
};

更新 test-runner-jest.config.js 文件并启用 snapshotResolver 选项以使用自定义快照解析器

./test-runner-jest.config.js
import { getJestConfig } from '@storybook/test-runner';
 
const defaultConfig = getJestConfig();
 
const config = {
  // The default Jest configuration comes from @storybook/test-runner
  ...defaultConfig,
  snapshotResolver: './snapshot-resolver.js',
};
 
export default config;

当测试运行器执行时,它将遍历所有 Story 并运行快照测试,在您指定的自定义目录中为项目中的每个 Story 生成一个快照文件。

自定义快照序列化

默认情况下,测试运行器使用 jest-serializer-html 来序列化 HTML 快照。如果您使用特定的 CSS-in-JS 库(如 Emotion)、Angular 的 ng 属性或生成基于哈希的 CSS 类标识符的类似库,这可能会导致问题。如果您需要自定义快照的序列化,可以定义一个自定义快照序列化器来指定快照如何序列化。

创建一个 snapshot-serializer.js 文件来实现自定义快照序列化器

./snapshot-serializer.js
// The jest-serializer-html package is available as a dependency of the test-runner
const jestSerializerHtml = require('jest-serializer-html');
 
const DYNAMIC_ID_PATTERN = /"react-aria-\d+(\.\d+)?"/g;
 
module.exports = {
  /*
   * The test-runner calls the serialize function when the test reaches the expect(SomeHTMLElement).toMatchSnapshot().
   * It will replace all dynamic IDs with a static ID so that the snapshot is consistent.
   * For instance, from <label id="react-aria970235672-:rl:" for="react-aria970235672-:rk:">Favorite color</label> to <label id="react-mocked_id" for="react-mocked_id">Favorite color</label>
   */
  serialize(val) {
    const withFixedIds = val.replace(DYNAMIC_ID_PATTERN, 'mocked_id');
    return jestSerializerHtml.print(withFixedIds);
  },
  test(val) {
    return jestSerializerHtml.test(val);
  },
};

更新 test-runner-jest.config.js 文件并启用 snapshotSerializers 选项以使用自定义快照解析器

./test-runner-jest.config.js
import { getJestConfig } from '@storybook/test-runner';
 
const defaultConfig = getJestConfig();
 
const config = {
  ...defaultConfig,
  snapshotSerializers: [
    // Sets up the custom serializer to preprocess the HTML before it's passed onto the test-runner
    './snapshot-serializer.js',
    ...defaultConfig.snapshotSerializers,
  ],
};
 
export default config;

当测试运行器执行您的测试时,它会内省生成的 HTML,在对组件进行快照之前,用自定义序列化器文件中正则表达式提供的静态属性替换动态生成的属性。这确保了快照在不同测试运行中保持一致。

生成代码覆盖率

Storybook 还提供了一个覆盖率插件。它由 Istanbul 提供支持,后者为 JavaScript 生态系统中最常用的框架和构建器提供了开箱即用的代码插桩。

设置

覆盖率插件设计用于与现代测试工具(例如,Playwright)协同工作,它会自动为您的代码进行插桩并生成代码覆盖率数据。为了获得最佳体验,我们建议将测试运行器与覆盖率插件一起使用来运行测试。

运行以下命令来安装插件。

npx storybook@latest add @storybook/addon-coverage

CLI 的 add 命令可自动执行插件的安装和设置。要手动安装,请参阅我们的文档,了解如何安装插件。

启动您的 Storybook:

npm run storybook

最后,打开一个新的终端窗口并运行测试运行器:

npm run test-storybook -- --coverage

配置

默认情况下,@storybook/addon-coverage 为 Storybook 提供零配置支持,并通过 istanbul-lib-instrumentWebpack,或通过 vite-plugin-istanbulVite 进行代码插桩。但是,您可以扩展 Storybook 配置文件(即 .storybook/main.js|ts)并为插件提供额外选项。下面列出了按构建器划分的可用选项及其使用示例。

.storybook/main.ts
// For Vite support add the following import
// import type { AddonOptionsVite } from '@storybook/addon-coverage';
 
import type { AddonOptionsWebpack } from '@storybook/addon-coverage';
 
// Replace your-framework with the framework and builder you are using (e.g., react-webpack5, vue3-webpack5)
import type { StorybookConfig } from '@storybook/your-framework';
 
const coverageConfig: AddonOptionsWebpack = {
  istanbul: {
    include: ['**/stories/**'],
    exclude: ['**/exampleDirectory/**'],
  },
};
 
const config: StorybookConfig = {
  stories: [],
  addons: [
    // Other Storybook addons
    {
      name: '@storybook/addon-coverage',
      options: coverageConfig,
    },
  ],
};
 
export default config;
Vite 选项
选项描述类型
checkProd配置插件以跳过生产环境中的插桩
options: { istanbul: { checkProd: true,}}
boolean
cwd配置覆盖率测试的工作目录。
默认为 process.cwd()
options: { istanbul: { cwd: process.cwd(),}}
string
cypressVITE_COVERAGE 环境变量替换为 CYPRESS_COVERAGE
需要 Cypress 的代码覆盖率
options: { istanbul: { cypress: true,}}
boolean
exclude用提供的文件或目录列表覆盖默认排除列表,以从覆盖范围中排除
options: { istanbul: { exclude: ['**/stories/**'],}}
Array<String> or string
extension用提供的文件扩展名列表扩展默认扩展名列表,以包含在覆盖范围中
options: { istanbul: { extension: ['.js', '.cjs', '.mjs'],}}
Array<String> or string
forceBuildInstrument配置插件在构建模式下添加插桩
options: { istanbul: { forceBuildInstrument: true,}}
boolean
include选择要收集覆盖率的文件
options: { istanbul: { include: ['**/stories/**'],}}
Array<String> or string
nycrcPath定义现有 nyc 配置文件的相对路径
options: { istanbul: { nycrcPath: '../nyc.config.js',}}
string
requireEnv通过授予对 env 变量的访问权限,覆盖 VITE_COVERAGE 环境变量的值
options: { istanbul: { requireEnv: true,}}
boolean
Webpack 5 选项
选项描述类型
autoWrap通过将程序代码包装在一个函数中,为顶层 return 语句提供支持
options: { istanbul: { autoWrap: true,}}
boolean
compact压缩插桩代码的输出。有助于调试
options: { istanbul: { compact: false,}}
boolean
coverageVariable定义 Istanbul 用于存储覆盖率结果的全局变量名
options: { istanbul: { coverageVariable: '__coverage__',}}
string
cwd配置覆盖率测试的工作目录。
默认为 process.cwd()
options: { istanbul: { cwd: process.cwd(),}}
string
debug启用调试模式,以便在插桩过程中获取额外的日志信息
options: { istanbul: { debug: true,}}
boolean
esModules启用对 ES Module 语法的支持
options: { istanbul: { esModules: true,}}
boolean
exclude用提供的文件或目录列表覆盖默认排除列表,以从覆盖范围中排除
options: { istanbul: { exclude: ['**/stories/**'],}}
Array<String> or string
extension用提供的文件扩展名列表扩展默认扩展名列表,以包含在覆盖范围中
options: { istanbul: { extension: ['.js', '.cjs', '.mjs'],}}
Array<String> or string
include选择要收集覆盖率的文件
options: { istanbul: { include: ['**/stories/**'],}}
Array<String> or string
nycrcPath定义现有 nyc 配置文件的相对路径
options: { istanbul: { nycrcPath: '../nyc.config.js',}}
string
preserveComments在插桩代码中包含注释
options: { istanbul: { preserveComments: true,}}
boolean
produceSourceMap配置 Instanbul 为插桩代码生成 source map
options: { istanbul: { produceSourceMap: true,}}
boolean
sourceMapUrlCallback定义一个回调函数,当生成 source map 时,该函数会接收文件名和 source map URL 作为参数被调用
options: { istanbul: { sourceMapUrlCallback: (filename, url) => {},}}
function

其他覆盖率报告工具有何用途?

开箱即用,代码覆盖率测试与 Storybook 的测试运行器和 @storybook/addon-coverage 无缝协同工作。但这并不意味着您不能使用其他报告工具(例如,Codecov)。例如,如果您使用 LCOV,可以使用生成的输出(在 coverage/storybook/coverage-storybook.json 中)并使用以下命令创建您自己的报告:

npx nyc report --reporter=lcov -t coverage/storybook --report-dir coverage/storybook

设置 CI 以运行测试

您还可以配置测试运行器在 CI 环境中运行测试。下面记录了一些帮助您入门的示例。

通过 Github Actions 部署针对已部署的 Storybook 运行测试

如果您使用 VercelNetlify 等服务发布 Storybook,它们会在 GitHub Actions 中发出 deployment_status 事件。您可以使用此事件并将 deployment_status.target_url 设置为 TARGET_URL 环境变量。以下是操作方法:

.github/workflows/storybook-tests.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-file: '.nvmrc'
      - name: Install dependencies
        run: yarn
      - name: Install Playwright
        run: npx playwright install --with-deps
      - name: Run Storybook tests
        run: yarn test-storybook
        env:
          TARGET_URL: '${{ github.event.deployment_status.target_url }}'

已发布的 Storybook 必须公开可用才能使此示例工作。如果需要身份验证,我们建议使用以下示例运行测试服务器。

针对未部署的 Storybook 运行测试

您可以使用 CI 提供商(例如,GitHub ActionsGitLab PipelinesCircleCI)构建 Storybook 并针对构建好的 Storybook 运行测试运行器。这是一个依赖于第三方库(即 concurrentlyhttp-serverwait-on)的示例,用于构建 Storybook 并使用测试运行器运行测试。

.github/workflows/storybook-tests.yml
name: 'Storybook Tests'
 
on: push
 
jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'
      - name: Install dependencies
        run: yarn
      - name: Install Playwright
        run: npx playwright install --with-deps
      - name: Build Storybook
        run: yarn build-storybook --quiet
      - name: Serve Storybook and run tests
        run: |
          npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
            "npx http-server storybook-static --port 6006 --silent" \
            "npx wait-on tcp:127.0.0.1:6006 && yarn test-storybook"

默认情况下,Storybook 会将构建输出到 storybook-static 目录。如果您使用不同的构建目录,则需要相应地调整示例。

高级配置

测试钩子 API

测试运行器会渲染一个 Story 并执行其play 函数(如果存在)。然而,有些行为是无法通过在浏览器中执行的 play 函数实现的。例如,如果您希望测试运行器为您截取可视化快照,这可以通过 Playwright/Jest 实现,但必须在 Node 中执行。

测试运行器导出了一些测试钩子,这些钩子可以全局覆盖,以启用可视化或 DOM 快照等用例。这些钩子让您可以在 Story 渲染的之前之后访问测试生命周期。下面列出了可用的钩子及其使用概览。

钩子描述
prepare准备浏览器以进行测试
async prepare({ page, browserContext, testRunnerConfig }) {}
setup在所有测试运行前执行一次
setup() {}
preVisit在 Story 初次访问并在浏览器中渲染前执行
async preVisit(page, context) {}
postVisit在 Story 访问并完全渲染后执行
async postVisit(page, context) {}

这些测试钩子是实验性的,可能会发生破坏性更改。我们鼓励您尽可能在 Story 的play 函数中进行测试。

要启用钩子 API,您需要在 Storybook 目录中添加一个新的配置文件,并按如下方式进行设置:

.storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';
 
const config: TestRunnerConfig = {
  // Hook that is executed before the test runner starts running tests
  setup() {
    // Add your configuration here.
  },
  /* Hook to execute before a story is initially visited before being rendered in the browser.
   * The page argument is the Playwright's page object for the story.
   * The context argument is a Storybook object containing the story's id, title, and name.
   */
  async preVisit(page, context) {
    // Add your configuration here.
  },
  /* Hook to execute after a story is visited and fully rendered.
   * The page argument is the Playwright's page object for the story
   * The context argument is a Storybook object containing the story's id, title, and name.
   */
  async postVisit(page, context) {
    // Add your configuration here.
  },
};
 
export default config;

除了 setup 函数外,所有其他函数都是异步运行的。preVisitpostVisit 函数都包含两个额外参数:Playwright page 和一个上下文对象,其中包含 Story 的 idtitlename

当测试运行器执行时,您现有的测试将经历以下生命周期:

  • setup 函数在所有测试运行之前执行。
  • 生成包含所需信息的上下文对象。
  • Playwright 导航到 Story 的页面。
  • preVisit 函数执行。
  • Story 被渲染,并执行所有现有的 play 函数。
  • postVisit 函数执行。

(实验性) 过滤测试

默认情况下,当您在 Storybook 上运行测试运行器时,它会测试每个 Story。但是,如果要过滤测试,可以使用 tags 配置选项。Storybook 最初引入此功能是为了为 stories 生成自动文档。但可以进一步扩展它,使用类似的配置选项或通过 CLI 标志(例如,--includeTags--excludeTags--skipTags,仅在最新稳定版本 (0.15 或更高) 中可用)配置测试运行器根据提供的标签运行测试。下面列出了可用的选项及其使用概览。

选项描述
exclude如果 Story 与提供的标签匹配,则阻止对其进行测试。
include定义仅当 Story 与启用的标签匹配时才进行测试的故事子集。
skip如果 Story 与提供的标签匹配,则跳过对其进行测试。
.storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';
 
const config: TestRunnerConfig = {
  tags: {
    include: ['test-only', 'pages'],
    exclude: ['no-tests', 'tokens'],
    skip: ['skip-test', 'layout'],
  },
};
 
export default config;

使用 CLI 标志运行测试优先于配置文件中提供的选项,并会覆盖配置文件中的可用选项。

禁用测试

如果您想阻止测试运行器测试特定 Story,可以使用自定义标签配置您的 Story,在测试运行器配置文件中启用它,或者使用 --excludeTags CLI 标志运行测试运行器并将其从测试中排除。当您想排除尚未准备好进行测试或与您的测试无关的 Story 时,这很有用。例如:

MyComponent.stories.ts|tsx
// Replace your-framework with the name of your framework
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { MyComponent } from './MyComponent';
 
const meta: Meta<typeof MyComponent> = {
  component: MyComponent,
  tags: ['no-tests'], // 👈 Provides the `no-tests` tag to all stories in this file
};
 
export default meta;
type Story = StoryObj<typeof MyComponent>;
 
export const ExcludeStory: Story = {
  //👇 Adds the `no-tests` tag to this story to exclude it from the tests when enabled in the test-runner configuration
  tags: ['no-tests'],
};

对 Story 子集运行测试

要让测试运行器仅对特定 Story 或 Story 子集运行测试,您可以使用自定义标签配置 Story,在测试运行器配置文件中启用它,或使用 --includeTags CLI 标志运行测试运行器并将其包含在测试中。例如,如果您想基于 test-only 标签运行测试,可以按如下方式调整配置:

MyComponent.stories.ts|tsx
// Replace your-framework with the name of your framework
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { MyComponent } from './MyComponent';
 
const meta: Meta<typeof MyComponent> = {
  component: MyComponent,
  tags: ['test-only'], // 👈 Provides the `test-only` tag to all stories in this file
};
 
export default meta;
type Story = StoryObj<typeof MyComponent>;
 
export const IncludeStory: Story = {
  //👇 Adds the `test-only` tag to this story to be included in the tests when enabled in the test-runner configuration
  tags: ['test-only'],
};

为组件的 Story 应用标签应在组件级别(使用 meta)或 Story 级别完成。在 Story 之间导入标签在 Storybook 中不受支持,并且不会按预期工作。

跳过测试

如果您想跳过对特定 Story 或 Story 子集运行测试,可以使用自定义标签配置您的 Story,在测试运行器配置文件中启用它,或使用 --skipTags CLI 标志运行测试运行器。使用此选项运行测试将导致测试运行器忽略它们并在测试结果中相应地标记它们,表明测试已暂时禁用。例如:

MyComponent.stories.ts|tsx
// Replace your-framework with the name of your framework
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { MyComponent } from './MyComponent';
 
const meta: Meta<typeof MyComponent> = {
  component: MyComponent,
  tags: ['skip-test'], // 👈 Provides the `skip-test` tag to all stories in this file
};
 
export default meta;
type Story = StoryObj<typeof MyComponent>;
 
export const SkipStory: Story = {
  //👇 Adds the `skip-test` tag to this story to allow it to be skipped in the tests when enabled in the test-runner configuration
  tags: ['skip-test'],
};

已部署 Storybook 的身份验证

如果您使用需要身份验证的安全托管服务提供商来托管 Storybook,您可能需要设置 HTTP 头部。这主要是因为测试运行器通过 fetch 请求和 Playwright 检查实例的状态及其 Story 的索引。要执行此操作,您可以修改测试运行器配置文件以包含 getHttpHeaders 函数。此函数接受 fetch 调用和页面访问的 URL 作为输入,并返回一个包含需要设置的头部的对象。

.storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';
 
const config: TestRunnerConfig = {
  getHttpHeaders: async (url) => {
    const token = url.includes('prod') ? 'prod-token' : 'dev-token';
    return {
      Authorization: `Bearer ${token}`,
    };
  },
};
 
export default config;

辅助函数

测试运行器导出了一些辅助函数,您可以通过访问 Storybook 的内部属性(例如,argsparameters)来使您的测试更具可读性和可维护性。下面列出了可用的辅助函数及其使用概览。

.storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';
import { getStoryContext, waitForPageReady } from '@storybook/test-runner';
 
const config: TestRunnerConfig = {
  // Hook that is executed before the test runner starts running tests
  setup() {
    // Add your configuration here.
  },
  /* Hook to execute before a story is initially visited before being rendered in the browser.
   * The page argument is the Playwright's page object for the story.
   * The context argument is a Storybook object containing the story's id, title, and name.
   */
  async preVisit(page, context) {
    // Add your configuration here.
  },
  /* Hook to execute after a story is visited and fully rendered.
   * The page argument is the Playwright's page object for the story
   * The context argument is a Storybook object containing the story's id, title, and name.
   */
  async postVisit(page, context) {
    // Get the entire context of a story, including parameters, args, argTypes, etc.
    const storyContext = await getStoryContext(page, context);
 
    // This utility function is designed for image snapshot testing. It will wait for the page to be fully loaded, including all the async items (e.g., images, fonts, etc.).
    await waitForPageReady(page);
 
    // Add your configuration here.
  },
};
 
export default config;

使用测试运行器访问 Story 信息

如果您需要访问有关 Story 的信息,例如其参数,测试运行器包含一个名为 getStoryContext 的辅助函数,您可以使用它来检索信息。然后,您可以根据需要进一步自定义您的测试。例如,如果您需要配置 Playwright 页面视口大小以使用 Story 参数中定义的视口大小,可以按如下方式进行:

.storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';
import { getStoryContext } from '@storybook/test-runner';
 
const { MINIMAL_VIEWPORTS } = require('storybook/viewport');
 
const DEFAULT_VIEWPORT_SIZE = { width: 1280, height: 720 };
 
const config: TestRunnerConfig = {
  async preVisit(page, story) {
    // Accesses the story's parameters and retrieves the viewport used to render it
    const context = await getStoryContext(page, story);
    const viewportName = context.parameters?.viewport?.defaultViewport;
    const viewportParameter = MINIMAL_VIEWPORTS[viewportName];
 
    if (viewportParameter) {
      const viewportSize = Object.entries(viewportParameter.styles).reduce(
        (acc, [screen, size]) => ({
          ...acc,
          // Converts the viewport size from percentages to numbers
          [screen]: parseInt(size),
        }),
        {},
      );
      // Configures the Playwright page to use the viewport size
      page.setViewportSize(viewportSize);
    } else {
      page.setViewportSize(DEFAULT_VIEWPORT_SIZE);
    }
  },
};
 
export default config;

处理资源

如果您正在运行一组特定的测试(例如,图像快照测试),测试运行器提供了一个名为 waitForPageReady 的辅助函数,您可以使用它来确保页面完全加载并准备就绪,然后再运行测试。例如:

.storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';
 
import { waitForPageReady } from '@storybook/test-runner';
 
import { toMatchImageSnapshot } from 'jest-image-snapshot';
 
const customSnapshotsDir = `${process.cwd()}/__snapshots__`;
 
const config: TestRunnerConfig = {
  setup() {
    expect.extend({ toMatchImageSnapshot });
  },
  async postVisit(page, context) {
    // Awaits for the page to be loaded and available including assets (e.g., fonts)
    await waitForPageReady(page);
 
    // Generates a snapshot file based on the story identifier
    const image = await page.screenshot();
    expect(image).toMatchImageSnapshot({
      customSnapshotsDir,
      customSnapshotIdentifier: context.id,
    });
  },
};
 
export default config;

Index.json 模式

在测试本地 Storybook 时,测试运行器会将您的 story 文件转换为测试。对于远程 Storybook,它使用 Storybook 的 index.json(以前称为 stories.json)文件(所有 stories 的静态索引)来运行测试。

为何?

假设您遇到本地和远程 Storybook 不同步的情况,或者您甚至无法访问代码。在这种情况下,index.json 文件可以保证最准确地表示您正在测试的已部署 Storybook。要使用此功能测试本地 Storybook,请使用 --index-json 标志,如下所示:

npm run test-storybook -- --index-json

index.json 模式与 watch 模式不兼容。

如果您需要禁用它,请使用 --no-index-json 标志

npm run test-storybook -- --no-index-json

如何检查我的 Storybook 是否有 index.json 文件?

Index.json 模式需要一个 index.json 文件。打开浏览器窗口,导航到您的已部署 Storybook 实例(例如,https://your-storybook-url-here.com/index.json)。您应该会看到一个 JSON 文件,它以 "v": 3 键开头,紧接着是另一个名为 "stories" 的键,其中包含 Story ID 到 JSON 对象的映射。如果符合这种情况,您的 Storybook 支持index.json 模式

Chromatic 和测试运行器有什么区别?

测试运行器是一个通用的测试工具,可以在本地或 CI 上运行,并且可以配置或扩展以运行各种测试。

Chromatic 是一项基于云的服务,无需设置测试运行器即可运行可视化测试和交互测试(很快还将支持无障碍测试)。它还会与您的 Git 提供商同步,并管理私有项目的访问控制。

然而,在某些情况下,您可能希望将测试运行器与 Chromatic 结合使用。

  • 在本地使用它,并在 CI 上使用 Chromatic。
  • 使用 Chromatic 进行可视化和组件测试,并使用测试运行器运行其他自定义测试。

故障排除

测试运行器似乎不稳定并经常超时

如果您的测试出现以下消息并超时:

Timeout - Async callback was not invoked within the 15000 ms timeout specified by jest.setTimeout

Playwright 可能无法处理测试您的项目中的 Story 数量。也许您有大量的 Story,或者您的 CI 环境配置的 RAM 非常低。在这种情况下,您应该通过如下调整命令来限制并行运行的工作进程数量:

package.json
{
  "scripts": {
    "test-storybook:ci": "yarn test-storybook --maxWorkers=2"
  }
}

CLI 中的错误输出太短

默认情况下,测试运行器会将错误输出截断至 1000 个字符,您可以在浏览器中的 Storybook 中直接查看完整输出。但是,如果您想更改此限制,可以通过将 DEBUG_PRINT_LIMIT 环境变量设置为您选择的数字来实现,例如,DEBUG_PRINT_LIMIT=5000 yarn test-storybook

在其他 CI 环境中运行测试运行器

由于测试运行器基于 Playwright,您可能需要根据您的 CI 设置使用特定的 docker 镜像或其他配置。在这种情况下,您可以参考Playwright CI 文档了解更多信息。

通过标签过滤的测试执行不正确

如果您启用了使用标签过滤测试,并为 includeexclude 列表提供了相似的标签,测试运行器将基于 exclude 列表执行测试,并忽略 include 列表。为避免这种情况,请确保为 includeexclude 列表提供的标签不同。

测试运行器默认不支持 Yarn PnP

如果您在启用了 Plug'n'Play (PnP) 的较新版本 Yarn 上运行的项目中启用了测试运行器,测试运行器可能无法按预期工作,并在运行测试时生成以下错误:

PlaywrightError: jest-playwright-preset: Cannot find playwright package to use chromium

这是因为测试运行器使用了社区维护的包 jest-playwright-preset,该包尚不支持此功能。要解决此问题,您可以将 nodeLinker 设置切换为 node-modules,或者在您的项目中将 Playwright 作为直接依赖项安装,然后通过 install 命令添加浏览器二进制文件。

在其他框架中运行测试覆盖率

如果您打算在带有特殊文件的框架(如 Vue 3 或 Svelte)中运行覆盖率测试,则需要调整配置并启用所需的文件扩展名。例如,如果您使用 Vue,则需要在 nyc 配置文件(即 .nycrc.jsonnyc.config.js)中添加以下内容:

.nyc.config.js
export default {
  // Other configuration options
  extension: ['.js', '.cjs', '.mjs', '.ts', '.tsx', '.jsx', '.vue'],
};

覆盖率插件不支持优化构建

如果您使用 --test 标志生成了经过性能优化的生产构建,并且您正在使用覆盖率插件针对您的 Storybook 运行测试,则可能会遇到覆盖率插件无法对您的代码进行插桩的情况。这是因为该标志的工作方式会移除对性能有影响的插件(例如,Docs覆盖率插件)。要解决此问题,您需要调整您的 Storybook 配置文件(即 .storybook/main.js|ts)并包含 disabledAddons 选项,以允许插件运行测试,但这会以较慢的构建速度为代价。

.storybook/main.ts
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import type { StorybookConfig } from '@storybook/your-framework';
 
const config: StorybookConfig = {
  framework: '@storybook/your-framework',
  stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
  addons: ['@storybook/addon-docs', '@storybook/addon-vitest', '@storybook/addon-coverage'],
  build: {
    test: {
      disabledAddons: ['@storybook/addon-docs'],
    },
  },
};
 
export default config;

覆盖率插件不支持已插桩的代码

由于覆盖率插件依赖于 Webpack5 加载器和 Vite 插件进行代码插桩,因此不依赖这些库的框架(例如,配置了 Webpack 的 Angular)需要额外的配置才能启用代码插桩。在这种情况下,您可以参考以下仓库了解更多信息。

更多测试资源