文件
Storybook 文件

測試執行器

Storybook 測試執行器會將您的所有 Story 轉換為可執行的測試。它由 JestPlaywright 提供支援。

  • 對於那些沒有 play 函式的 Story:它會驗證 Story 是否在沒有任何錯誤的情況下渲染。
  • 對於那些具有 play 函式的 Story:它還會檢查 play 函式中的錯誤,並檢查是否所有斷言都通過了。

這些測試會在即時瀏覽器中執行,並且可以透過命令列或您的 CI 伺服器執行。

設定

測試執行器是一個獨立的、與框架無關的公用程式,它與您的 Storybook 並行執行。您需要採取一些額外的步驟才能正確設定它。以下詳細說明我們建議的設定和執行方式。

執行以下命令來安裝它。

npm install @storybook/test-runner --save-dev

更新您的 package.json 指令碼,並啟用測試執行器。

{
  "scripts": {
    "test-storybook": "test-storybook"
  }
}

使用以下方式啟動您的 Storybook

npm run storybook

Storybook 的測試執行器需要本機執行的 Storybook 執行個體或已發佈的 Storybook,才能執行所有現有的測試。

最後,開啟一個新的終端機視窗,並使用以下命令執行測試執行器

npm run test-storybook

設定

測試執行器為 Storybook 提供零配置支援。不過,您可以執行 test-storybook --eject 來獲得更精細的控制。它會在專案的根目錄產生 test-runner-jest.config.js 檔案,您可以修改此檔案。此外,您可以擴充產生的組態檔案,並提供 testEnvironmentOptions,因為測試執行器在底層也使用 jest-playwright

CLI 選項

測試執行器由 Jest 提供支援,並接受其 CLI 選項的子集(例如,--watch--maxWorkers)。如果您已在專案中使用任何這些旗標,您應該能夠將它們遷移到 Storybook 的測試執行器中,而不會有任何問題。以下列出所有可用的旗標以及使用它們的範例。

選項描述
--help輸出使用方式資訊
test-storybook --help
-s--index-json以索引 json 模式執行。自動偵測(需要相容的 Storybook)
test-storybook --index-json
--no-index-json停用索引 json 模式
test-storybook --no-index-json
-c--config-dir [dir-name]從中載入 Storybook 設定的目錄
test-storybook -c .storybook
--watch以監看模式執行
test-storybook --watch
--watchAll監看檔案的變更,並在發生變更時重新執行所有測試。
test-storybook --watchAll
--coverage在您的 Story 和元件上執行覆蓋率測試
test-storybook --coverage
--coverageDirectory寫入覆蓋率報告輸出的目錄
test-storybook --coverage --coverageDirectory coverage/ui/storybook
--url定義執行測試的 URL。適用於自訂 Storybook URL
test-storybook --url http://the-storybook-url-here.com
--browsers定義執行測試的瀏覽器。可以是以下一個或多個:chromium、firefox、webkit
test-storybook --browsers firefox chromium
--maxWorkers [數量]指定工作執行緒集區為執行測試而產生之工作執行緒的最大數量
test-storybook --maxWorkers=2
--testTimeout [數量]定義測試在自動標示為失敗之前可以執行的最長時間 (以毫秒為單位)。適用於長時間執行的測試
test-storybook --testTimeout=60000
--no-cache停用快取
test-storybook --no-cache
--clearCache刪除 Jest 快取目錄,然後結束而不執行測試
test-storybook --clearCache
--verbose以測試套件階層結構顯示個別測試結果
test-storybook --verbose
-u, --updateSnapshot使用此標記來重新錄製本次測試執行期間失敗的每個快照
test-storybook -u
--eject建立本機設定檔,以覆寫測試執行器的預設值
test-storybook --eject
--json以 JSON 格式列印測試結果。此模式會將所有其他測試輸出和使用者訊息傳送到 stderr。
test-storybook --json
--outputFile當也指定了 --json 選項時,將測試結果寫入檔案。
test-storybook --json --outputFile results.json
--junit表示應在 junit 檔案中報告測試資訊。
test-storybook --**junit**
--ci它不會自動儲存新的快照,而是會讓測試失敗,並要求使用 --updateSnapshot 執行 Jest。
test-storybook --ci
--shard [index/count]需要 CI。將測試套件的執行分割到多個機器上
test-storybook --shard=1/8
--failOnConsole讓測試在瀏覽器主控台發生錯誤時失敗
test-storybook --failOnConsole
--includeTags實驗性功能
定義要測試的故事子集,如果它們符合啟用的標籤
test-storybook --includeTags="test-only, pages"
--excludeTags實驗性功能
如果故事符合提供的標籤,則會防止測試這些故事。
test-storybook --excludeTags="no-tests, tokens"
--skipTags實驗性功能
將測試執行器設定為跳過執行符合提供的標籤的故事的測試。
test-storybook --skipTags="skip-test, layout"
npm run test-storybook -- --watch

針對已部署的 Storybook 執行測試

依預設,測試執行器假設您正在針對本機伺服器上的 Storybook,連接埠為 6006 執行測試。如果您想要定義目標 URL 以針對已部署的 Storybook 執行測試,您可以使用 --url 標記

npm run test-storybook -- --url https://the-storybook-url-here.com

或者,您可以設定 TARGET_URL 環境變數並執行測試執行器

TARGET_URL=https://the-storybook-url-here.com yarn test-storybook

設定 CI 以執行測試

您也可以設定測試執行器在 CI 環境中執行測試。以下是一些可以幫助您入門的秘訣。

透過 Github Actions 部署針對已部署的 Storybook 執行測試

如果您使用 VercelNetlify 等服務發佈您的 Storybook,它們會在 GitHub Actions 中發出 deployment_status 事件。您可以使用它,並將 deployment_status.target_url 設定為 TARGET_URL 環境變數。以下說明如何操作

# .github/workflows/storybook-tests.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-file: '.nvmrc'
      - name: Install dependencies
        run: yarn
      - name: Install Playwright
        run: npx playwright install --with-deps
      - name: Run Storybook tests
        run: yarn test-storybook
        env:
          TARGET_URL: '${{ github.event.deployment_status.target_url }}'

發佈的 Storybook 必須公開可用,此範例才能運作。如果需要驗證,我們建議使用下方的秘訣來執行測試伺服器。

針對未部署的 Storybook 執行測試

您可以使用您的 CI 提供者(例如,GitHub ActionsGitLab PipelinesCircleCI)來建置和執行測試執行器,以針對您建置的 Storybook 執行測試。以下是一個依賴協力廠商程式庫的秘訣,也就是說,concurrentlyhttp-serverwait-on 來建置 Storybook 並使用測試執行器執行測試。

# .github/workflows/storybook-tests.yml
 
name: 'Storybook Tests'
on: push
jobs:
  test:
    timeout-minutes: 60
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version-file: '.nvmrc'
      - name: Install dependencies
        run: yarn
      - name: Install Playwright
        run: npx playwright install --with-deps
      - name: Build Storybook
        run: yarn build-storybook --quiet
      - name: Serve Storybook and run tests
        run: |
          npx concurrently -k -s first -n "SB,TEST" -c "magenta,blue" \
            "npx http-server storybook-static --port 6006 --silent" \
            "npx wait-on tcp:127.0.0.1:6006 && yarn test-storybook"

依預設,Storybook 會將建置輸出到 storybook-static 目錄。如果您使用的是不同的建置目錄,則需要相應地調整秘訣。

Chromatic 和測試執行器有何差異?

測試執行器是一種通用測試工具,可以在本機或 CI 上執行,並且可以設定或擴充以執行各種測試。

Chromatic 是一個雲端服務,可執行視覺元件測試(以及即將推出的協助工具測試),而無需設定測試執行器。它也會與您的 git 提供者同步,並管理私人專案的存取控制。

但是,在某些情況下,您可能想要將測試執行器和 Chromatic 配對使用。

  • 在本機上使用,並在您的 CI 上使用 Chromatic。
  • 使用 Chromatic 進行視覺和元件測試,並使用測試執行器執行其他自訂測試。

進階設定

測試掛勾 API

測試執行器會轉譯一個故事,並執行其play 函式(如果有的話)。但是,某些行為無法透過在瀏覽器中執行的 play 函式來達成。例如,如果您想要讓測試執行器為您拍攝視覺快照,這可以透過 Playwright/Jest 實現,但必須在 Node 中執行。

測試執行器會匯出可以全域覆寫的測試掛勾,以啟用視覺或 DOM 快照等使用案例。這些掛勾可讓您在轉譯故事之前之後存取測試生命週期。以下列出可用的掛勾,以及如何使用它們的概觀。

掛勾描述
prepare準備瀏覽器進行測試
async prepare({ page, browserContext, testRunnerConfig }) {}
setup在所有測試執行之前執行一次
setup() {}
preVisit在首次瀏覽故事並在瀏覽器中轉譯之前執行
async preVisit(page, context) {}
postVisit在瀏覽並完全轉譯故事後執行
async postVisit(page, context) {}

這些測試掛勾是實驗性的,可能會隨時變更。我們鼓勵您盡可能在故事的 play 函式中進行測試。

若要啟用掛勾 API,您需要在 Storybook 目錄中新增一個設定檔,並依照以下方式進行設定

.storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';
 
const config: TestRunnerConfig = {
  // Hook that is executed before the test runner starts running tests
  setup() {
    // Add your configuration here.
  },
  /* Hook to execute before a story is initially visited before being rendered in the browser.
   * The page argument is the Playwright's page object for the story.
   * The context argument is a Storybook object containing the story's id, title, and name.
   */
  async preVisit(page, context) {
    // Add your configuration here.
  },
  /* Hook to execute after a story is visited and fully rendered.
   * The page argument is the Playwright's page object for the story
   * The context argument is a Storybook object containing the story's id, title, and name.
   */
  async postVisit(page, context) {
    // Add your configuration here.
  },
};
 
export default config;

除了 setup 函式之外,所有其他函式都會非同步執行。preVisitpostVisit 函式都包含兩個額外引數,即 Playwright 頁面和一個內容物件,其中包含故事的 idtitlename

當測試執行器執行時,您現有的測試會經歷以下生命週期

  • 會在所有測試執行之前執行 setup 函式。
  • 會產生包含所需資訊的內容物件。
  • Playwright 會瀏覽至故事的頁面。
  • 會執行 preVisit 函式。
  • 會轉譯故事,並執行任何現有的 play 函式。
  • 會執行 postVisit 函式。

(實驗性)篩選測試

當你在 Storybook 上執行測試工具時,預設會測試每個 story。然而,如果你想篩選測試,可以使用 tags 配置選項。 Storybook 最初引入此功能是為了產生 story 的自動文件。但是,它可以進一步擴展,以使用類似的配置選項或透過 CLI 標誌(例如,--includeTags--excludeTags--skipTags)來配置測試工具,以根據提供的標籤執行測試,這些標誌僅在最新的穩定版本(0.15 或更高版本)中可用。下面列出了可用的選項以及如何使用它們的概述。

選項描述
排除 (exclude)防止符合所提供標籤的 story 被測試。
包含 (include)定義一個 story 的子集,只有當它們符合啟用的標籤時才進行測試。
跳過 (skip)如果 story 符合所提供的標籤,則跳過該 story 的測試。
.storybook/test-runner.ts
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;

使用 CLI 標誌執行測試的優先級高於在設定檔中提供的選項,並且會覆蓋設定檔中的可用選項。

停用測試

如果你想防止測試工具測試特定的 story,你可以使用自訂標籤配置你的 story,將其啟用到測試工具的設定檔中,或者使用 --excludeTags CLI 標誌執行測試工具,並將它們排除在測試之外。當你想排除尚未準備好進行測試或與你的測試無關的 story 時,這很有幫助。例如:

MyComponent.stories.ts|tsx
// Replace your-framework with the name of your framework
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { MyComponent } from './MyComponent';
 
const meta: Meta<typeof MyComponent> = {
  component: MyComponent,
  tags: ['no-tests'], // 👈 Provides the `no-tests` tag to all stories in this file
};
 
export default meta;
type Story = StoryObj<typeof 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'],
};

為 story 的子集執行測試

若要允許測試工具僅在特定 story 或 story 的子集上執行測試,你可以使用自訂標籤配置 story,在測試工具的設定檔中啟用它,或者使用 --includeTags CLI 標誌執行測試工具,並將它們包含在你的測試中。例如,如果你想根據 test-only 標籤執行測試,你可以如下調整你的設定:

MyComponent.stories.ts|tsx
// Replace your-framework with the name of your framework
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { MyComponent } from './MyComponent';
 
const meta: Meta<typeof MyComponent> = {
  component: MyComponent,
  tags: ['test-only'], // 👈 Provides the `test-only` tag to all stories in this file
};
 
export default meta;
type Story = StoryObj<typeof MyComponent>;
 
export const IncludeStory: Story = {
  //👇 Adds the `test-only` tag to this story to be included in the tests when enabled in the test-runner configuration
  tags: ['test-only'],
};

為元件的 story 應用標籤應在元件層級 (使用 meta) 或在 story 層級完成。 Storybook 不支援在 story 之間導入標籤,且不會如預期般運作。

跳過測試

如果你想跳過在特定 story 或 story 子集上執行測試,你可以使用自訂標籤配置你的 story,在測試工具的設定檔中啟用它,或使用 --skipTags CLI 標誌執行測試工具。使用此選項執行測試會導致測試工具忽略這些測試,並在測試結果中相應地標記它們,表示這些測試已暫時停用。例如:

MyComponent.stories.ts|tsx
// Replace your-framework with the name of your framework
import type { Meta, StoryObj } from '@storybook/your-framework';
 
import { MyComponent } from './MyComponent';
 
const meta: Meta<typeof MyComponent> = {
  component: MyComponent,
  tags: ['skip-test'], // 👈 Provides the `skip-test` tag to all stories in this file
};
 
export default meta;
type Story = StoryObj<typeof MyComponent>;
 
export const SkipStory: Story = {
  //👇 Adds the `skip-test` tag to this story to allow it to be skipped in the tests when enabled in the test-runner configuration
  tags: ['skip-test'],
};

已部署 Storybook 的身份驗證

如果你使用需要身份驗證的安全託管服務供應商來託管你的 Storybook,你可能需要設定 HTTP 標頭。這主要是因為測試工具如何透過 fetch 請求和 Playwright 檢查實例的狀態及其 story 的索引。要執行此操作,你可以修改測試工具的設定檔以包含 getHttpHeaders 函數。此函數將 fetch 呼叫和頁面訪問的 URL 作為輸入,並返回一個包含需要設定的標頭的物件。

.storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';
 
const config: TestRunnerConfig = {
  getHttpHeaders: async (url) => {
    const token = url.includes('prod') ? 'prod-token' : 'dev-token';
    return {
      Authorization: `Bearer ${token}`,
    };
  },
};
 
export default config;

輔助函數

測試工具匯出了一些輔助函數,可以透過存取 Storybook 的內部元件(例如,argsparameters)來使你的測試更具可讀性和可維護性。下面列出了可用的輔助函數以及如何使用它們的概述。

.storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';
import { getStoryContext, waitForPageReady } from '@storybook/test-runner';
 
const config: TestRunnerConfig = {
  // Hook that is executed before the test runner starts running tests
  setup() {
    // Add your configuration here.
  },
  /* Hook to execute before a story is initially visited before being rendered in the browser.
   * The page argument is the Playwright's page object for the story.
   * The context argument is a Storybook object containing the story's id, title, and name.
   */
  async preVisit(page, context) {
    // Add your configuration here.
  },
  /* Hook to execute after a story is visited and fully rendered.
   * The page argument is the Playwright's page object for the story
   * The context argument is a Storybook object containing the story's id, title, and name.
   */
  async postVisit(page, context) {
    // Get the entire context of a story, including parameters, args, argTypes, etc.
    const storyContext = await getStoryContext(page, context);
 
    // This utility function is designed for image snapshot testing. It will wait for the page to be fully loaded, including all the async items (e.g., images, fonts, etc.).
    await waitForPageReady(page);
 
    // Add your configuration here.
  },
};
 
export default config;

使用測試工具存取 story 資訊

如果你需要存取關於 story 的資訊,例如其參數,測試工具包含一個名為 getStoryContext 的輔助函數,你可以用它來檢索資訊。然後,你可以根據需要使用它來進一步自訂你的測試。例如,如果你需要設定 Playwright 頁面的viewport 大小以使用 story 參數中定義的 viewport 大小,你可以按照以下方式操作:

.storybook/test-runner.js
import type { TestRunnerConfig } from '@storybook/test-runner';
import { getStoryContext } from '@storybook/test-runner';
 
const { MINIMAL_VIEWPORTS } = require('@storybook/addon-viewport');
 
const DEFAULT_VIEWPORT_SIZE = { width: 1280, height: 720 };
 
const config: TestRunnerConfig = {
  async preVisit(page, story) {
    // Accesses the story's parameters and retrieves the viewport used to render it
    const context = await getStoryContext(page, story);
    const viewportName = context.parameters?.viewport?.defaultViewport;
    const viewportParameter = MINIMAL_VIEWPORTS[viewportName];
 
    if (viewportParameter) {
      const viewportSize = Object.entries(viewportParameter.styles).reduce(
        (acc, [screen, size]) => ({
          ...acc,
          // Converts the viewport size from percentages to numbers
          [screen]: parseInt(size),
        }),
        {},
      );
      // Configures the Playwright page to use the viewport size
      page.setViewportSize(viewportSize);
    } else {
      page.setViewportSize(DEFAULT_VIEWPORT_SIZE);
    }
  },
};
 
export default config;

使用資源

如果你正在執行一組特定的測試(例如,影像快照測試),測試工具提供了一個名為 waitForPageReady 的輔助函數,你可以用它來確保頁面在執行測試之前已完全載入並準備就緒。例如:

.storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';
 
import { waitForPageReady } from '@storybook/test-runner';
 
import { toMatchImageSnapshot } from 'jest-image-snapshot';
 
const customSnapshotsDir = `${process.cwd()}/__snapshots__`;
 
const config: TestRunnerConfig = {
  setup() {
    expect.extend({ toMatchImageSnapshot });
  },
  async postVisit(page, context) {
    // Awaits for the page to be loaded and available including assets (e.g., fonts)
    await waitForPageReady(page);
 
    // Generates a snapshot file based on the story identifier
    const image = await page.screenshot();
    expect(image).toMatchImageSnapshot({
      customSnapshotsDir,
      customSnapshotIdentifier: context.id,
    });
  },
};
 
export default config;

Index.json 模式

在測試本機 Storybook 時,測試工具會將你的 story 檔案轉換為測試。對於遠端 Storybook,它會使用 Storybook 的 index.json(以前的 stories.json)檔案(所有 story 的靜態索引)來執行測試。

為什麼?

假設你遇到本機和遠端 Storybook 似乎不同步的情況,或者你甚至可能無法存取程式碼。在這種情況下,index.json 檔案保證是你正在測試的已部署 Storybook 的最準確表示。若要使用此功能測試本機 Storybook,請使用 --index-json 標誌,如下所示:

npm run test-storybook -- --index-json

index.json 模式與監看模式不相容。

如果你需要停用它,請使用 --no-index-json 標誌

npm run test-storybook -- --no-index-json

如何檢查我的 Storybook 是否有 index.json 檔案?

Index.json 模式需要一個 index.json 檔案。開啟瀏覽器視窗並導覽至您部署的 Storybook 實例(例如,https://your-storybook-url-here.com/index.json)。您應該會看到一個以 "v": 3 鍵開頭的 JSON 檔案,緊接著另一個名為 "stories" 的鍵,其中包含故事 ID 到 JSON 物件的映射。如果是這樣,您的 Storybook 支援 index.json 模式


疑難排解

測試執行器似乎不穩定,且持續逾時

如果您的測試逾時並顯示以下訊息

Timeout - Async callback was not invoked within the 15000 ms timeout specified by jest.setTimeout

可能是 Playwright 無法處理您專案中大量的測試故事。也許您有大量的故事,或者您的 CI 環境的 RAM 配置非常低。在這種情況下,您應該通過調整您的命令,來限制並行執行的工作人員數量,如下所示

{
  "scripts": {
    "test-storybook:ci": "yarn test-storybook --maxWorkers=2"
  }
}

CLI 中的錯誤輸出太短

預設情況下,測試執行器會在 1000 個字元處截斷錯誤輸出,您可以直接在瀏覽器中的 Storybook 中查看完整輸出。但是,如果您想更改該限制,可以通過將 DEBUG_PRINT_LIMIT 環境變數設定為您選擇的數字來實現,例如,DEBUG_PRINT_LIMIT=5000 yarn test-storybook

在其他 CI 環境中運行測試執行器

由於測試執行器基於 Playwright,您可能需要使用特定的 Docker 映像或其他配置,具體取決於您的 CI 設定。在這種情況下,您可以參考 Playwright CI 文件以獲取更多資訊。

按標籤過濾的測試執行不正確

如果您已啟用使用標籤過濾測試,並且為 includeexclude 列表提供了相似的標籤,則測試執行器將根據 exclude 列表執行測試,並忽略 include 列表。為了避免這種情況,請確保提供給 includeexclude 列表的標籤不同。

測試執行器不支援開箱即用的 Yarn PnP

如果您在啟用了 Plug'n'Play (PnP) 的較新版本 Yarn 上運行的專案中啟用了測試執行器,則測試執行器可能無法按預期工作,並且在運行測試時可能會產生以下錯誤

PlaywrightError: jest-playwright-preset: Cannot find playwright package to use chromium

這是由於測試執行器使用了社群維護的套件 jest-playwright-preset,該套件仍需要支援此功能。為了解決此問題,您可以將 nodeLinker 設定切換為 node-modules,或者在您的專案中直接安裝 Playwright 作為直接依賴項,然後通過 install 命令新增瀏覽器二進制檔案。

了解其他 UI 測試