无障碍测试
观看视频教程
无障碍性是一种使网站对所有人具有包容性的实践。这意味着支持以下要求:键盘导航、屏幕阅读器支持、触摸友好、可用的颜色对比度、减少的运动和缩放支持。
无障碍测试根据 WCAG 规则和其他行业公认的最佳实践,审核渲染的 DOM 是否符合一系列启发式规则。它们充当 QA 的第一道防线,以捕获明显的无障碍违规行为。
使用 a11y 插件进行无障碍检查
Storybook 提供了一个官方的 a11y 插件。它由 Deque 的 axe-core 提供支持,可以自动捕获高达 57% 的 WCAG 问题。
设置 a11y 插件
如果您想使用 插件 检查您的 stories 的无障碍性,您需要将其添加到您的 Storybook 中。您可以通过运行以下命令来完成此操作
npx storybook add @storybook/addon-a11y
启动您的 Storybook,您将在 UI 中看到一些明显的差异。一个新的工具栏图标和无障碍面板,您可以在其中检查测试结果。
工作原理
Storybook 的 a11y 插件在选定的 story 上运行 Axe。允许您在开发过程中捕获和修复无障碍问题。例如,如果您正在开发一个按钮组件,并包含以下 stories 集
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',
},
};
循环浏览这两个 stories,您将看到 Inaccessible
story 包含一些需要修复的问题。打开无障碍面板中的违规选项卡,可以清楚地描述无障碍问题和解决指南。
配置
开箱即用,Storybook 的无障碍插件包含一组涵盖大多数问题的无障碍规则。您还可以微调插件配置或覆盖 Axe 的规则集,以最好地满足您的需求。
全局 a11y 配置
如果您需要忽略无障碍规则或修改其在所有 stories 中的设置,您可以将以下内容添加到您的 storybook/preview.js|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: 'body',
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
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter
* to learn more about the available options.
*/
options: {},
},
},
globals: {
a11y: {
// Optional flag to prevent the automatic check
manual: true,
},
},
};
export default preview;
组件级 a11y 配置
您还可以为组件的所有 stories 自定义您自己的一组规则。更新 story 文件的默认导出,并添加带有必要配置的参数和全局变量
// 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: 'body',
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
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter
* to learn more about the available options.
*/
options: {},
},
},
globals: {
a11y: {
manual: true,
},
},
};
export default meta;
Story 级别 a11y 配置
通过更新您的 story 以包含新参数,在 story 级别自定义 a11y 规则集
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: 'body',
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
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter
* to learn more about the available options.
*/
options: {},
},
},
globals: {
a11y: {
// Optional flag to prevent the automatic check
manual: true,
},
},
};
关闭自动 a11y 测试
通过将以下全局变量添加到您的 story 导出或组件的默认导出中,禁用 stories 或组件的自动无障碍测试
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 = {
globals: {
a11y: {
// This option disables all automatic a11y checks on this story
manual: true,
},
},
};
测试插件集成
无障碍插件与 测试插件 提供了无缝集成,使您能够在后台为所有测试运行自动无障碍测试,同时您运行组件测试。如果存在任何违规行为,测试将失败,您将在侧边栏中看到结果,而无需任何额外的设置。
手动升级
如果您启用了插件并且正在手动升级到 Storybook 8.5 或更高版本,则需要调整现有配置(即,.storybook/vitest.setup.ts
)以按如下方式启用集成
import { beforeAll } from 'vitest';
import { setProjectAnnotations } from '@storybook/react';
// Import the a11y addon annotations
import * as a11yAddonAnnotations from '@storybook/addon-a11y/preview';
// Optionally import your own annotations
import * as projectAnnotations from './preview';
const project = setProjectAnnotations([
// Add the a11y addon annotations
a11yAddonAnnotations,
projectAnnotations,
]);
beforeAll(project.beforeAll);
使用测试插件配置无障碍测试
您可以使用 parameters.a11y.test
参数 配置无障碍测试,该参数确定 story 的无障碍测试行为,并接受以下值
值 | 描述 |
---|---|
'off' | 不运行无障碍测试(您仍然可以通过插件面板手动验证) |
'todo' | 运行无障碍测试;违规行为在 Storybook UI 中返回警告,并在 CLI/CI 中返回摘要计数 |
'error' | 运行无障碍测试;违规行为在 Storybook UI 和 CLI/CI 中返回失败的测试 |
与其他参数一样,您可以在项目级别在 .storybook/preview.js|ts
中定义它,也可以在 story 文件的默认导出或单个 story 级别定义它。例如,要使文件中除一个 story 之外的所有 story 的无障碍测试都失败
// Replace your-renderer with the renderer you are using (e.g., react, vue3)
import { Meta, StoryObj } from '@storybook/your-renderer';
import { Button } from './Button';
const meta: Meta<typeof Button> = {
component: Button,
parameters: {
a11y: { test: 'error' },
},
};
export default meta;
type Story = StoryObj<typeof Button>;
// 👇 This story will use the 'error' value and fail on accessibility violations
export const Primary: Story = {
args: { primary: true },
};
// 👇 This story will not fail on accessibility violations
// (but will still run the tests and show warnings)
export const NoA11yFail: Story = {
parameters: {
a11y: { test: 'todo' },
},
};
为什么值称为“todo”而不是“warn”?此值旨在用作代码库中的文字 TODO
。它可以用于标记您知道存在无障碍问题但尚未准备好修复的 stories。这样,您可以跟踪它们并在以后解决它们。
'off'
值应仅用于不需要进行无障碍测试的 stories,例如用于演示组件用法中的反模式的 story。
您还可以 禁用个别规则,当这些规则不适用于您的用例时。
推荐工作流程
您可以使用配置通过组合多种测试行为来逐步实现更易于访问的 UI。例如,您可以从 'error'
开始,以便在无障碍违规时失败,然后切换到 'todo'
以标记需要修复的组件,最后在所有 stories 通过无障碍测试后删除 todos
-
更新您的项目配置,通过将
parameters.a11y.test
设置为'error'
,使其在无障碍违规时失败。这确保所有新 stories 都经过测试以符合无障碍标准。.storybook/preview.ts// Replace your-renderer with the renderer you are using (e.g., react, vue3) import { Preview } from '@storybook/your-renderer'; const preview: Preview = { // ... parameters: { // 👇 Fail all accessibility tests when violations are found a11y: { test: 'error' }, }, }; export default preview;
-
您可能会发现许多组件都存在无障碍故障(并且可能感到有些不知所措!)。
-
记下存在无障碍问题的组件,并通过应用
'todo'
参数值暂时将它们的故障降级为警告。这使无障碍问题保持可见,同时不妨碍开发。这也是提交您的工作作为未来改进基准的好时机。DataTable.stories.ts// Replace your-renderer with the renderer you are using (e.g., react, vue3) import { Meta } from '@storybook/your-renderer'; import { DataTable } from './DataTable'; const meta: Meta<typeof DataTable> = { component: DataTable, parameters: { // 👇 This component's accessibility tests will not fail // Instead, they display warnings in the Storybook UI a11y: { test: 'todo' }, }, }; export default meta;
-
从您刚刚标记为
'todo'
的组件中选择一个好的起点(我们建议选择类似 Button 的组件,因为它简单且可能在其他组件中使用)。使用插件面板中的建议修复该组件中的问题,以确保其通过无障碍测试,然后删除参数。Button.stories.ts// Replace your-renderer with the renderer you are using (e.g., react, vue3) import { Meta } from '@storybook/your-renderer'; import { Button } from './Button'; const meta: Meta<typeof Button> = { component: Button, parameters: { // 👇 Remove this once all stories pass accessibility tests // a11y: { test: 'todo' }, }, }; export default meta;
-
选择另一个组件并重复此过程,直到您涵盖了所有组件,并且您成为无障碍英雄!
使用测试运行器自动化无障碍测试
检查无障碍性的最准确方法是在真实设备上手动检查。但是,您可以使用自动化工具来捕获常见的无障碍问题。例如,Axe 平均可以自动捕获高达 57% 的 WCAG 问题。
这些工具的工作原理是根据 WCAG 规则和其他行业公认的最佳实践,审核渲染的 DOM 是否符合启发式规则。然后,您可以使用 Storybook 测试运行器 和 axe-playwright 将这些工具集成到您的测试自动化管道中。
设置
要使用测试运行器启用无障碍测试,您需要采取额外的步骤来正确设置它。我们建议您在继续进行其余的必要配置之前,先阅读 测试运行器文档。
运行以下命令以安装所需的依赖项。
npm install axe-playwright --save-dev
在您的 Storybook 目录中添加一个新的 配置文件,并在其中添加以下内容
import type { TestRunnerConfig } from '@storybook/test-runner';
import { injectAxe, checkA11y } from 'axe-playwright';
/*
* See https://storybook.org.cn/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, 'body', {
detailedReport: true,
detailedReportOptions: {
html: true,
},
});
},
};
export default config;
preVisit
和 postVisit
是方便的钩子,可让您扩展测试运行器的默认配置。在此处阅读有关它们的更多信息 here。
当您执行测试运行器时(例如,使用 yarn test-storybook
),它将运行无障碍审核以及您可能为每个组件 story 配置的任何 组件测试。
它从 story 的根元素开始遍历 DOM 树来开始检查问题,并根据遇到的问题生成详细的报告。
测试运行器的 A11y 配置
测试运行器提供 助手方法,允许访问 story 的信息。您可以使用它们来扩展测试运行器的配置,并为您可能为特定 story 提供的其他选项提供支持。例如
import type { TestRunnerConfig } from '@storybook/test-runner';
import { getStoryContext } from '@storybook/test-runner';
import { injectAxe, checkA11y, configureAxe } from 'axe-playwright';
/*
* See https://storybook.org.cn/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 ?? 'body';
await checkA11y(page, element, {
detailedReport: true,
detailedReportOptions: {
html: true,
},
});
},
};
export default config;
使用测试运行器禁用 a11y 测试
此外,如果您已经为任何特定的 story 禁用了无障碍 测试,您还可以配置测试运行器以避免也对其进行测试。例如
import type { TestRunnerConfig } from '@storybook/test-runner';
import { getStoryContext } from '@storybook/test-runner';
import { injectAxe, checkA11y } from 'axe-playwright';
/*
* See https://storybook.org.cn/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, 'body', {
detailedReport: true,
detailedReportOptions: {
html: true,
},
});
},
};
export default config;
基于浏览器的无障碍测试和基于 linter 的无障碍测试之间有什么区别?
基于浏览器的无障碍测试(如 Storybook 中发现的测试)评估渲染的 DOM,因为这为您提供了最高的准确性。审核尚未编译的代码与真实事物相差一步,因此您不会捕获用户可能体验到的一切。
故障排除
为什么我的测试在不同环境中失败?
如果您启用了实验性测试插件(即,@storybook/experimental-addon-test
),您的测试将在 Vitest 中使用您的项目配置和 Playwright 的 Chromium 浏览器运行。这可能导致 Storybook UI 或 CLI 中报告的测试结果不一致。不一致可能是由于 axe-core
在不同环境(如浏览器版本或配置)中报告不同的结果。如果您遇到此问题,我们建议使用默认的沟通渠道联系我们(例如,GitHub 讨论,Github issues)。
插件面板未显示预期的违规行为
现代 React 组件通常使用异步技术,如 Suspense 或 React 服务端组件 (RSC) 来处理复杂的数据获取和渲染。这些组件不会立即渲染其最终的 UI 状态。Storybook 本身并不知道异步组件何时完全渲染。因此,a11y 检查有时会过早运行,在组件完成渲染之前运行,从而导致误报(即使存在违规行为,也没有报告违规行为)。
为了解决这个问题,我们引入了一个功能标志:developmentModeForBuild
。此功能标志允许您在构建的 Storybook 中将 process.env.NODE_ENV
设置为 'development'
,从而启用通常在生产构建中禁用的与开发相关的优化。这些开发优化之一是 React 的 act
实用程序,它有助于确保在进行断言(如 a11y 检查)之前,与测试相关的所有更新都已处理和应用。
要启用此功能标志,请将以下配置添加到您的 .storybook/main.js|ts
文件中
// Replace your-framework with the framework you are using (e.g., react-webpack5, vue3-vite)
import type { StorybookConfig } from '@storybook/your-framework';
const config: StorybookConfig = {
framework: '@storybook/your-framework',
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|mjs|ts|tsx)'],
features: {
developmentModeForBuild: true,
},
};
export default config;
了解有关其他 UI 测试的信息