Storybook Test Runner 按 Tags 过滤测试:include、exclude、skip 配置与 CLI 实战指南
Storybook Test Runner 默认会为每一个 story 生成并执行测试,但真实项目中常常存在"尚未就绪的组件""仅用于展示的布局页""与测试无关的 token 样式"等场景。本指南基于 Storybook 仓库中的官方文档 test-runner-tags-config.md 与其宿主章节 test-runner.mdx,系统讲解如何利用 Storybook 的 tags 机制,通过配置文件或 --includeTags、--excludeTags、--skipTags 三个 CLI 参数,精准控制"哪些 story 被测、哪些被跳过、哪些被彻底排除",并结合仓库源码说明其底层实现原理。
一、为什么需要按 Tags 过滤测试
Test Runner 将你的每一个 story 都转化为可执行的测试:
- 对没有 play function 的 story:验证其能否无错误地渲染;
- 对带有 play function 的 story:还会检查 play function 中的断言是否全部通过。
这些测试运行在真实浏览器中,可通过命令行或 CI 执行。默认情况下,每次运行都会测试所有 story。当组件库规模变大后,全量测试会带来几个痛点:
- 某些 story 处于"草稿"状态,尚未准备好被测试;
- 某些 story 只服务于文档演示(如布局、token 展示),测试它们没有意义;
- 某些 story 依赖外部服务或环境变量,不适合在常规测试中执行。
Storybook 原本引入 tags 特性是为了生成自动文档,但它可以被进一步扩展:test-runner 使用与 Storybook 相同的 tags 配置体系(或等价的 CLI 标志)来按需运行测试。该 CLI 过滤能力仅在 test-runner 最新稳定版(0.15 及以上)中可用。
二、在 test-runner 配置文件中启用 tags 过滤
在项目根目录的 .storybook/test-runner.js(或 TypeScript 版本的 test-runner.ts)中增加 tags 字段,即可声明三组过滤规则。以下是官方配置的完整示例:
module.exports = {
tags: {
include: ['test-only', 'pages'],
exclude: ['no-tests', 'tokens'],
skip: ['skip-test', 'layout'],
},
};
如果你使用 TypeScript 编写配置文件,可以引入官方类型 TestRunnerConfig 获得完整的类型提示:
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;
三个选项的含义
| 选项 | 说明 |
|---|---|
exclude | 匹配到所提供 tags 的 story 不会被测试(彻底排除) |
include | 只有匹配到所提供 tags 的 story 子集才会被测试 |
skip | 匹配到所提供 tags 的 story 会被跳过,并在测试结果中被标记出来 |
三、通过 CLI 标志覆盖配置(0.15+)
除了配置文件,test-runner 还暴露了三个对应的 CLI 标志,它们适用于不想改动配置文件、或希望在 CI 流水线的不同阶段动态切换过滤规则的场景:
| CLI 选项 | 说明 | 示例命令 |
|---|---|---|
--includeTags | 定义仅测试匹配这些 tags 的 story 子集(实验性) | test-storybook --includeTags="test-only, pages" |
--excludeTags | 阻止匹配这些 tags 的 story 被测试(实验性) | test-storybook --excludeTags="no-tests, tokens" |
--skipTags | 配置 test-runner 跳过匹配这些 tags 的 story(实验性) | test-storybook --skipTags="skip-test, layout" |
重要规则:CLI 标志的优先级高于配置文件。当同时提供 CLI 标志与配置文件中的选项时,CLI 标志会生效并覆盖配置文件中的对应选项。这为"默认配置 + 按需覆盖"的 CI 工作流提供了灵活性。
CLI 执行示例
# 仅测试带有 test-only 或 pages 标签的 story
test-storybook --includeTags="test-only, pages"
# 排除带有 no-tests 或 tokens 标签的 story
test-storybook --excludeTags="no-tests, tokens"
# 跳过带有 skip-test 或 layout 标签的 story
test-storybook --skipTags="skip-test, layout"
四、在 CSF 中为 story 打标签
过滤规则生效的前提是 story 本身带有匹配的 tags。Tags 可以在 组件级(meta) 或 story 级 声明,官方文档明确指出:为组件的 story 应用 tags 应在组件级(meta)或 story 级完成,跨 story 导入 tags 在 Storybook 中不受支持,也不会按预期工作。
场景一:禁用(排除)某些 story 的测试
当你想让 test-runner 完全跳过某些尚未就绪或不相关的 story 时,为其打上自定义 tag,并在配置文件的 exclude 中声明(或运行时使用 --excludeTags)。以下为 React(CSF 3)示例:
import type { Meta, StoryObj } from '@storybook/react';
import { MyComponent } from './MyComponent';
const meta = {
component: MyComponent,
//👇 为文件内所有 story 提供 no-tests 标签
tags: ['no-tests'],
} satisfies Meta<typeof MyComponent>;
export default meta;
type Story = StoryObj<typeof meta>;
export const ExcludeStory: Story = {
//👇 为单个 story 添加 no-tests 标签,以便在 test-runner 配置启用后将其排除出测试
tags: ['no-tests'],
};
场景二:只运行特定子集的测试
当你想让 test-runner 只对某一部分 story 执行测试时,为其打上自定义 tag,并在配置文件的 include 中声明(或使用 --includeTags):
import type { Meta, StoryObj } from '@storybook/react';
import { MyComponent } from './MyComponent';
const meta = {
component: MyComponent,
//👇 为文件内所有 story 提供 test-only 标签
tags: ['test-only'],
} satisfies Meta<typeof MyComponent>;
export default meta;
type Story = StoryObj<typeof meta>;
export const IncludeStory: Story = {
//👇 为单个 story 添加 test-only 标签,使其在 test-runner 配置启用后被纳入测试
tags: ['test-only'],
};
场景三:跳过(标记为临时禁用)某些 story
当你想让 test-runner 忽略某些 story,但又希望它们在测试结果中明确显示为"被跳过"(而不是彻底消失)时,为其打上自定义 tag,并在配置文件的 skip 中声明(或使用 --skipTags):
import type { Meta, StoryObj } from '@storybook/react';
import { MyComponent } from './MyComponent';
const meta = {
component: MyComponent,
//👇 为文件内所有 story 提供 skip-test 标签
tags: ['skip-test'],
} satisfies Meta<typeof MyComponent>;
export default meta;
type Story = StoryObj<typeof meta>;
export const SkipStory: Story = {
//👇 为单个 story 添加 skip-test 标签,使其在 test-runner 配置启用后被跳过
tags: ['skip-test'],
};
官方文档还提供了 Angular、Vue、Web Components 等多个框架以及 CSF Next(🧪 实验性写法,通过 preview.meta() / meta.story() 声明) 的完整代码示例,分别见 my-component-exclude-tags.md、my-component-include-tags.md 与 my-component-skip-tags.md。三者的核心逻辑一致:tag 加在 meta 上作用于整组 story,加在单个 story 上仅作用于该 story。
五、include、exclude、skip 三者的区别
三个选项的语义容易混淆,总结如下:
exclude(排除):匹配的 story 不会出现在测试执行列表中,相当于"不测这个"。include(包含):只有匹配的 story 才会被测试,相当于"只测这些";不匹配的 story 全部被排除在外。skip(跳过):匹配的 story 会被 test-runner 忽略,并在测试结果中被标记出来,表明这些测试被临时禁用——这是它和exclude的关键差异。
实践建议:
- 组件开发中常用
include:例如仅对带有test-only标签的 story 跑交互测试,加快本地反馈循环; - 发布前可用
exclude:将文档型、占位型 story 排除出回归测试; skip适合临时屏蔽:如某条故事依赖的接口暂时不可用,跳过并保留可见的标记,方便后续恢复。
六、源码级原理:tags 过滤在 Storybook 中如何生效
本仓库(Storybook 源码)中,tags 过滤在多个层面都有对应实现,可以作为理解其工作机制的依据。
1. 默认值与配置合并
在 code/addons/vitest/src/vitest-plugin/index.ts 中,Vitest 插件会合并用户传入的 tags 选项,并为 include 设置了默认值 Tag.TEST(即 test 标签):
tags: {
include: options?.tags?.include ?? [Tag.TEST],
exclude: options?.tags?.exclude ?? [],
skip: options?.tags?.skip ?? [],
},
也就是说,即使你不做任何配置,Storybook 默认也会只测试带有 test 标签(或满足其他默认规则的)的 story;一旦你在 test-runner 配置中提供了 include/exclude/skip,则会覆盖这些默认值。
2. 运行时传递:tags 注入测试环境
同一文件的 L379-L381 显示,最终合并后的 tags 会通过环境变量注入测试运行环境,供 setup 文件消费:
__VITEST_INCLUDE_TAGS__: finalOptions.tags.include.join(','),
__VITEST_EXCLUDE_TAGS__: finalOptions.tags.exclude.join(','),
__VITEST_SKIP_TAGS__: finalOptions.tags.skip.join(','),
从源码结构可以推断,include/exclude 主要用于在测试生成阶段决定"哪些 story 被纳入测试文件",而 skip 则在单条 story 执行阶段生效。
3. skip 的执行逻辑:context.skip()
在 code/addons/vitest/src/vitest-plugin/test-utils.ts 中可以看到 skip 的具体落地实现——当组合出的 story 的 tags 命中 skipTags 中的任意一个时,直接调用 context.skip() 跳过该条测试:
if (composedStory === undefined || skipTags?.some((tag) => composedStory.tags.includes(tag))) {
context.skip();
}
这解释了 skip 与 exclude 的行为差异:skip 仍会创建测试用例,只是在执行时被标记为跳过(测试报告中可见);而 exclude 则是在更早的阶段(测试收集/生成阶段)就把匹配的 story 过滤掉。
七、常见陷阱与排查
官方文档在 test-runner.mdx 中记录了该功能最典型的一个坑:
按 tags 过滤后测试仍被错误执行:如果你在
include和exclude列表中提供了相同的 tag,test-runner 会以exclude列表为准执行测试,并忽略include列表。因此务必保证include与exclude中的 tags 互不相同。
另一个需要留意的是版本前提:--includeTags、--excludeTags、--skipTags 属于实验性功能,仅在 test-runner 0.15 及以上的稳定版本中可用;在旧版本中请使用配置文件方式,并确认你安装的 test-runner 版本支持 tags 过滤。
八、完整工作流示例
综合以上内容,一个典型的"按 tags 管理测试"工作流如下:
- 在 CSF 中打标签:为组件
meta或单个 story 添加自定义 tags(如test-only、no-tests、skip-test); - 在 test-runner 配置中声明规则:在
.storybook/test-runner.js中配置tags.include/tags.exclude/tags.skip; - 按需用 CLI 覆盖:在 CI 的不同阶段使用
--includeTags、--excludeTags、--skipTags动态调整过滤范围; - 检查测试结果:被
skip的 story 会在结果中被标记,便于追踪临时禁用的测试,及时恢复。
# 本地开发:只测关键路径
test-storybook --includeTags="test-only, pages"
# 发布前:排除文档型与 token 展示 story 后全量回归
test-storybook --excludeTags="no-tests, tokens"
# 临时跳过某个 story 并保留可见标记
test-storybook --skipTags="skip-test, layout"
通过将 tags 过滤与 CI 结合,你可以把"全量测试、关键子集测试、发布前回归"等不同粒度的测试策略落到同一条测试管线上,既保证测试覆盖面,又避免无关 story 拖慢流水线。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



