文件
Storybook 文件

測試插件

(⚠️ 實驗性功能)

⚠️ **實驗性功能** 🧪

雖然此插件為實驗性功能,但已作為 @storybook/experimental-addon-test 套件發布,且 API 可能在未來版本中變更。我們歡迎您提供回饋與貢獻,以協助改進此功能。

Storybook 的 Test 插件讓您直接在 Storybook 內部測試元件。它本身會將您的 stories 轉換為 元件測試,以在真實瀏覽器環境中測試元件的渲染和行為。它還可以計算由您的 stories 提供的專案 覆蓋率

如果您的專案正在使用其他測試插件,例如 視覺測試插件可訪問性插件,您可以將這些測試與元件測試一起執行。

當 story 執行測試時,狀態會顯示在側邊欄中。側邊欄可以篩選為僅顯示失敗的 stories,您可以按下失敗 story 上的選單按鈕以查看偵錯選項。

您也可以在觀察模式下執行測試,當您變更元件或 stories 時,這會自動重新執行測試。若要啟用,請按下測試模組中的觀察模式切換按鈕(眼睛圖示)。

安裝與設定

在安裝之前,請確保您的專案符合以下需求

  • Storybook ≥ 8.5
  • 使用 Vite 的 Storybook 框架(例如 vue3-vitereact-vite'sveltekit` 等),或 搭配 Vite 的 Storybook Next.js 框架
  • Vitest ≥ 2.1
    • 如果您尚未使用 Vitest,則在您安裝插件時,將會為您安裝和設定 Vitest
  • (選用)MSW ≥ 2.0
    • 如果已安裝 MSW,則必須為 v2.0.0 或更高版本,以免與 Vitest 的依賴項衝突

搭配 Next.js 使用 — Test 插件在 Next.js ≥ 14.1 專案中受到支援,但您必須使用 @storybook/experimental-nextjs-vite 框架。當您執行以下設定命令時,如果您尚未安裝和使用該框架,系統將提示您安裝和使用。

如果您尚未使用 Storybook 8.5,您可以升級您的 Storybook 到預發布版本

npx storybook@next upgrade

自動設定

執行以下命令以安裝和設定插件,其中包含使用 Vitest 將您的 stories 作為測試執行的插件

npx storybook add @storybook/experimental-addon-test

add 命令 將安裝並註冊測試插件。它還將檢查您專案的 Vite 和 Vitest 設定,並在必要時使用合理的預設值安裝和設定它們。您可能需要調整設定以符合您專案的需求。完整的設定選項可以在下方的 API 區段 中找到。

手動設定

對於某些專案設定,add 命令可能無法自動化插件和插件設定,並要求您完成其他設定步驟。以下是應採取的步驟

  1. 確保在您的專案中設定了 Vite 和 Vitest。
  2. 設定 Vitest 以使用 瀏覽器模式
  3. 在您的專案中安裝插件 @storybook/experimental-addon-test,並在您的 Storybook 設定中註冊它。
  4. 建立測試設定檔 .storybook/vitest.setup.ts。您可以使用範例設定檔作為指南。
  5. 調整您的 Vitest 設定以包含插件並參考設定檔。您可以使用範例設定檔作為指南。

設定檔範例

當插件自動設定時,它將為您建立或調整您的 Vitest 設定檔。如果您正在手動設定,您可以將以下範例作為參考來設定您的專案。

Vitest 設定檔範例

Storybook stories 包含在 .storybook/preview.js|ts 中定義的設定。為了確保該設定可用於您的測試,您可以在 Vitest 設定檔中套用它。以下是如何執行此操作的範例

.storybook/vitest.setup.ts
import { beforeAll } from 'vitest';
// 👇 If you're using Next.js, import from @storybook/nextjs
//   If you're using Next.js with Vite, import from @storybook/experimental-nextjs-vite
import { setProjectAnnotations } from '@storybook/react';
import * as previewAnnotations from './preview';
 
const annotations = setProjectAnnotations([previewAnnotations]);
 
// Run Storybook's beforeAll hook
beforeAll(annotations.beforeAll);

setProjectAnnotations 函式是 可攜式 stories API 的一部分,Vitest 插件在內部使用它將您的 stories 轉換為測試。

Vitest 設定檔範例

插件最簡單的應用是將其包含在您的 Vitest 設定檔中

vitest.config.ts
import { defineConfig, mergeConfig } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
 
const dirname =
  typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
 
import viteConfig from './vite.config';
 
export default mergeConfig(
  viteConfig,
  defineConfig({
    plugins: [
      storybookTest({
        // The location of your Storybook config, main.js|ts
        configDir: path.join(dirname, '.storybook'),
        // This should match your package.json script to run Storybook
        // The --ci flag will skip prompts and not open a browser
        storybookScript: 'yarn storybook --ci',
      }),
    ],
    test: {
      // Enable browser mode
      browser: {
        enabled: true,
        name: 'chromium',
        // Make sure to install Playwright
        provider: 'playwright',
        headless: true,
      },
      setupFiles: ['./.storybook/vitest.setup.ts'],
    },
  })
);
Vitest 工作區檔案範例

如果您正在使用 Vitest 工作區,您可以定義新的工作區專案

vitest.workspace.ts
import { defineWorkspace } from 'vitest/config';
import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin';
import path from 'node:path';
import { fileURLToPath } from 'node:url';
 
const dirname =
  typeof __dirname !== 'undefined' ? __dirname : path.dirname(fileURLToPath(import.meta.url));
 
export default defineWorkspace([
  // This is the path to your existing Vitest config file
  './vitest.config.ts',
  {
    // This is the path to your existing Vite config file
    extends: './vite.config.ts',
    plugins: [
      storybookTest({
        // The location of your Storybook config, main.js|ts
        configDir: path.join(dirname, '.storybook'),
        // This should match your package.json script to run Storybook
        // The --ci flag will skip prompts and not open a browser
        storybookScript: 'yarn storybook --ci',
      }),
    ],
    test: {
      name: 'storybook',
      // Enable browser mode
      browser: {
        enabled: true,
        name: 'chromium',
        // Make sure to install Playwright
        provider: 'playwright',
        headless: true,
      },
      setupFiles: ['./.storybook/vitest.setup.ts'],
    },
  },
]);

用法

有多種方法可以使用插件執行測試。

我們建議(並預設設定)在 瀏覽器模式下執行 Vitest,使用 Playwright 的 Chromium 瀏覽器。瀏覽器模式確保您的元件在真實瀏覽器環境中進行測試,這比 JSDom 或 HappyDom 等模擬更準確。對於測試依賴瀏覽器 API 或功能的元件而言,這尤其重要。

Storybook UI

執行測試最簡單的方法是透過 Storybook UI。只需按一下,您就可以為專案中的所有 stories、stories 群組或單個 story 執行多種類型的測試。

若要為整個專案執行所有測試,請按下側邊欄底部測試模組中的「執行測試」按鈕。

或者,您可以展開測試模組以個別執行特定類型的測試。對於那些具有觀察模式的測試類型(這將在程式碼變更時自動重新執行相關測試),您可以開啟或關閉該模式。

Screenshot of test module, expanded, showing test types and watch mode toggle

如果您安裝了視覺測試插件,您將看到一個選項,可以與元件測試一起執行視覺測試。

Screenshot of test module, expanded, showing Visual tests

其他插件,例如 a11y,也可能提供可以從測試模組執行的測試類型,並影響 stories 和元件上的狀態指示器。

若要為特定的 story 或 stories 群組執行測試,請按下側邊欄項目懸停時出現的選單按鈕(三個點)。然後,您可以選擇要執行的測試類型。

Screenshot of story sidebar item with open menu

執行測試後,您現在將在 stories 和元件上看到狀態指示器,以指示它們的通過、失敗或錯誤狀態。當您懸停在 story 上時,可以按下選單按鈕以查看該 story 的測試結果。在選單中選擇結果將會導航您到該 story 並開啟適當的偵錯面板。例如,如果元件測試失敗,您可以直接跳到元件測試插件面板中的失敗處。該面板為您的元件測試提供了一個互動式偵錯工具,讓您可以逐步執行每個模擬行為或斷言。

當安裝 Test 插件時,元件測試插件面板會取代 Interactions 插件面板。雖然測試機制不同,但插件面板本身的功能保持不變。

測試模組還將顯示您執行的測試總數、通過的測試數量以及失敗或錯誤的測試數量。您可以按下失敗數字來篩選側邊欄,使其僅顯示那些失敗的 stories。

CLI

您也可以使用 Vitest CLI 執行測試。我們建議在您的 package.json 中新增腳本,以簡化執行測試的步驟。以下是如何執行此操作的範例

package.json
{
  "scripts": {
    "test": "vitest",
    "test-storybook": "vitest --project=storybook"
  }
}

在此範例中,我們新增了兩個腳本:test 用於執行專案中的所有測試(您可能已經有此腳本),以及 test-storybook 用於僅執行您的 Storybook 測試。--project=storybook 標記告訴 Vitest 執行 Storybook 專案的測試。

然後,執行此命令以使用 Vitest CLI 執行您的測試(在 觀察模式下,預設情況下)

npm run test-storybook

偵錯

雖然插件在測試時不需要 Storybook 執行,但您可能仍然希望執行 Storybook 以偵錯您的測試。若要啟用此功能,請在插件設定中提供 storybookScript 選項。當您在觀察模式下執行 Vitest 時,插件將使用此腳本啟動 Storybook,並在測試失敗時在輸出中提供 stories 的連結。這可讓您快速跳到 Storybook 中的 story 以偵錯問題。

您也可以在插件設定中提供 storybookUrl 選項。當您未使用觀察模式且測試失敗時,插件將在輸出中使用此 URL 提供 story 的連結。當 在 CI 中執行測試 或 Storybook 尚未執行的其他環境中時,這非常有用。

Screenshot of test failure in the console, showing a failure with a link to the story

編輯器擴充功能

使用插件將您的 stories 轉換為 Vitest 測試,還可以讓您使用 Vitest IDE 整合來執行和偵錯測試。這可讓您直接從編輯器(例如 VSCode 和 JetBrains IDE)執行測試。

此螢幕截圖顯示如何在 VSCode 中使用 Vitest 擴充功能執行 Vitest 測試。Stories 會使用測試狀態進行註釋,並且當測試失敗時,會提供 story 的連結以進行偵錯

Screenshot of test failure in VSCode, showing a failure attached to a story

在 CI 中

在大多數情況下,在 CI 中執行 Storybook 測試是透過 CLI 完成的。但是,為了讓測試輸出連結到您發布的 Storybook 以處理測試失敗,您需要在插件設定中提供 storybookUrl 選項

以下是使用 GitHub Actions 的範例。其他 CI 提供者的步驟類似,但語法或設定中的詳細資訊可能會有所不同。

當 Vercel、Netlify 等服務的動作執行部署工作時,它們會遵循發出 deployment_status 事件的模式,其中包含 deployment_status.target_url 下新產生的 URL。這是已發布 Storybook 實例的 URL。然後,我們使用環境變數 SB_URL 將該 URL 傳遞到插件設定。最後,我們更新插件設定以在 storybookUrl 選項中使用該環境變數。

.github/workflows/test-storybook.yml
name: Storybook Tests
on: deployment_status
jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    if: github.event.deployment_status.state == 'success'
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '18.x'
      - name: Install dependencies
        run: yarn
      - name: Run Storybook tests
        run: yarn test-storybook
        env:
          SB_URL: '${{ github.event.deployment_status.target_url }}'
vitest.workspace.ts
export default defineWorkspace([
  // ...
  {
    // ...
    {
      plugins: [
        storybookTest({
          // ...
          storybookScript: 'yarn storybook --ci',
          storybookUrl: process.env.SB_URL
        }),
      ],
    },
  },
])

運作方式

Test 插件透過使用 Vitest 插件將您的 stories 轉換為使用 可攜式 storiesVitest 測試來運作。它還設定 Vitest 以在 瀏覽器模式下執行這些測試,使用 Playwright 的 Chromium 瀏覽器。由於它建立在 Vitest 之上,因此插件需要基於 Vite 的 Storybook 框架。

Stories 以兩種方式進行測試:冒煙測試以確保其渲染,以及如果定義了 play 函式,則執行該函式並驗證其中進行的任何斷言

當您在 Storybook UI 中執行測試時,插件會在背景中執行 Vitest,並在側邊欄中報告結果。

設定測試

可以透過兩種方式設定插件執行的測試。您可以切換要執行的測試類型,並包含、排除或略過要測試的 stories。

切換測試類型

除了元件測試外,Test 插件還支援多種類型的測試,具體取決於您在專案中使用的其他插件。某些測試類型(例如 視覺測試)是獨立執行的。其他測試類型(例如 可訪問性)必須與元件測試一起執行。對於這些依賴性測試類型,您可以透過按下編輯按鈕(鉛筆圖示)並勾選或取消勾選您想要執行的測試類型,在測試模組中開啟或關閉它們。

Screenshot of test module, expanded, edit mode, everything is checked

請注意,根據您已安裝的插件,您可能不會擁有圖片中顯示的所有測試類型。

您也可以在 story 或 stories 群組的側邊欄項目選單中存取編輯模式

Screenshot of story sidebar item with open menu, edit mode

請注意,在選單的編輯模式中切換測試類型會影響所有測試,而不僅僅是針對所選 story 或 stories 群組的測試。它的目的是為了方便快速開啟或關閉測試類型。

包含、排除或略過測試

您可以使用標籤來包含、排除或略過要測試的 stories。包含的 stories 會進行測試,排除的 stories 不會進行測試,而略過的 stories 則不會進行測試,但會計入測試結果中。

預設情況下,插件將執行所有帶有 test 標籤的 stories。您可以透過在插件設定中提供 tags 選項來調整此行為。這可讓您根據 stories 的標籤來包含、排除或略過 stories。

在此範例中,我們將 stable 標籤套用至 Button 元件的所有 stories,但 ExperimentalFeatureStory 除外,後者將具有 experimental 標籤

Button.stories.ts
// Replace your-framework with the framework you are using (e.g., nextjs, vue3-vite)
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { Button } from './Button';
 
const meta: Meta<typeof Button> = {
  component: Button,
  // 👇 Applies to all stories in this file
  tags: ['stable'],
};
 
export default meta;
type Story = StoryObj<typeof Button>;
 
export const ExperimentalFeatureStory: Story = {
  //👇 For this particular story, remove the inherited `stable` tag and apply the `experimental` tag
  tags: ['!stable', 'experimental'],
};

若要將這些標籤連接到我們的測試行為,我們可以調整插件設定以排除 experimental 標籤

vitest.workspace.ts
export default defineWorkspace([
  // ...
  {
    // ...
    {
      plugins: [
        storybookTest({
          // ...
          tags: {
            include: ['test'],
            exclude: ['experimental'],
          },
        }),
      ],
    },
  },
])

如果相同的標籤同時位於 includeexclude 陣列中,則 exclude 行為優先。

與測試執行器的比較

測試執行器需要執行的 Storybook 實例來測試您的 stories,因為它會訪問每個 story、執行 play 函式並監聽結果。但是,Vitest 插件會使用 Vite 和可攜式 stories 將您的 stories 轉換為測試,因此它不需要執行 Storybook 即可測試您的 stories。由於對 Vite 的依賴,該插件只能與使用 Vite 的 Storybook 框架(和 Next.js)一起使用。另一方面,測試執行器可以與任何 Storybook 框架一起使用。

測試執行器僅是一個 CLI 工具。它沒有用於執行測試的 UI,也沒有編輯器擴充功能。但是,該插件在 Storybook 中提供了一個 UI 用於執行測試,並且它使您能夠使用 Vitest IDE 整合來執行和偵錯測試。

此外,測試執行器在 Jest 中將您的 stories 作為協調測試執行,並且該協調帶來了一些複雜性。相比之下,此插件將您的 stories 轉換為真實測試,然後使用 Vitest 執行它們,這更簡單且更可設定。

最後,由於更簡單的架構和 Vitest 的使用,對於大多數專案而言,此插件應該比測試執行器更快。我們將在未來進行更多基準測試以量化這一點。

常見問題

如果 Vitest 本身發生錯誤會怎麼樣?

有時測試可能會因為 Vitest 本身發生錯誤而失敗。當這種情況發生時,Storybook UI 中的測試模組會提醒您注意此錯誤,您可以點擊連結以檢視完整錯誤訊息。錯誤訊息也會記錄到主控台。

Screenshot of test module, expanded, showing Vitest error

Vitest 針對常見錯誤提供疑難排解協助。

當多個環境中出現不同的測試結果時,會發生什麼情況?

當您使用此附加元件執行測試時,它們會以 Vitest 測試的方式執行,並採用您在專案中設定的任何配置。預設情況下,它們將在瀏覽器模式下執行,使用 Playwright 的 Chromium 瀏覽器。有時,在附加元件中(或透過 CLI)執行測試會失敗,但在元件測試附加元件面板中檢視時(或反之亦然)卻會通過。這可能是因為測試在不同的環境中執行,而這些環境可能具有不同的行為。

我如何在 Storybook 中偵錯我的 CLI 測試?

當 CLI 中的測試失敗時,此插件會嘗試提供 Storybook 中故事的連結,以用於偵錯目的。

如果網址在監看模式下執行測試時無法運作,您應該檢查兩個配置選項

  • storybookUrl:確保此網址正確且可存取。例如,預設值為 https://#:6006,這可能未使用您正在使用的相同埠號。
  • storybookScript:確保此腳本正確啟動 Storybook。

如果網址在 CI 中執行測試時無法運作,您應該確保在執行測試之前已建置並發布 Storybook。然後,您可以使用 storybookUrl 選項提供已發布 Storybook 的網址。請參閱在 CI 中章節以取得範例。

我如何確保我的測試可以找到 public 目錄中的資源?

如果您的故事使用 public 目錄中的資源,且您未使用預設的 public 目錄位置(public),則需要調整 Vitest 配置以包含 public 目錄。您可以透過在 Vitest 配置檔案中提供 publicDir 選項來執行此操作。

我如何將 Storybook 測試與其他測試隔離?

某些專案可能在其 Vite 配置中包含 test 屬性。由於此插件使用的 Vitest 配置擴展了該 Vite 配置,因此 test 屬性會合併。這種缺乏隔離可能會導致您的 Storybook 測試出現問題。

若要將您的 Storybook 測試與其他測試隔離,您需要將 test 屬性從您的 Vite 配置移至 Vitest 配置。然後,插件使用的 Vitest 配置可以安全地擴展您的 Vite 配置,而不會合併 test 屬性。

為什麼我們建議使用瀏覽器模式?

Vitest 的瀏覽器模式會在真實瀏覽器(預設配置中透過 Playwright 使用 Chromium)中執行您的測試。另一種替代方案是模擬的瀏覽器環境,例如 JSDom 或 HappyDom,與真實瀏覽器相比,它們的行為可能存在差異。對於 UI 組件,它們通常依賴瀏覽器 API 或功能,在真實瀏覽器中執行測試更準確。

如需更多資訊,請參閱 Vitest 關於有效使用瀏覽器模式的指南

我如何使用 WebDriver 而不是 Playwright?

我們建議使用 Playwright 在瀏覽器中執行測試,但您也可以改用 WebDriverIO。若要執行此操作,您需要調整 Vitest 配置檔案中的瀏覽器供應商

我如何使用 Chromium 以外的瀏覽器?

我們建議使用 Chromium,因為它最有可能最符合您大多數使用者的體驗。但是,您可以透過調整 Vitest 配置檔案中的瀏覽器名稱來使用其他瀏覽器。請注意,Playwright 和 WebDriverIO 支援不同的瀏覽器

我如何自訂測試名稱?

預設情況下,故事的匯出名稱會對應到測試名稱。若要建立更具描述性的測試說明,您可以為故事提供 name 屬性。這可讓您包含空格、括號或其他特殊字元。

Example.stories.js|ts
export const Story = {
  name: 'custom, descriptive name'
};

我該如何修正 m.createRoot is not a function 錯誤?

當在使用 React 18 以外版本的專案上使用附加元件時,可能會發生此錯誤。若要解決此問題,您可以提供別名以確保使用正確的 React 版本。以下範例說明如何在 Vitest 配置檔案中執行此操作

vitest.config.ts
import { defineConfig } from 'vitest/config';
 
export default defineConfig({
  // ...
  resolve: {
    alias: {
      "@storybook/react-dom-shim": "@storybook/react-dom-shim/dist/react-16",
    },
  },
});

API

匯出

此附加元件具有以下匯出

import { storybookTest } from '@storybook/experimental-addon-test/vitest-plugin'

storybookTest

類型:function

一個 Vitest 插件,可將您的故事轉換為測試。它接受用於配置的選項物件

選項

插件是使用選項物件配置的。以下是可用的屬性

configDir

類型:string

預設值:.storybook

相對於目前工作目錄,Storybook 配置所在的目錄。

如果您的 Storybook 配置不在預設位置,您必須在此處指定位置,插件才能正常運作。

storybookScript

類型:string

用於執行 Storybook 的選用腳本。如果提供此腳本,Vitest 將在監看模式下執行時使用此腳本啟動 Storybook。僅當 storybookUrl 中的 Storybook 尚不可用時才會執行。

storybookUrl

類型:string

預設值:https://#:6006

Storybook 託管的網址。這用於內部檢查,並提供測試輸出失敗時故事的連結

tags

類型

{
  include: string[];
  exclude: string[];
  skip: string[];
}

預設值

{
  include: ['test'],
  exclude: [],
  skip: [],
}

要包含、排除或略過的標籤。這些標籤定義為您的故事、meta 或預覽中的註釋。

  • include:具有這些標籤的故事將會進行測試
  • exclude:具有這些標籤的故事將不會進行測試,也不會計入測試結果中
  • skip:具有這些標籤的故事將不會進行測試,但會計入測試結果中