Storybook 按 Tag 过滤测试:用 no-tests 标签从 Test Runner 排除组件与故事
导读
在 Storybook 的 Test Runner 测试流程中,并非每一个故事都适合被执行——尚未就绪的交互、依赖真实后端数据的占位故事、或是仅用于文档展示的视觉示例,强行纳入测试只会造成无谓的失败与噪声。本指南基于 Storybook 官方文档与仓库源码,讲解如何借助 CSF 中的 tags 机制(以 no-tests 为例),从组件(meta)层或故事(story)层把指定故事排除出测试范围,并给出配置文件与命令行两种启用方式,帮助你精准掌控"哪些故事该被测、哪些故事不该被测"。
为什么需要用标签排除测试
默认情况下,Storybook Test Runner(由 Jest 与 Playwright 驱动)会把项目中的每一个故事都转化为一条可执行测试:
- 对于没有 play 函数 的故事,验证其能否无错误渲染;
- 对于带有 play 函数的故事,还会进一步校验 play 函数内的断言是否全部通过。
这些测试在真实浏览器中运行,因此凡是会拖慢、干扰或注定失败的故事都会直接影响整条流水线。典型需要排除的场景包括:尚未实现完成的组件、需要登录态或真实网络的后端耦合故事、仅供设计走查的"装饰性"示例等。
为此,Storybook 为故事与组件引入了 tags(标签) 能力。虽然这一特性最初是为了自动生成文档(autodocs) 而引入的,但它可以被扩展用于驱动 Test Runner 的过滤逻辑,实现"按标签跑测试"。相关完整背景可参见 test-runner.mdx 中关于 "Filter tests" 的实验性章节。
第一步:在故事中打上 no-tests 标签
在 docs/_snippets/my-component-exclude-tags.md 中,官方给出了最直接的用法:定义自定义标签(如 no-tests),并将其添加在:
- 组件层级(
meta):为该文件内的所有故事统一提供该标签; - 故事层级(单个导出):仅为当前这个故事添加标签。
下面分不同的框架与 CSF 版本给出完整示例。
CSF 3:类型化写法(React / Vue 等通用框架)
以 satisfies 约束 Meta 类型的标准 CSF 3 写法为例:
// 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'],
};
非 TypeScript 项目的 JavaScript/JSX 写法(renderer="common" 对应框架无关形式):
import { MyComponent } from './MyComponent';
export default {
component: MyComponent,
//👇 Provides the `no-tests` tag to all stories in this file
tags: ['no-tests'],
};
export const ExcludeStory = {
//👇 Adds the `no-tests` tag to this story to exclude it from the tests when enabled in the test-runner configuration
tags: ['no-tests'],
};
CSF 3:Angular 与 Web Components
Angular 中 Meta<MyComponent> 的组件来源于 my-component.component:
import type { Meta, StoryObj } from '@storybook/angular';
import { MyComponent } from './my-component.component';
const meta: Meta<MyComponent> = {
component: MyComponent,
//👇 Provides the `no-tests` tag to all stories in this file
tags: ['no-tests'],
};
export default meta;
type Story = StoryObj<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'],
};
Web Components 则以自定义元素名(如 'my-component')注册组件,tag 的挂载方式完全一致:
import type { Meta, StoryObj } from '@storybook/web-components-vite';
const meta: Meta = {
component: 'my-component',
//👇 Provides the `no-tests` tag to all stories in this file
tags: ['no-tests'],
};
export default meta;
type Story = StoryObj;
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'],
};
CSF Next(🧪 实验性语法)
在 Storybook 新一代的 CSF 语法中,故事统一经由 preview.meta({...}) 与 meta.story({...}) 声明,标签声明位置不变。以 React 为例:
import preview from '../.storybook/preview';
import { MyComponent } from './MyComponent';
const meta = preview.meta({
component: MyComponent,
//👇 Provides the `no-tests` tag to all stories in this file
tags: ['no-tests'],
});
export const ExcludeStory = meta.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'],
});
Vue 中仅组件导入路径不同(指向 .vue 单文件组件):
import preview from '../.storybook/preview';
import MyComponent from './MyComponent.vue';
const meta = preview.meta({
component: MyComponent,
//👇 Provides the `no-tests` tag to all stories in this file
tags: ['no-tests'],
});
export const ExcludeStory = meta.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'],
});
Web Components 的 CSF Next 写法则将 component 指向自定义元素标识符:
import preview from '../.storybook/preview';
const meta = preview.meta({
component: 'my-component',
//👇 Provides the `no-tests` tag to all stories in this file
tags: ['no-tests'],
});
export const ExcludeStory = meta.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 的 tags 配置或对应 CLI 参数认领后,过滤才会生效。Test Runner 在 tags 配置对象中提供三个语义各异的过滤维度:
| 配置项 | 作用 |
|---|---|
exclude |
命中给定标签的故事不被测试(直接排除) |
include |
仅测试命中给定标签的故事子集 |
skip |
命中给定标签的故事被跳过,并在结果中标记为"暂时禁用" |
对应 CLI 参数为 --excludeTags、--includeTags 与 --skipTags。
方式一:配置文件
在 Storybook 目录(默认 .storybook)下新建 test-runner.js(或 TypeScript 风格的 test-runner.ts),例如仅排除 no-tests 的极简配置(参见 test-runner-tags-exclude.md):
module.exports = {
tags: {
exclude: ['no-tests'],
},
};
同时覆盖三个维度的完整示例(参见 test-runner-tags-config.md):
module.exports = {
tags: {
include: ['test-only', 'pages'],
exclude: ['no-tests', 'tokens'],
skip: ['skip-test', 'layout'],
},
};
TypeScript 项目可通过 @storybook/test-runner 提供的 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;
方式二:命令行参数
无需修改任何配置,直接在运行测试时追加过滤参数(实验性特性,需 Test Runner 0.15 或更高稳定版本):
test-storybook --excludeTags="no-tests, tokens"
同类的包含与跳过参数写法一致:
test-storybook --includeTags="test-only, pages"
test-storybook --skipTags="skip-test, layout"
注意:官方文档明确说明,CLI 参数优先级高于配置文件——一旦通过命令行传入 tags 参数,会覆盖配置文件中的对应选项。
exclude、skip、include 三种语义的取舍
exclude(本文场景):直接把标签故事从测试集合中拿掉。适合"还没准备好、或与测试无关"的故事,例如使用no-tests标注后,即使运行全部测试也完全不触碰它们。skip:故事仍被 Test Runner 识别与列举,但执行时被标记为跳过,提示"测试被暂时禁用"。适合希望保留可见性、后续再恢复测试的故事。include:从全量中挑出一个子集来测试(白名单思路),实现"只跑某些故事"。
从源码结构看,三者在 Vitest 风格的实现中被统称为"tags 过滤",并在生成测试时作用于每一个故事。以 code/addons/vitest/src/vitest-plugin/test-utils.ts 为例,skipTags 的消费逻辑为:
if (composedStory === undefined || skipTags?.some((tag) => composedStory.tags.includes(tag))) {
context.skip();
}
即每个故事在组合(composeStory)完成后,若其 tags 命中任一 skip 标签,则直接调用 context.skip() 使其跳过。这印证了 tags 在底层是如何被收集(composedStory.tags)并与配置/CLI 传入的标签集合做交集判断的,你也可以据此推断 exclude 类标签的过滤发生在更靠前的测试筛选阶段。
使用标签时的注意事项
综合官方文档与代码,落地此方案时有几点需要特别留意:
- 层级限制:标签要么挂在组件层级(
meta/preview.meta)统一作用于本文件全部故事,要么挂在单个故事导出上。官方明确提示:跨文件 import 其他故事的 tags 不被支持,也不会按预期工作。因此需要隔离测试的组件,请直接在其自己的 stories 文件中声明。 no-tests只是约定:该标签名是官方文档示例中的自定义约定,并非硬编码的内建标签——你完全可以使用符合团队语义的名字(如tokens、wip),只要保证故事定义与 test-runner 配置两侧的字符串完全一致即可。- 功能成熟度:按文档描述,tags 过滤属于实验性能力,使用前请确认你的 Test Runner 版本达到
0.15及以上稳定版本;相关能力亦面向由 Vitest 驱动的下一代测试方案(详见 vitest-addon/index.mdx)。 - 为更细粒度的掌控留好入口:若需要在运行期动态决定排除哪些故事,可优先使用 CLI 参数而非固化在配置文件里,避免团队在多个 tag 集合间切换时反复改动
.storybook/test-runner.*。
小结
通过 tags 标签把 no-tests(或任意自定义标签)挂到 meta 或单个故事上,再配合 .storybook/test-runner.js 的 tags.exclude 配置或 --excludeTags 命令行参数,你即可在 Storybook 的组件测试体系中实现精细、可版本化控制的测试范围管理。这套机制不依赖任何额外插件——故事用 CSF 声明标签、Test Runner 消费标签、底层在生成每条测试前完成过滤判断,形成一条完整且可预期的链路,是隔离不稳定故事、保持组件测试绿色稳定的低成本手段。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0626
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00