文档
Storybook 文档

测试运行器

测试运行器已被 Vitest 插件 取代,该插件提供相同的功能,并由更快、更现代的 Vitest 浏览器模式提供支持。它还启用了完整的 Storybook 测试体验,允许您从 Storybook 应用运行交互、可访问性和视觉测试。

如果您使用的是 Vite 驱动的 Storybook 框架,我们建议使用 Vitest 插件而不是测试运行器。

Storybook 测试运行器将您的所有故事变成可执行的测试。它由 JestPlaywright 提供支持。

  • 对于那些 没有 play 函数 的用户:它会验证故事是否能正常渲染而不会出现任何错误。
  • 对于那些 有 play 函数 的用户:它还会检查 play 函数中的错误以及所有断言是否通过。

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

设置

test-runner 是一个独立的、与框架无关的实用工具,与您的 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 选项

test-runner 由 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在 watch 模式下运行
test-storybook --watch
--watchAll监视文件更改并在更改时重新运行所有测试。
test-storybook --watchAll
--coverage对您的故事和组件运行覆盖率测试
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 [amount]指定 worker 池将为运行测试生成的最大 worker 数
test-storybook --maxWorkers=2
--testTimeout [amount]定义测试在被自动标记为失败之前可以运行的最大时间(以毫秒为单位)。对于长时间运行的测试很有用
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 格式打印测试结果。此模式会将所有其他测试输出和用户消息发送到 stderr。
test-storybook --json
--outputFile当也指定了 --json 选项时,将测试结果写入文件。
test-storybook --json --outputFile results.json
--junit指示应以 junit 文件格式报告测试信息。
test-storybook --**junit**
--ci与自动存储新快照的常规行为相反,它会使测试失败,并要求 Jest 以 --updateSnapshot 运行。
test-storybook --ci
--shard [index/count]需要 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 运行测试

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

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

或者,您可以设置 TARGET_URL 环境变量并运行 test-runner:

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),它将遍历您的所有故事,运行快照测试,为项目中的每个故事生成一个快照文件,该文件位于 __snapshots__ 目录中。

配置

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

覆盖默认快照目录

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

创建一个 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;

当 test-runner 执行时,它将遍历您的所有故事并运行快照测试,为项目中的每个故事生成一个快照文件,该文件位于您指定的自定义目录中。

自定义快照序列化

默认情况下,test-runner 使用 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;

当 test-runner 执行您的测试时,它将检查生成的 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>string
extension用提供的要包含在覆盖率中的文件扩展名列表来扩展默认扩展名列表
options: {istanbul: { extension: ['.js', '.cjs', '.mjs'],}}
Array<String>string
forceBuildInstrument配置插件以在构建模式下添加插桩
options: {istanbul: { forceBuildInstrument: true,}}
boolean
include选择要收集覆盖率的文件
options: {istanbul: { include: ['**/stories/**'],}}
Array<String>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>string
extension用提供的要包含在覆盖率中的文件扩展名列表来扩展默认扩展名列表
options: {istanbul: { extension: ['.js', '.cjs', '.mjs'],}}
Array<String>string
include选择要收集覆盖率的文件
options: {istanbul: { include: ['**/stories/**'],}}
Array<String>string
nycrcPath为现有的 nyc 配置文件定义相对路径
options: {istanbul: { nycrcPath: '../nyc.config.js',}}
string
preserveComments在插桩代码中包含注释
options: {istanbul: { preserveComments: true,}}
boolean
produceSourceMap配置 Istanbul 为插桩代码生成源映射
options: {istanbul: { produceSourceMap: true,}}
boolean
sourceMapUrlCallback定义一个回调函数,在生成源映射时,该函数将使用文件名和源映射 URL 调用
options: {istanbul: { sourceMapUrlCallback: (filename, url) => {},}}
function

其他覆盖率报告工具呢?

开箱即用,代码覆盖率测试可与 Storybook 的 test-runner 和 @storybook/addon-coverage 无缝协同工作。但是,这并不意味着您不能使用额外的报告工具(例如 Codecov)。例如,如果您使用 LCOV,您可以使用生成的输出(位于 coverage/storybook/coverage-storybook.json)并使用以下方式创建您自己的报告:

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

设置 CI 来运行测试

您还可以将 test-runner 配置为在 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。这是一个依赖第三方库的配方,即 concurrentlyhttp-serverwait-on 来构建 Storybook 并使用 test-runner 运行测试。

.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

test-runner 会渲染一个故事,并在存在的情况下执行其play 函数。但是,某些行为无法通过在浏览器中执行的 play 函数来实现。例如,如果您希望 test-runner 为您拍摄视觉快照,这可以通过 Playwright/Jest 实现,但必须在 Node 中执行。

test-runner 导出可以全局覆盖的测试钩子,以启用视觉或 DOM 快照等用例。这些钩子让您在故事渲染

之前

之后

访问测试生命周期。下面列出了可用的钩子以及如何使用它们的概述。

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

这些测试钩子是实验性的,可能会发生破坏性更改。我们鼓励您在故事的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 和一个包含故事的 idtitlename 的上下文对象。

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

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

(实验性) 过滤测试

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

选项描述
exclude阻止故事被测试,如果它们匹配提供的标签。
include定义一个故事子集,仅当它们与启用的标签匹配时才进行测试。
skip跳过匹配提供标签的故事的测试。
.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 标志运行测试优先于配置文件中提供的选项,并将覆盖配置文件中的可用选项。

禁用测试

如果您想阻止特定的故事被 test-runner 测试,您可以为故事配置自定义标签,在 test-runner 配置文件中启用它,或者使用 CLI 标志 --excludeTags 运行 test-runner 并将它们排除在测试之外。这在您想排除尚未准备好测试或与您的测试无关的故事时很有用。例如:

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 = {
  component: MyComponent,
  //👇 Provides the `no-tests` tag to all stories in this file
  tags: ['no-tests'],
} satisfies Meta<typeof MyComponent>;
 
export default meta;
type Story = StoryObj<typeof meta>;
 
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'],
};

运行特定故事集的测试

为了让 test-runner 只运行特定故事或故事子集的测试,您可以为故事配置自定义标签,在 test-runner 配置文件中启用它,或者使用 CLI 标志 --includeTags 并在您的测试中包含它们。例如,如果您想根据 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 = {
  component: MyComponent,
  //👇 Provides the `test-only` tag to all stories in this file
  tags: ['test-only'],
} satisfies Meta<typeof MyComponent>;
 
export default meta;
type Story = StoryObj<typeof meta>;
 
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'],
};

在组件的故事中应用标签可以按组件级别(使用 meta)或故事级别进行。在故事之间导入标签在 Storybook 中不受支持,也不会按预期工作。

跳过测试

如果您想跳过在特定故事或故事子集上运行测试,您可以为故事配置自定义标签,在 test-runner 配置文件中启用它,或者使用 CLI 标志 --skipTags 运行 test-runner。使用此选项运行测试将导致 test-runner 忽略它们,并在测试结果中标记它们,表明测试暂时被禁用。例如:

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 = {
  component: MyComponent,
  //👇 Provides the `skip-test` tag to all stories in this file
  tags: ['skip-test'],
} satisfies Meta<typeof MyComponent>;
 
export default meta;
type Story = StoryObj<typeof meta>;
 
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 标头。这主要是因为 test-runner 通过 fetch 请求和 Playwright 来检查实例的状态及其故事的索引。要做到这一点,您可以修改 test-runner 配置文件以包含 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;

辅助函数

test-runner 导出了几个辅助函数,可以使用它们通过访问 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;

使用 test-runner 访问故事信息

如果您需要访问故事的信息,例如其参数,test-runner 包含一个名为 getStoryContext 的辅助函数,您可以使用它来检索该信息。然后,您可以使用它来进一步自定义您的测试,以满足您的需求。例如,如果您需要配置 Playwright 页面的 视口大小 以使用故事参数中定义的视口大小,您可以按如下方式进行:

.storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';
import { getStoryContext } from '@storybook/test-runner';
import { MINIMAL_VIEWPORTS } from '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;

处理资源

如果您正在运行特定的一组测试(例如,图像快照测试),test-runner 提供了一个名为 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 时,test-runner 会将您的故事文件转换为测试。对于远程 Storybook,它使用 Storybook 的 index.json(以前称为 stories.json)文件(所有故事的静态索引)来运行测试。

为什么?

假设您遇到本地和远程 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" 的键,其中包含一个故事 ID 到 JSON 对象映射。如果是这样,您的 Storybook 就支持 index.json 模式

Chromatic 和 Test runner 之间有什么区别?

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

Chromatic 是一个基于云的服务,可以运行视觉交互测试(即将支持可访问性测试),而无需设置 test-runner。它还与您的 git 提供商同步,并管理私有项目的访问控制。

但是,在某些情况下,您可能希望将 test-runner 和 Chromatic 配对使用。

  • 在本地使用 test-runner,在 CI 上使用 Chromatic。
  • 使用 Chromatic 进行视觉和组件测试,并使用 test-runner 运行其他自定义测试。

故障排除

Test runner 似乎不稳定并持续超时

如果您的测试超时并显示以下消息:

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

这可能是因为 Playwright 无法处理您项目中故事的数量。您的故事可能很多,或者您的 CI 环境的 RAM 配置非常低。在这种情况下,您应该通过调整命令来限制并行运行的工作进程数量:

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

CLI 中的错误输出太短

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

在其他 CI 环境中运行 test runner

由于 test-runner 基于 Playwright,您可能需要根据您的 CI 设置使用特定的 docker 镜像或其他配置。在这种情况下,您可以参考 Playwright CI 文档 以获取更多信息。

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

如果您启用了按标签过滤测试,并且向 includeexclude 列表提供了相似的标签,test-runner 将根据 exclude 列表执行测试,并忽略 include 列表。为避免这种情况,请确保 includeexclude 列表提供的标签不同。

Test runner 默认不支持 Yarn PnP

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

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

这是因为 test-runner 使用社区维护的包 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 标志生成了一个针对性能优化的生产构建,并且您正在使用 coverage 插件来测试您的 Storybook,那么您可能会遇到 coverage 插件不插桩代码的情况。这是由于该标志的工作方式,它会移除对性能有影响的插件(例如 Docscoverage 插件)。要解决此问题,您需要调整您的 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;

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

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

更多测试资源