首页
/ Storybook 按 Tag 过滤测试:用 no-tests 标签从 Test Runner 排除组件与故事

Storybook 按 Tag 过滤测试:用 no-tests 标签从 Test Runner 排除组件与故事

2026-09-07 13:55:10作者:曹令琨Iris

导读

在 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 类标签的过滤发生在更靠前的测试筛选阶段。

使用标签时的注意事项

综合官方文档与代码,落地此方案时有几点需要特别留意:

  1. 层级限制:标签要么挂在组件层级(meta/preview.meta)统一作用于本文件全部故事,要么挂在单个故事导出上。官方明确提示:跨文件 import 其他故事的 tags 不被支持,也不会按预期工作。因此需要隔离测试的组件,请直接在其自己的 stories 文件中声明。
  2. no-tests 只是约定:该标签名是官方文档示例中的自定义约定,并非硬编码的内建标签——你完全可以使用符合团队语义的名字(如 tokenswip),只要保证故事定义与 test-runner 配置两侧的字符串完全一致即可。
  3. 功能成熟度:按文档描述,tags 过滤属于实验性能力,使用前请确认你的 Test Runner 版本达到 0.15 及以上稳定版本;相关能力亦面向由 Vitest 驱动的下一代测试方案(详见 vitest-addon/index.mdx)。
  4. 为更细粒度的掌控留好入口:若需要在运行期动态决定排除哪些故事,可优先使用 CLI 参数而非固化在配置文件里,避免团队在多个 tag 集合间切换时反复改动 .storybook/test-runner.*

小结

通过 tags 标签把 no-tests(或任意自定义标签)挂到 meta 或单个故事上,再配合 .storybook/test-runner.jstags.exclude 配置或 --excludeTags 命令行参数,你即可在 Storybook 的组件测试体系中实现精细、可版本化控制的测试范围管理。这套机制不依赖任何额外插件——故事用 CSF 声明标签、Test Runner 消费标签、底层在生成每条测试前完成过滤判断,形成一条完整且可预期的链路,是隔离不稳定故事、保持组件测试绿色稳定的低成本手段。

登录后查看全文
热门项目推荐
相关项目推荐