文件
Storybook 文件

無障礙測試

觀看影片教學

無障礙性是指讓網站對所有人都能存取的實踐。這意味著支援以下要求:鍵盤導覽、螢幕閱讀器支援、觸控友善、可用的色彩對比、減少動態效果和縮放支援。

無障礙測試會根據 WCAG 規則和其他業界認可的最佳實務,稽核渲染的 DOM 是否符合一組啟發式規則。它們是第一道 QA 防線,用於發現明顯的無障礙違規行為。

使用 a11y 附加元件進行無障礙檢查

Storybook 提供官方的 a11y 附加元件。由 Deque 的 axe-core 提供技術支援,可自動偵測高達 57% 的 WCAG 問題

設定 a11y 附加元件

如果您想使用 附加元件檢查 story 的無障礙性,則需要將其新增至 Storybook。您可以執行下列命令來完成此操作

npx storybook add @storybook/addon-a11y

CLI 的 add 命令會自動執行附加元件的安裝與設定。若要手動安裝,請參閱我們的 文件,了解如何安裝附加元件。

啟動 Storybook,您會在 UI 中看到一些明顯的差異。新的工具列圖示和無障礙面板,您可以在其中檢查測試結果。

Storybook accessibility addon running

運作方式

Storybook 的 a11y 附加元件會在選取的 story 上執行 Axe。讓您可以在開發期間偵測並修正無障礙性問題。例如,如果您正在開發按鈕元件,並且包含以下一組 story

Button.stories.ts|tsx
import type { Meta, StoryObj } from '@storybook/react';
 
import { Button } from './Button';
 
const meta: Meta<typeof Button> = {
  component: Button,
  argTypes: {
    backgroundColor: { control: 'color' },
  },
};
 
export default meta;
type Story = StoryObj<typeof Button>;
 
// This is an accessible story
export const Accessible: Story = {
  args: {
    primary: false,
    label: 'Button',
  },
};
 
// This is not
export const Inaccessible: Story = {
  args: {
    ...Accessible.args,
    backgroundColor: 'red',
  },
};

在兩個 story 中循環瀏覽時,您會看到 Inaccessible story 包含一些需要修正的問題。開啟無障礙面板中的違規標籤,可以清楚描述無障礙性問題和解決問題的指南。

Storybook accessibility addon running

設定

Storybook 的無障礙附加元件包含一組涵蓋大多數問題的無障礙規則。您也可以微調 附加元件設定或覆寫 Axe 的規則集,以最符合您的需求。

全域 a11y 設定

如果您需要忽略無障礙規則或修改其在所有 story 中的設定,您可以將以下內容新增至您的 storybook/preview.js|ts

.storybook/preview.ts
// Replace your-framework with the framework you are using (e.g., react, vue3)
import { Preview } from '@storybook/your-framework';
 
const preview: Preview = {
  parameters: {
    a11y: {
      // Optional selector to inspect
      element: '#storybook-root',
      config: {
        rules: [
          {
            // The autocomplete rule will not run based on the CSS selector provided
            id: 'autocomplete-valid',
            selector: '*:not([autocomplete="nope"])',
          },
          {
            // Setting the enabled option to false will disable checks for this particular rule on all stories.
            id: 'image-alt',
            enabled: false,
          },
        ],
      },
      // Axe's options parameter
      options: {},
      // Optional flag to prevent the automatic check
      manual: true,
    },
  },
};
 
export default preview;

元件層級的無障礙設定

您還可以為元件的所有 stories 自訂一組規則。更新您 story 的預設匯出,並加入一個帶有所需設定的參數。

MyComponent.stories.ts|tsx
// Replace your-framework with the name of your framework
import type { Meta } from '@storybook/your-framework';
 
import { MyComponent } from './MyComponent';
 
const meta: Meta<typeof MyComponent> = {
  component: MyComponent,
  parameters: {
    a11y: {
      // Optional selector to inspect
      element: '#storybook-root',
      config: {
        rules: [
          {
            // The autocomplete rule will not run based on the CSS selector provided
            id: 'autocomplete-valid',
            selector: '*:not([autocomplete="nope"])',
          },
          {
            // Setting the enabled option to false will disable checks for this particular rule on all stories.
            id: 'image-alt',
            enabled: false,
          },
        ],
      },
      options: {},
      manual: true,
    },
  },
};
 
export default meta;

Story 層級的無障礙設定

透過更新您的 story 以包含新的參數,來自訂 story 層級的 a11y 規則集。

MyComponent.stories.ts|tsx
import type { Meta, StoryObj } from '@storybook/react';
 
import { MyComponent } from './MyComponent';
 
const meta: Meta<typeof MyComponent> = {
  component: MyComponent,
};
 
export default meta;
type Story = StoryObj<typeof MyComponent>;
 
export const ExampleStory: Story = {
  parameters: {
    a11y: {
      element: '#storybook-root',
      config: {
        rules: [
          {
            // The autocomplete rule will not run based on the CSS selector provided
            id: 'autocomplete-valid',
            selector: '*:not([autocomplete="nope"])',
          },
          {
            // Setting the enabled option to false will disable checks for this particular rule on all stories.
            id: 'image-alt',
            enabled: false,
          },
        ],
      },
      options: {},
      manual: true,
    },
  },
};

如何停用 a11y 測試

透過將以下參數分別加入至您 story 的匯出或元件的預設匯出,來停用 stories 或元件的無障礙測試。

MyComponent.stories.ts|tsx
import type { Meta, StoryObj } from '@storybook/react';
 
import { MyComponent } from './MyComponent';
 
const meta: Meta<typeof MyComponent> = {
  component: MyComponent,
};
 
export default meta;
type Story = StoryObj<typeof MyComponent>;
 
export const NonA11yStory: Story = {
  parameters: {
    a11y: {
      // This option disables all a11y checks on this story
      disable: true,
    },
  },
};

使用測試執行器自動執行無障礙測試

檢查無障礙功能最準確的方式是在真實裝置上手動檢查。不過,您可以使用自動化工具來找出常見的無障礙問題。例如,Axe 平均可以自動找出高達 57% 的 WCAG 問題

這些工具會根據 WCAG 規則和其他業界接受的最佳實務,審核已渲染的 DOM 並使用啟發式方法。然後,您可以使用 Storybook 測試執行器axe-playwright 將這些工具整合到您的測試自動化流程中。

設定

若要使用測試執行器啟用無障礙測試,您需要採取額外的步驟進行正確的設定。我們建議您在繼續進行其餘的必要設定之前,先瀏覽 測試執行器文件

執行以下命令以安裝所需的相依性。

npm install axe-playwright --save-dev

在您的 Storybook 目錄內新增一個 設定檔,並在其中加入以下內容

.storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';
import { injectAxe, checkA11y } from 'axe-playwright';
 
/*
 * See https://storybook.dev.org.tw/docs/writing-tests/test-runner#test-hook-api
 * to learn more about the test-runner hooks API.
 */
const config: TestRunnerConfig = {
  async preVisit(page) {
    await injectAxe(page);
  },
  async postVisit(page) {
    await checkA11y(page, '#storybook-root', {
      detailedReport: true,
      detailedReportOptions: {
        html: true,
      },
    });
  },
};
 
export default config;

preVisitpostVisit 是方便的 hook,可讓您擴充測試執行器的預設設定。請在此處閱讀更多相關資訊 這裡

當您執行測試執行器時 (例如,使用 yarn test-storybook),它會執行無障礙審核以及您可能已為每個元件 story 設定的任何元件測試

它會從 story 的根元素開始遍歷 DOM 樹狀結構,並根據它遇到的問題產生詳細的報告,藉此開始檢查問題。

Accessibility testing with the test runner

使用測試執行器的 A11y 設定

測試執行器提供輔助方法,允許存取 story 的資訊。您可以使用它們來擴充測試執行器的設定,並提供您可能對特定 story 擁有的其他選項。例如

.storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';
import { getStoryContext } from '@storybook/test-runner';
 
import { injectAxe, checkA11y, configureAxe } from 'axe-playwright';
 
/*
 * See https://storybook.dev.org.tw/docs/writing-tests/test-runner#test-hook-api
 * to learn more about the test-runner hooks API.
 */
const config: TestRunnerConfig = {
  async preVisit(page) {
    await injectAxe(page);
  },
  async postVisit(page, context) {
    // Get the entire context of a story, including parameters, args, argTypes, etc.
    const storyContext = await getStoryContext(page, context);
 
    // Apply story-level a11y rules
    await configureAxe(page, {
      rules: storyContext.parameters?.a11y?.config?.rules,
    });
 
    const element = storyContext.parameters?.a11y?.element ?? '#storybook-root';
    await checkA11y(page, element, {
      detailedReport: true,
      detailedReportOptions: {
        html: true,
      },
    });
  },
};
 
export default config;

使用測試執行器停用 a11y 測試

此外,如果您已為任何特定的 story 停用無障礙測試,您也可以設定測試執行器以避免測試該 story。例如

.storybook/test-runner.ts
import type { TestRunnerConfig } from '@storybook/test-runner';
import { getStoryContext } from '@storybook/test-runner';
 
import { injectAxe, checkA11y } from 'axe-playwright';
 
/*
 * See https://storybook.dev.org.tw/docs/writing-tests/test-runner#test-hook-api
 * to learn more about the test-runner hooks API.
 */
const config: TestRunnerConfig = {
  async preVisit(page) {
    await injectAxe(page);
  },
  async postVisit(page, context) {
    // Get the entire context of a story, including parameters, args, argTypes, etc.
    const storyContext = await getStoryContext(page, context);
 
    // Do not run a11y tests on disabled stories.
    if (storyContext.parameters?.a11y?.disable) {
      return;
    }
    await checkA11y(page, '#storybook-root', {
      detailedReport: true,
      detailedReportOptions: {
        html: true,
      },
    });
  },
};
 
export default config;

基於瀏覽器的無障礙測試和基於 linter 的無障礙測試有什麼區別?

基於瀏覽器的無障礙測試 (例如 Storybook 中的測試) 會評估已渲染的 DOM,因為這可提供最高的準確性。審核尚未編譯的程式碼與真實情況相差一步,因此您無法捕捉到使用者可能會體驗到的所有情況。

了解其他 UI 測試