📋 目錄
你會學到怎麼把 Storybook 從「展示櫥窗」升級成「可執行規格」,讓文件與測試共享同一份來源。
前言
很多團隊都有同一個痛點:元件文件在一處、測試在另一處,久了就會分歧。文件寫「按鈕可 disabled」,測試卻沒驗;或測試涵蓋了關鍵互動,但文件根本沒寫到。最終結果是,大家都以為自己有規格,實際上沒有單一真相來源。
Storybook 最有價值的地方,不只是「看得到元件」,而是讓每個 story 同時承擔三件事:展示、文件、可執行測試。這樣規格不是只存在於文字,而是能被 CI 驗證。
這篇會以 2026-04-02 可驗證的主線 寫法為基準:storybook + @storybook/addon-vitest + storybook/test。同時也會標註舊做法 @storybook/test-runner 的適用情境,避免版本混線。
先釐清版本邊界:目前建議做法 vs 舊版做法
截至 2026-04-02(npm dist-tags):
storybooklatest:10.3.3@storybook/addon-vitestlatest:10.3.3vitestlatest:4.1.2playwrightlatest:1.59.1@storybook/test-runnerlatest:0.24.3(舊路線,仍可用)
在 Storybook 官方文件中,Vite-based framework 已明確建議優先使用 Vitest addon;test-runner 則偏向舊路線或非 Vite 情境。
你可以用下面的簡單判斷:
- 已用 Vite(React/Vue/Next.js Vite framework)→ 優先
@storybook/addon-vitest - 歷史包袱重、仍綁 Jest 流程 → 可暫留
@storybook/test-runner - 新專案、希望文件和測試完全同軌 → 直接走 Vitest addon
play function 的正確定位:互動腳本,不是測試執行器
play function 常被誤解。它的角色是「描述這個 story 在畫面上要怎麼互動與斷言」,例如點擊、輸入、檢查文字。它不是 Playwright runner,也不是 Vitest runner 本身。
真正執行測試的是測試框架(這條主線是 Vitest),而 Storybook addon 會把 stories 轉成 Vitest 可跑的測試。你可以把責任拆成:
story:元件在特定狀態下的規格play:該規格下的互動腳本與斷言@storybook/addon-vitest:把 stories 轉成可執行測試- Vitest browser mode + Playwright:在真實瀏覽器環境執行
這樣分層清楚,遇到失敗時你才知道該修 story、修測試腳本,還是修執行環境。
實作步驟:把 Storybook 文件與測試整合到同一條主線
1) 安裝與初始化
# 初始化或升級 Storybook
npx storybook@latest init
# 安裝並自動設定 Vitest addon
npx storybook@latest add @storybook/addon-vitest
# 安裝 Playwright browser binaries(CI 與本機都建議明確執行)
npx playwright install --with-deps chromium
add 指令會協助你註冊 addon、調整 Vitest 設定,並把 Storybook 測試拉進 browser mode。
2) 設定 .storybook/main.ts
// .storybook/main.ts
import type { StorybookConfig } from '@storybook/react-vite';
const config: StorybookConfig = {
framework: '@storybook/react-vite',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(ts|tsx)'],
addons: [
'@storybook/addon-docs',
'@storybook/addon-a11y',
'@storybook/addon-vitest', // 讓 story 可直接轉成 Vitest 測試
],
};
export default config;
重點是 framework 要走 Vite 版本(例如 @storybook/react-vite、@storybook/nextjs-vite),否則無法套用這條主線。
3) 在 story 同時寫文件與互動測試
// src/components/Button.stories.tsx
import type { Meta, StoryObj } from 'storybook/react';
import { expect, fn, userEvent, within } from 'storybook/test';
import { Button } from './Button';
const meta = {
title: 'Form/Button',
component: Button,
tags: ['autodocs'],
parameters: {
docs: {
description: {
// component 層級描述,放在 meta 才是正確位置
component: '通用按鈕元件,支援主要操作、次要操作與 disabled 狀態。',
},
},
},
args: {
onClick: fn(),
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const Primary: Story = {
args: {
children: '送出',
disabled: false,
},
};
export const Disabled: Story = {
args: {
children: '送出',
disabled: true,
},
};
export const Clickable: Story = {
args: {
children: '送出',
disabled: false,
},
play: async ({ canvasElement, args }) => {
const canvas = within(canvasElement);
const button = canvas.getByRole('button', { name: '送出' });
await userEvent.click(button);
// 在 story 規格中完成互動斷言
await expect(args.onClick).toHaveBeenCalledTimes(1);
await expect(button).toBeEnabled();
},
};
這個範例有兩個關鍵:
parameters.docs.description.component放在meta層級,而不是單一 storyplay是互動規格,搭配storybook/test的查找與斷言 API
4) CLI 與 CI:讓 story 在 pipeline 可被驗證
package.json 可以分開一般測試與 Storybook 測試:
{
"scripts": {
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
"test": "vitest",
"test-storybook": "vitest --project=storybook --run"
}
}
GitHub Actions 範例:
name: Storybook Component Tests
on:
pull_request:
push:
branches: [main]
jobs:
storybook-tests:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: npm
- run: npm ci
- name: Install Playwright Chromium
run: npx playwright install --with-deps chromium
- name: Run Storybook tests via Vitest project
run: npm run test-storybook
實務上,build-storybook 不一定每次都要跑;如果你的目標是先驗證互動規格,vitest --project=storybook 就能先擋掉大部分回歸。發佈流程再補 build,可讓 PR 回饋更快。
什麼時候該上 Storybook 測試?給團隊的決策框架
以下是我建議的落地順序:
- 先上文件(Stories + Autodocs):先讓可見性建立起來
- 補關鍵互動的
play:像表單送出、狀態切換、權限按鈕 - 進 CI,但只跑高價值 story:避免一次把所有故事都變阻塞點
- 最後再擴 coverage:依事故或 bug 熱點擴充,不要盲目追全覆蓋
如果你的元件是靜態展示型(純排版、無事件),只寫 story 文件通常就夠。反過來,若元件有狀態轉換與副作用(Modal、Toast、Form Wizard),建議一定要有 play 與 CI 驗證。
常見問題 / 注意事項
Q1:@storybook/test-runner 還能用嗎?
可以,尤其在既有 Jest 流程或非 Vite 架構下仍有價值;但若你是 Vite-based Storybook,官方主線已偏向 Vitest addon。
Q2:play 能不能等同 E2E 測試?
不能。play 比較像 component interaction spec,適合元件級互動。跨頁導覽、後端整合、權限流等情境,仍建議保留 Playwright/Cypress 的完整 E2E。
Q3:Autodocs 會自動幫我產生每個 story 截圖嗎?
不會。Autodocs 主要負責把 stories、args、型別資訊整理成文件頁。若要做視覺回歸或快照比對,要另外導入對應測試方案。
Q4:Next.js 專案可以用這條主線嗎?
可以,但要使用 Vite-based 的 Storybook framework(例如 @storybook/nextjs-vite),再搭配 Vitest addon。
總結
Storybook 的進階價值,不是「把元件秀出來」,而是讓元件規格可執行、可回歸、可進 CI。當 stories 既是文件又是測試入口,團隊就能把規格維護成本降到最低。
這篇重點可以收斂成三句話:
- 把版本基線定清楚:Vite framework +
@storybook/addon-vitest+storybook/test。 - 把責任分層:
play是互動腳本,Vitest/Playwright 才是執行環境。 - 把 CI 策略做漸進:先跑高價值 story,再擴大覆蓋。
照這個節奏做,你的 Storybook 就不會停在「UI 展示」,而會成為真正可驗證的元件規格系統。