Storybook 8 组件测试与文档一体化

Sarah Wong | 2026-09-02T01:07:15 | JavaScript, Frontend

详解 Storybook 8 的组件驱动开发流程,涵盖 CSF3 编写 Stories、交互测试 play 函数、视觉回归测试与自动生成 API 文档。

# Storybook 8 组件测试与文档一体化 ## 为什么用 Storybook 组件开发最大的痛点是上下文依赖太重 — 要看一个按钮的效果,得启动整个应用、登录、导航到对应页面。Storybook 提供了独立于应用的组件开发环境,每个组件状态都可以单独预览和测试。 Storybook 8 带来了大幅性能提升和改进的测试能力。 ## 初始化 ```bash npx storybook@latest init ``` ## CSF3 格式编写 Stories Component Story Format 3 (CSF3) 使用对象语法替代了以前的模板语法: ```tsx // Button.stories.tsx import type { Meta, StoryObj } from "@storybook/react"; import { Button } from "./Button"; const meta: Meta = { title: "UI/Button", component: Button, tags: ["autodocs"], argTypes: { variant: { control: "select", options: ["primary", "secondary", "danger", "ghost"], }, size: { control: "radio", options: ["sm", "md", "lg"], }, disabled: { control: "boolean" }, }, }; export default meta; type Story = StoryObj; export const Primary: Story = { args: { variant: "primary", children: "Click Me", }, }; export const Secondary: Story = { args: { variant: "secondary", children: "Cancel", }, }; export const Loading: Story = { args: { variant: "primary", children: "Submitting...", loading: true, }, }; export const AllSizes: Story = { render: () => ( Small Medium Large ), }; ``` ## 交互测试 (Play Function) Storybook 8 内置了 `@storybook/test`,可以直接在 Story 中编写交互测试: ```tsx import { expect, userEvent, within } from "@storybook/test"; export const FormValidation: Story = { render: () => , play: async ({ canvasElement }) => { const canvas = within(canvasElement); // 不填内容直接提交 await userEvent.click(canvas.getByRole("button", { name: /submit/i })); // 验证错误信息 await expect(canvas.getByText("Email is required")).toBeInTheDocument(); await expect(canvas.getByText("Password is required")).toBeInTheDocument(); // 填写无效邮箱 await userEvent.type(canvas.getByLabelText("Email"), "invalid-email"); await userEvent.click(canvas.getByRole("button", { name: /submit/i })); await expect(canvas.getByText("Invalid email format")).toBeInTheDocument(); // 填写有效数据 await userEvent.clear(canvas.getByLabelText("Email")); await userEvent.type(canvas.getByLabelText("Email"), "user@test.com"); await userEvent.type(canvas.getByLabelText("Password"), "password123"); await userEvent.click(canvas.getByRole("button", { name: /submit/i })); // 验证成功状态 await expect(canvas.getByText("Login successful")).toBeInTheDocument(); }, }; ``` ## 视觉回归测试 配合 Chromatic 或 Percy 实现视觉回归测试: ```bash npm install --save-dev chromatic # 运行视觉测试 npx chromatic --project-token=YOUR_TOKEN ``` 在 CI 中集成: ```yaml # .github/workflows/chromatic.yml jobs: chromatic: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 with: fetch-depth: 0 - run: npm ci - uses: chromaui/action@latest with: projectToken: ${{ secrets.CHROMATIC_TOKEN }} ``` ## 自动生成 API 文档 给组件添加 `tags: ["autodocs"]` 后,Storybook 自动从 TypeScript 类型和 JSDoc 注释生成文档: ```tsx interface ButtonProps { /** Button visual style */ variant?: "primary" | "secondary" | "danger" | "ghost"; /** Button size */ size?: "sm" | "md" | "lg"; /** Whether the button is disabled */ disabled?: boolean; /** Show loading spinner */ loading?: boolean; /** Click handler */ onClick?: () => void; /** Button content */ children: React.ReactNode; } ``` ## 插件推荐 - `@storybook/addon-a11y` — 无障碍检查 - `@storybook/addon-viewport` — 响应式预览 - `@storybook/addon-measure` — 测量间距 - `storybook-dark-mode` — 深色模式切换 ## 最佳实践 1. **每个组件一个 stories 文件**,放在组件同目录下 2. **覆盖所有状态**:正常、加载中、禁用、错误、空、边界 3. **使用 Decorators** 提供共享上下文(主题、路由等) 4. **命名规范**:按功能分组(UI/Button, Form/Input) ## 总结 Storybook 8 实现了组件开发、测试、文档的大统一。CSF3 语法更简洁,play 函数让交互测试无需额外框架,autodocs 自动生成维护成本极低的组件文档。它是组件库开发和设计系统维护的核心工具。

← Back to Blog