文件
Storybook 文件

測試執行器

Storybook 測試執行器將你所有的 stories 轉換為可執行的測試。它由 JestPlaywright 驅動。

  • 對於那些沒有 play 函數的 stories:它驗證 story 是否在沒有任何錯誤的情況下渲染。
  • 對於那些有 play 函數的 stories:它也檢查 play 函數中是否有錯誤,以及所有斷言是否通過。

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

設定

測試執行器是一個獨立、框架無關的工具,與你的 Storybook 並行運行。你需要採取一些額外的步驟來正確設定它。以下詳細介紹了我們配置和執行它的建議。

執行以下命令來安裝它。

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

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

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在 index json 模式下運行。自動偵測(需要相容的 Storybook)
test-storybook --index-json
--no-index-json停用 index 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在你的 stories 和元件上運行覆蓋率測試
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 [amount]指定 worker-pool 將為運行測試產生的最大 worker 數量
test-storybook --maxWorkers=2
--testTimeout [amount]定義測試可以運行的最大時間(毫秒),超過此時間將自動標記為失敗。對於長時間運行的測試很有用
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實驗性功能
定義一組 stories 子集,只有當它們符合啟用的標籤時才進行測試。
test-storybook --includeTags="test-only, pages"
--excludeTags實驗性功能
阻止符合提供的標籤的 stories 被測試。
test-storybook --excludeTags="no-tests, tokens"
--skipTags實驗性功能
設定測試執行器跳過對符合提供的標籤的 stories 運行測試。
test-storybook --skipTags="skip-test, layout"
npm run test-storybook -- --watch

對已部署的 Storybook 運行測試

預設情況下,測試執行器假定你正在針對本地伺服在 6006 端口上的 Storybook 運行它。如果你想定義一個目標 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 並針對你建置的 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 進行視覺化和元件測試,並使用測試執行器運行其他自訂測試。

進階設定

測試 Hook API

測試執行器渲染一個 story 並執行其 play 函數(如果存在)。但是,某些行為無法通過在瀏覽器中執行的 play 函數實現。例如,如果你希望測試執行器為你拍攝視覺快照,這可以通過 Playwright/Jest 實現,但必須在 Node 中執行。

測試執行器匯出可以全域覆蓋的測試 hooks,以啟用諸如視覺或 DOM 快照之類的使用案例。這些 hooks 使你可以在 story 渲染之前和之後訪問測試生命週期。以下列出可用的 hooks 以及如何使用它們的概述。

Hook描述
prepare準備瀏覽器以進行測試
async prepare({ page, browserContext, testRunnerConfig }) {}
setup在所有測試運行之前執行一次
setup() {}
preVisit在 story 最初被訪問並在瀏覽器中渲染之前執行
async preVisit(page, context) {}
postVisit在 story 被訪問並完全渲染之後執行
async postVisit(page, context) {}

這些測試 hooks 是實驗性的,可能會發生重大變更。我們鼓勵你在 story 的 play 函數中盡可能多地進行測試。

要啟用 hooks 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 page 和一個 context 物件,其中包含 story 的 idtitlename

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

  • setup 函數在所有測試運行之前執行。
  • 生成包含所需資訊的 context 物件。
  • Playwright 導航到 story 的頁面。
  • preVisit 函數被執行。
  • story 被渲染,並且任何現有的 play 函數都被執行。
  • postVisit 函數被執行。

(實驗性) 過濾測試

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

選項描述
exclude阻止符合提供的標籤的 stories 被測試。
include定義一組 stories 子集,只有當它們符合啟用的標籤時才進行測試。
skip跳過對符合提供的標籤的 stories 進行測試。
.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 標誌運行測試優先於設定檔中提供的選項,並將覆蓋設定檔中可用的選項。

停用測試

如果你想阻止特定 stories 被測試執行器測試,你可以使用自訂標籤配置你的 story,在測試執行器設定檔中啟用它,或使用 --excludeTags CLI 標誌運行測試執行器並將它們從測試中排除。當你想排除尚未準備好進行測試或與你的測試無關的 stories 時,這很有幫助。例如

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'],
};

為 stories 的子集運行測試

為了允許測試執行器僅對特定 story 或 stories 子集運行測試,你可以使用自訂標籤配置 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'],
};

將標籤應用於元件的 stories 應在元件層級(使用 meta)或 story 層級完成。Storybook 不支援跨 stories 導入標籤,並且不會按預期工作。

跳過測試

如果你想跳過對特定 story 或 stories 子集運行測試,你可以使用自訂標籤配置你的 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 檢查實例狀態及其故事索引的方式。若要執行此操作,您可以修改測試執行器的組態檔,以包含 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;

使用測試執行器存取故事資訊

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

.storybook/test-runner.ts
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 時,測試執行器會將您的故事檔案轉換為測試。對於遠端 Storybook,它會使用 Storybook 的 index.json (以前稱為 stories.json) 檔案 (所有故事的靜態索引) 來執行測試。

為什麼?

假設您遇到本機和遠端 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)。您應該會看到一個 JSON 檔案,該檔案以 "v": 3 索引鍵開頭,緊接著另一個名為 "stories" 的索引鍵,其中包含故事 ID 到 JSON 物件的對應。如果是這種情況,您的 Storybook 支援 index.json 模式


疑難排解

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

如果您的測試因下列訊息而逾時

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

可能是 Playwright 無法處理測試您專案中故事的數量。也許您的故事數量龐大,或者您的 CI 環境的 RAM 組態非常低。在這種情況下,您應該限制並行執行的 worker 數量,方法是依照下列方式調整您的命令

package.json
{
  "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 測試