Docs
Storybook Docs

测试运行器

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

¥The test runner has been superseded by the Vitest addon, which offers the same functionality, powered by the faster and more modern Vitest browser mode. It also enables the full Storybook Test experience, allowing you to run interaction, accessibility, and visual tests from your Storybook app.

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

¥If you are using a Vite-powered Storybook framework, we recommend using the Vitest addon instead of the test runner.

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

¥Storybook test runner turns all of your stories into executable tests. It is powered by Jest and Playwright.

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

¥These tests run in a live browser and can be executed via the command line or your CI server.

设置

¥Setup

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

¥The test-runner is a standalone, framework-agnostic utility that runs parallel to your Storybook. You will need to take some additional steps to set it up properly. Detailed below is our recommendation to configure and execute it.

运行以下命令来安装它。

¥Run the following command to install it.

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

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

¥Update your package.json scripts and enable the test runner.

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

使用以下方式启动 Storybook:

¥Start your Storybook with:

npm run storybook

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

¥Storybook's test runner requires either a locally running Storybook instance or a published Storybook to run all the existing tests.

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

¥Finally, open a new terminal window and run the test-runner with:

npm run test-storybook

配置

¥Configure

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

¥Test runner offers zero-config support for Storybook. However, you can run test-storybook --eject for more fine-grained control. It generates a test-runner-jest.config.js file at the root of your project, which you can modify. Additionally, you can extend the generated configuration file and provide testEnvironmentOptions as the test runner also uses jest-playwright under the hood.

CLI 选项

¥CLI Options

测试运行器由 Jest 提供支持,并接受其 CLI 选项 的子集(例如 --watch--maxWorkers)。如果你已经在项目中使用了任何这些标志,你应该能够将它们迁移到 Storybook 的测试运行器中而不会出现任何问题。下面列出了所有可用的标志及其使用示例。

¥The test-runner is powered by Jest and accepts a subset of its CLI options (for example, --watch, --maxWorkers). If you're already using any of those flags in your project, you should be able to migrate them into Storybook's test-runner without any issues. Listed below are all the available flags and examples of using them.

选项描述
--help输出使用信息
test-storybook --help
-s, --index-json在索引 json 模式下运行。自动检测(需要兼容的 Storybook)
test-storybook --index-json
--no-index-json禁用索引 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在你的故事和组件
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]指定工作池将为运行测试
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与自动存储新快照的常规行为不同,它将无法通过测试并需要使用 --updateSnapshot 运行 Jest。
test-storybook --ci
--shard [index/count]需要 CI。将测试套件执行拆分为多台机器
test-storybook --shard=1/8
--failOnConsole使测试在浏览器控制台错误
test-storybook --failOnConsole 上失败
--includeTags实验性功能
定义要测试的故事子集(如果它们与启用的 tags 匹配)。
test-storybook --includeTags="test-only, pages"
--excludeTags实验性功能
如果故事与提供的 tags 匹配,则阻止测试它们。
test-storybook --excludeTags="no-tests, tokens"
--skipTags实验性功能
配置测试运行器以跳过与提供的 tags 匹配的故事的运行测试。
test-storybook --skipTags="skip-test, layout"
npm run test-storybook -- --watch

测试构建速度提高 2-4 倍

¥Run tests against a deployed Storybook

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

¥By default, the test-runner assumes that you're running it against a locally served Storybook on port 6006. If you want to define a target URL to run against deployed Storybooks, you can use the --url flag:

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

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

¥Alternatively, you can set the TARGET_URL environment variable and run the test-runner:

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

运行可访问性测试

¥Run accessibility tests

安装 可访问性插件 后,你可以使用测试运行器与交互测试一起运行无障碍测试。

¥When you have the Accessibility addon installed, you can run accessibility tests alongside your interaction tests, using the test-runner.

有关更多详细信息(包括配置选项),请参阅 无障碍测试文档

¥For more details, including configuration options, see the Accessibility testing documentation.

运行快照测试

¥Run snapshot tests

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

¥Snapshot testing is a helpful tool for verifying that edge cases like errors are handled correctly. It can also be used to verify that the rendered output of a component is consistent across different test runs.

设置

¥Set up

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

¥To enable snapshot testing with the test-runner, you'll need to take additional steps to set it up properly.

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

¥Add a new configuration file inside your Storybook directory with the following inside:

.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 钩子允许你扩展测试运行器的默认配置。阅读有关它们 此处 的更多信息。

¥The postVisit hook allows you to extend the test runner's default configuration. Read more about them here.

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

¥When you execute the test-runner (for example, with yarn test-storybook), it will run through all of your stories and run the snapshot tests, generating a snapshot file for each story in your project located in the __snapshots__ directory.

配置

¥Configure

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

¥Out of the box, the test-runner provides an inbuilt snapshot testing configuration covering most use cases. You can also fine-tune the configuration to fit your needs via test-storybook --eject or by creating a test-runner-jest.config.js file at the root of your project.

覆盖默认快照目录

¥Override the default snapshot directory

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

¥The test-runner uses a specific naming convention and path for the generated snapshot files by default. If you need to customize the snapshot directory, you can define a custom snapshot resolver to specify the directory where the snapshots are stored.

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

¥Create a snapshot-resolver.js file to implement a custom snapshot resolver:

./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 选项以使用自定义快照解析器:

¥Update the test-runner-jest.config.js file and enable the snapshotResolver option to use the custom snapshot resolver:

./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;

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

¥When the test-runner is executed, it will cycle through all of your stories and run the snapshot tests, generating a snapshot file for each story in your project located in the custom directory you specified.

自定义快照序列化

¥Customize snapshot serialization

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

¥By default, the test-runner uses jest-serializer-html to serialize HTML snapshots. This may cause issues if you use specific CSS-in-JS libraries like Emotion, Angular's ng attributes, or similar libraries that generate hash-based identifiers for CSS classes. If you need to customize the serialization of your snapshots, you can define a custom snapshot serializer to specify how the snapshots are serialized.

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

¥Create a snapshot-serializer.js file to implement a custom snapshot serializer:

./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 选项以使用自定义快照解析器:

¥Update the test-runner-jest.config.js file and enable the snapshotSerializers option to use the custom snapshot resolver:

./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,在快照组件之前将动态生成的属性替换为自定义序列化器文件中的正则表达式提供的静态属性。这可确保快照在不同的测试运行中保持一致。

¥When the test-runner executes your tests, it will introspect the resulting HTML, replacing the dynamically generated attributes with the static ones provided by the regular expression in the custom serializer file before snapshotting the component. This ensures that the snapshots are consistent across different test runs.

生成代码覆盖率

¥Generate code coverage

Storybook 也提供了 覆盖插件。它由 伊斯坦布尔 提供支持,伊斯坦布尔 允许为 JavaScript 生态系统中最常用的框架和构建器提供开箱即用的代码检测。

¥Storybook also provides a coverage addon. It is powered by Istanbul, which allows out-of-the-box code instrumentation for the most commonly used frameworks and builders in the JavaScript ecosystem.

设置

¥Set up

覆盖率插件经过精心设计,可与现代测试工具(例如 Playwright)配合使用,可自动检测你的代码并生成代码覆盖率数据。为了获得最佳体验,我们建议使用 test-runner 和 coverage 插件来运行测试。

¥Engineered to work alongside modern testing tools (e.g., Playwright), the coverage addon automatically instruments your code and generates code coverage data. For an optimal experience, we recommend using the test-runner alongside the coverage addon to run your tests.

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

¥Run the following command to install the addon.

npx storybook@latest add @storybook/addon-coverage

CLI 的 add 命令可以自动补齐插件的安装和设置。要手动安装,请参阅我们的 documentation 了解如何安装插件。

¥The CLI's add command automates the addon's installation and setup. To install it manually, see our documentation on how to install addons.

使用以下方式启动 Storybook:

¥Start your Storybook with:

npm run storybook

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

¥Finally, open a new terminal window and run the test-runner with:

npm run test-storybook -- --coverage

配置

¥Configure

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

¥By default, the @storybook/addon-coverage offers zero-config support for Storybook and instruments your code via istanbul-lib-instrument for Webpack, or vite-plugin-istanbul for Vite. However, you can extend your Storybook configuration file (i.e., .storybook/main.js|ts) and provide additional options to the addon. Listed below are the available options divided by builder and examples of how to use them.

.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 options
选项描述类型
checkProd配置插件以在生产环境中跳过检测
options: { istanbul: { checkProd: true,}}
boolean
cwd配置覆盖率测试的工作目录。
默认为 process.cwd()
options: { istanbul: { cwd: process.cwd(),}}
string
cypressCYPRESS_COVERAGE 替换 VITE_COVERAGE 环境变量。
需要 Cypress 的 代码覆盖率
options: { istanbul: { cypress: true,}}
boolean
exclude使用提供的要从覆盖范围中排除的文件或目录列表覆盖 默认排除列表
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 options
选项描述类型
autoWrap通过将程序代码封装在函数中来提供对顶层返回语句的支持
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 模块语法的支持
options: { istanbul: { esModules: true,}}
boolean
exclude使用提供的要从覆盖范围中排除的文件或目录列表覆盖 默认排除列表
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配置 Instanbul 以生成已检测代码的源映射
options: { istanbul: { produceSourceMap: true,}}
boolean
sourceMapUrlCallback定义一个回调函数,在生成源映射时使用文件名和源映射 URL 调用
options: { istanbul: { sourceMapUrlCallback: (filename, url) => {},}}
function

其他覆盖率报告工具怎么样?

¥What about other coverage reporting tools?

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

¥Out of the box, code coverage tests work seamlessly with Storybook's test-runner and the @storybook/addon-coverage. However, that doesn't mean you can't use additional reporting tools (e.g., Codecov). For instance, if you're working with LCOV, you can use the generated output (in coverage/storybook/coverage-storybook.json) and create your own report with:

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

设置 CI 以运行测试

¥Set up CI to run tests

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

¥You can also configure the test-runner to run tests on a CI environment. Documented below are some recipes to help you get started.

通过 Github Actions 部署针对已部署的 Storybooks 运行

¥Run against deployed Storybooks via Github Actions deployment

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

¥If you're publishing your Storybook with services such as Vercel or Netlify, they emit a deployment_status event in GitHub Actions. You can use it and set the deployment_status.target_url as the TARGET_URL environment variable. Here's how:

.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 必须公开可用,此示例才能正常工作。如果需要身份验证,我们建议使用秘诀 below 运行测试服务器。

¥The published Storybook must be publicly available for this example to work. We recommend running the test server using the recipe below if it requires authentication.

针对运行未部署的 Storybook

¥Run against non-deployed Storybooks

你可以使用你的 CI 提供程序(例如,GitHub ActionsGitLab 管道CircleCI)来针对你构建的 Storybook 构建和运行测试运行器。这是一个依赖第三方库的秘诀,也就是说,concurrentlyhttp-serverwait-on 来构建 Storybook 并使用测试运行器运行测试。

¥You can use your CI provider (for example, GitHub Actions, GitLab Pipelines, CircleCI) to build and run the test runner against your built Storybook. Here's a recipe that relies on third-party libraries, that is to say, concurrently, http-server, and wait-on to build Storybook and run tests with the 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 将 build 输出到 storybook-static 目录。如果你使用的是不同的构建目录,则需要相应地调整秘诀。

¥By default, Storybook outputs the build to the storybook-static directory. If you're using a different build directory, you'll need to adjust the recipe accordingly.

高级配置

¥Advanced configuration

测试钩子 API

¥Test hook API

测试运行器渲染故事并执行其 播放函数(如果存在)。但是,某些行为无法通过在浏览器中执行的播放函数实现。例如,如果你希望测试运行器为你拍摄视觉快照,这可以通过 Playwright/Jest 实现,但必须在 Node 中执行。

¥The test-runner renders a story and executes its play function if one exists. However, certain behaviors are impossible to achieve via the play function, which executes in the browser. For example, if you want the test-runner to take visual snapshots for you, this is possible via Playwright/Jest but must be executed in Node.

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

¥The test-runner exports test hooks that can be overridden globally to enable use cases like visual or DOM snapshots. These hooks give you access to the test lifecycle before and after the story is rendered. Listed below are the available hooks and an overview of how to use them.

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

这些测试钩子是实验性的,可能会发生重大变化。我们鼓励你在故事的 播放函数 中尽可能多地进行测试。

¥These test hooks are experimental and may be subject to breaking changes. We encourage you to test as much as possible within the story's play function.

要启用 hooks API,你需要在 Storybook 目录中添加一个新的配置文件并按如下方式设置它们:

¥To enable the hooks API, you'll need to add a new configuration file inside your Storybook directory and set them up as follows:

.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 页面 和上下文对象,其中包含故事的 idtitlename

¥Except for the setup function, all other functions run asynchronously. Both preVisit and postVisit functions include two additional arguments, a Playwright page and a context object which contains the id, title, and the name of the story.

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

¥When the test-runner executes, your existing tests will go through the following lifecycle:

  • setup 函数在所有测试运行之前执行。

    ¥The setup function is executed before all the tests run.

  • 生成包含所需信息的上下文对象。

    ¥The context object is generated containing the required information.

  • Playwright 导航到故事页面。

    ¥Playwright navigates to the story's page.

  • preVisit 函数已执行。

    ¥The preVisit function is executed.

  • 故事被渲染,并且任何现有的 play 函数都被执行。

    ¥The story is rendered, and any existing play functions are executed.

  • postVisit 函数已执行。

    ¥The postVisit function is executed.

(实验性)过滤测试

¥(Experimental) Filter tests

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

¥When you run the test-runner on Storybook, it tests every story by default. However, if you want to filter the tests, you can use the tags configuration option. Storybook originally introduced this feature to generate automatic documentation for stories. But it can be further extended to configure the test-runner to run tests according to the provided tags using a similar configuration option or via CLI flags (e.g., --includeTags, --excludeTags, --skipTags), only available with the latest stable release (0.15 or higher). Listed below are the available options and an overview of how to use them.

选项描述
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 标志运行测试优先于配置文件中提供的选项,并将覆盖配置文件中的可用选项。

¥Running tests with the CLI flags takes precedence over the options provided in the configuration file and will override the available options in the configuration file.

禁用测试

¥Disabling tests

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

¥If you want to prevent specific stories from being tested by the test-runner, you can configure your story with a custom tag, enable it to the test-runner configuration file or run the test-runner with the --excludeTags CLI flag and exclude them from testing. This is helpful when you want to exclude stories that are not yet ready for testing or are irrelevant to your tests. For example:

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'],
};

(选项

¥Run tests for a subset of stories

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

¥To allow the test-runner only to run tests on a specific story or subset of stories, you can configure the story with a custom tag, enable it in the test-runner configuration file or run the test-runner with the --includeTags CLI flag and include them in your tests. For example, if you wanted to run tests based on the test-only tag, you can adjust your configuration as follows:

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 不支持跨故事导入标签,并且无法按预期工作。

¥Applying tags for the component's stories should either be done at the component level (using meta) or at the story level. Importing tags across stories is not supported in Storybook and won't work as intended.

跳过测试

¥Skip tests

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

¥If you want to skip running tests on a particular story or subset of stories, you can configure your story with a custom tag, enable it in the test-runner configuration file, or run the test-runner with the --skipTags CLI flag. Running tests with this option will cause the test-runner to ignore and flag them accordingly in the test results, indicating that the tests are temporarily disabled. For example:

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'],
};

已部署 Storybooks 的身份验证

¥Authentication for deployed Storybooks

如果你使用需要身份验证来托管 Storybook 的安全托管提供商,则可能需要设置 HTTP 标头。这主要是因为测试运行器如何通过获取请求和 Playwright 检查实例的状态及其故事的索引。为此,你可以修改测试运行器配置文件以包含 getHttpHeaders 函数。此函数将获取调用和页面访问的 URL 作为输入,并返回包含需要设置的标头的对象。

¥If you use a secure hosting provider that requires authentication to host your Storybook, you may need to set HTTP headers. This is mainly because of how the test runner checks the status of the instance and the index of its stories through fetch requests and Playwright. To do this, you can modify the test-runner configuration file to include the getHttpHeaders function. This function takes the URL of the fetch calls and page visits as input and returns an object containing the headers that need to be set.

.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;

助手

¥Helpers

测试运行器导出一些辅助程序,这些辅助程序可用于通过访问 Storybook 的内部结构(例如 argsparameters)使你的测试更具可读性和可维护性。下面列出了可用的辅助程序及其使用方法概述。

¥The test-runner exports a few helpers that can be used to make your tests more readable and maintainable by accessing Storybook's internals (e.g., args, parameters). Listed below are the available helpers and an overview of how to use them.

.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;

使用测试运行器访问故事信息

¥Accessing story information with the test-runner

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

¥If you need to access information about the story, such as its parameters, the test-runner includes a helper function named getStoryContext that you can use to retrieve it. You can then use it to customize your tests further as needed. For example, if you need to configure Playwright's page viewport size to use the viewport size defined in the story's parameters, you can do so as follows:

.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;

使用资源

¥Working with assets

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

¥If you're running a specific set of tests (e.g., image snapshot testing), the test-runner provides a helper function named waitForPageReady that you can use to ensure the page is fully loaded and ready before running the test. For example:

.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 模式

¥Index.json mode

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

¥The test-runner transforms your story files into tests when testing a local Storybook. For a remote Storybook, it uses the Storybook's index.json (formerly stories.json) file (a static index of all the stories) to run the tests.

为什么?

¥Why?

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

¥Suppose you run into a situation where the local and remote Storybooks appear out of sync, or you might not even have access to the code. In that case, the index.json file is guaranteed to be the most accurate representation of the deployed Storybook you are testing. To test a local Storybook using this feature, use the --index-json flag as follows:

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

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

¥The index.json mode is not compatible with the watch mode.

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

¥If you need to disable it, use the --no-index-json flag:

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

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

¥How do I check if my Storybook has a index.json file?

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

¥Index.json mode requires a index.json file. Open a browser window and navigate to your deployed Storybook instance (for example, https://your-storybook-url-here.com/index.json). You should see a JSON file that starts with a "v": 3 key, immediately followed by another key called "stories", which contains a map of story IDs to JSON objects. If that is the case, your Storybook supports index.json mode.

Chromatic 和 Test runner 有什么区别?

¥What's the difference between Chromatic and Test runner?

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

¥The test-runner is a generic testing tool that can run locally or on CI and be configured or extended to run all kinds of tests.

Chromatic 是一项基于云的服务,无需设置测试运行器即可运行 visual交互测试(以及即将推出的 可访问性测试)。它还与你的 git 提供程序同步并管理私有项目的访问控制。

¥Chromatic is a cloud-based service that runs visual and interaction tests (and soon accessibility tests) without setting up the test runner. It also syncs with your git provider and manages access control for private projects.

但是,在某些情况下,你可能希望将测试运行器和 Chromatic 配对。

¥However, you might want to pair the test runner and Chromatic in some cases.

  • 在本地使用它并在你的 CI 上使用 Chromatic。

    ¥Use it locally and Chromatic on your CI.

  • 使用 Chromatic 进行视觉和组件测试,并使用测试运行器运行其他自定义测试。

    ¥Use Chromatic for visual and component tests and run other custom tests using the test runner.

故障排除

¥Troubleshooting

测试构建速度提高 2-4 倍

¥The test runner seems flaky and keeps timing out

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

¥If your tests time out with the following message:

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

可能是 Playwright 无法处理测试你项目中的故事数量。也许你有大量的故事,或者你的 CI 环境的 RAM 配置真的很低。在这种情况下,你应该通过调整命令来限制并行运行的工作程序数量,如下所示:

¥It might be that Playwright couldn't handle testing the number of stories you have in your project. Perhaps you have a large number of stories, or your CI environment has a really low RAM configuration. In such cases, you should limit the number of workers that run in parallel by adjusting your command as follows:

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

CLI 中的错误输出太短

¥The error output in the CLI is too short

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

¥By default, the test runner truncates error outputs at 1000 characters, and you can check the full output directly in Storybook in the browser. However, if you want to change that limit, you can do so by setting the DEBUG_PRINT_LIMIT environment variable to a number of your choosing, for example, DEBUG_PRINT_LIMIT=5000 yarn test-storybook.

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

¥Run the test runner in other CI environments

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

¥As the test runner is based on Playwright, you might need to use specific docker images or other configurations depending on your CI setup. In that case, you can refer to the Playwright CI docs for more information.

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

¥Tests filtered by tags are incorrectly executed

如果你已启用使用标签过滤测试并为 includeexclude 列表提供了类似的标签,则测试运行器将根据 exclude 列表执行测试并忽略 include 列表。为了避免这种情况,请确保提供给 includeexclude 列表的标签不同。

¥If you've enabled filtering tests with tags and provided similar tags to the include and exclude lists, the test-runner will execute the tests based on the exclude list and ignore the include list. To avoid this, make sure the tags provided to the include and exclude lists differ.

测试运行器不支持开箱即用的 Yarn PnP

¥The test runner doesn't support Yarn PnP out of the box

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

¥If you've enabled the test-runner in a project running on a newer version of Yarn with Plug'n'Play (PnP) enabled, the test-runner might not work as expected and may generate the following error when running tests:

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

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

¥This is due to the test-runner using the community-maintained package jest-playwright-preset that still needs to support this feature. To solve this, you can either switch the nodeLinker setting to node-modules or install Playwright as a direct dependency in your project, followed by adding the browser binaries via the install command.

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

¥Run test coverage in other frameworks

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

¥If you intend on running coverage tests in frameworks with special files like Vue 3 or Svelte, you'll need to adjust your configuration and enable the required file extensions. For example, if you're using Vue, you'll need to add the following to your nyc configuration file (i.e., .nycrc.json or nyc.config.js):

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

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

¥The coverage addon doesn't support optimized builds

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

¥If you generated a production build optimized for performance with the --test flag, and you're using the coverage addon to run tests against your Storybook, you may run into a situation where the coverage addon doesn't instrument your code. This is due to how the flag works, as it removes addons that have an impact on performance (e.g., Docs, coverage addon). To resolve this issue, you'll need to adjust your Storybook configuration file (i.e., .storybook/main.js|ts) and include the disabledAddons option to allow the addon to run tests at the expense of a slower build.

.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;

覆盖率插件不支持已检测代码

¥The coverage addon doesn't support instrumented code

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

¥As the coverage addon is based on Webpack5 loaders and Vite plugins for code instrumentation, frameworks that don't rely upon these libraries (e.g., Angular configured with Webpack), will require additional configuration to enable code instrumentation. In that case, you can refer to the following repository for more information.

更多测试资源

¥More testing resources