无障碍测试
Web 无障碍是确保所有人员(无论能力或所使用的技术如何)都能访问和使用网站和应用程序的实践。这意味着支持键盘导航、屏幕阅读器支持、足够的颜色对比度等要求。
无障碍不仅是正确的事情,而且正日益成为强制要求。例如,《欧洲无障碍法案》定于 2025 年 6 月生效。同样在美国,像《美国残疾人法案 (ADA)》和《康复法案第 508 条》这样的法律适用于许多面向公众的服务。这些法律中有许多是基于 WCAG(Web 内容无障碍指南)制定的,WCAG 是用于制作无障碍 Web 内容的标准化指南。
无障碍测试根据基于 WCAG 规则和其他行业公认的最佳实践的一组启发式规则,审计渲染后的 DOM。它们是捕获明显的无障碍违规行为的第一道质量保证防线。
安装插件
Storybook 提供了一个无障碍 (a11y) 插件,以帮助确保您的组件具有无障碍性。它构建在 Deque 的 axe-core 库之上,该库可以自动捕获高达 57% 的 WCAG 问题。
运行此命令可在您的项目中安装和配置插件
npx storybook add @storybook/addon-a11y
您的 Storybook 现在将包含一些检查组件无障碍性的功能,包括工具栏中用于模拟不同视力障碍的按钮以及用于检查违规行为的无障碍插件面板。
检查违规行为
当您导航到一个故事时,会自动运行无障碍检查,结果会在无障碍插件面板中报告。
结果分为三个子标签页
- 违规是已知违反 WCAG 规则和最佳实践的行为
- 通过是已知未违反的行为
- 未完成突出显示了应手动确认的区域,因为它们无法自动检查
配置
由于该插件构建在 axe-core
之上,因此大部分可用配置都映射到其可用选项。
属性 | 默认值 | 描述 |
---|---|---|
parameters.a11y.context | 'body' | 传递给 axe.run 的上下文。定义对哪些元素运行检查。 |
parameters.a11y.config | (见下文) | 传递给 axe.configure() 的配置。最常用于配置单个规则。 |
parameters.a11y.options | {} | 选项传递给 axe.run 。可用于调整检查的规则集。 |
parameters.a11y.test | undefined | 确定与 Vitest 插件一起运行时测试的行为。更多详情见下文。 |
globals.a11y.manual | undefined | 设置为 true 可防止故事在访问时被自动分析。更多详情见下文。 |
默认的 parameters.a11y.config
默认情况下,Storybook 禁用 区域规则,该规则通常不适用于故事中的组件,并可能导致误报。
{
rules: [
{
id: 'region',
enabled: false,
}
]
}
我们将分享示例,展示如何使用其中一些配置属性。
在这里,它们应用于项目中的所有故事,位于 .storybook/preview.ts
中。
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import { Preview } from '@storybook/your-framework';
const preview: Preview = {
parameters: {
a11y: {
/*
* Axe's context parameter
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter
* to learn more. Typically, this is the CSS selector for the part of the DOM you want to analyze.
*/
context: 'body',
/*
* Axe's configuration
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure
* to learn more about the available properties.
*/
config: {},
/*
* 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;
您还可以将配置应用于文件中的所有故事(在 meta
中)或单个故事。
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
import { Button } from './Button';
const meta = {
component: Button,
parameters: {
a11y: {
/*
* Axe's context parameter
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter
* to learn more.
*/
context: {},
/*
* Axe's configuration
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#api-name-axeconfigure
* to learn more about the available properties.
*/
config: {},
/*
* 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: {},
/*
* Configure test behavior
* See: https://storybook.org.cn/docs/next/writing-tests/accessibility-testing#test-behavior
*/
test: 'error',
},
},
globals: {
a11y: {
// Optional flag to prevent the automatic check
manual: true,
},
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
export const ExampleStory: Story = {
parameters: {
a11y: {
// ...same config available as above
},
},
globals: {
a11y: {
// ...same config available as above
},
},
};
规则集
该插件使用 axe-core
库运行无障碍检查。默认情况下,它运行基于 WCAG 2.0 和 2.1 指南以及一些最佳实践的规则集。
您可以在 axe-core 的文档中找到这些规则集的详细说明以及其他可用规则集。
要更改检查的规则(例如,检查 WCAG 2.2 AA 或 WCAG 2.x AAA 规则),请使用 runOnly
选项。
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import { Preview } from '@storybook/your-framework';
const preview: Preview = {
parameters: {
a11y: {
options: {
/*
* Opt in to running WCAG 2.x AAA rules
* Note that you must explicitly re-specify the defaults (all but the last array entry)
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#options-parameter-examples for more details
*/
runOnly: ['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'best-practice', 'wcag2aaa'],
},
},
},
};
export default preview;
单个规则
您还可以启用、禁用或配置单个规则。这可以在 parameters.a11y
对象的 config
属性中完成。例如:
// ...rest of story file
export const IndividualA11yRulesExample: Story = {
parameters: {
a11y: {
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,
},
],
},
},
},
};
测试行为
您可以使用 parameters.a11y.test
参数配置无障碍测试,该参数决定了与 Vitest 插件或 test-runner 一起运行时,故事的无障碍测试行为。该参数接受三个值:
值 | 描述 |
---|---|
'off' | 不运行无障碍测试(您仍然可以通过插件面板手动验证) |
'todo' | 运行无障碍测试;违规行为在 Storybook UI 中返回警告 |
'error' | 运行无障碍测试;违规行为在 Storybook UI 和 CLI/CI 中返回失败的测试 |
与其它参数类似,您可以在项目级别(在 .storybook/preview.js|ts
中)、故事文件的默认导出中(组件级别)或单个故事级别定义它。例如,除了一个故事之外,让文件中所有故事的无障碍测试都失败:
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
import { Button } from './Button';
const meta = {
component: Button,
parameters: {
// 👇 Applies to all stories in this file
a11y: { test: 'error' },
},
} satisfies Meta<typeof Button>;
export default meta;
type Story = StoryObj<typeof meta>;
// 👇 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
。它可用于标记您已知存在无障碍问题但尚未准备好修复的故事。通过这种方式,您可以跟踪它们并稍后解决。
值 'off'
仅应用于不需要进行无障碍测试的故事,例如用于演示组件用法中的反模式的故事。
您还可以禁用单个规则,当它们不适用于您的用例时。
排除的元素
有时,可能需要从无障碍检查中排除某些元素。为此,您可以定义一个自定义上下文,以选择在运行检查时包含(或排除)哪些元素。例如,此故事将忽略带有 no-a11y-check
类名的元素。
// ...rest of story file
export const ExampleStory: Story = {
parameters: {
a11y: {
/*
* Axe's context parameter
* See https://github.com/dequelabs/axe-core/blob/develop/doc/API.md#context-parameter
* to learn more.
*/
context: {
include: ['body'],
exclude: ['.no-a11y-check'],
},
},
},
};
禁用自动检查
禁用自动无障碍检查后,当您导航到故事或使用 Vitest 插件运行测试时,该插件将不会运行任何测试。您仍然可以在无障碍插件面板中手动触发检查。这对于那些并非旨在具有无障碍性的故事很有用,例如演示反模式或特定用例的故事。
通过将以下全局变量添加到故事的导出或组件的默认导出中,禁用故事或组件的自动无障碍检查:
// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, nextjs-vite, etc.
import type { Meta, StoryObj } from '@storybook/your-framework';
import { MyComponent } from './MyComponent';
const meta = {
component: MyComponent,
} satisfies Meta<typeof MyComponent>;
export default meta;
type Story = StoryObj<typeof meta>;
export const NonA11yStory: Story = {
globals: {
a11y: {
// This option disables all automatic a11y checks on this story
manual: true,
},
},
};
运行无障碍测试
使用 Vitest 插件
如果您使用 Vitest 插件,您可以通过以下方式运行无障碍测试,作为组件测试的一部分:
要在 Storybook UI 中运行无障碍测试,首先展开侧边栏中的测试小部件并勾选“无障碍”复选框。现在,当您按下“运行组件测试”按钮时,无障碍测试将与您配置的其他测试一起运行。
运行测试后,您将在侧边栏中看到结果,每个已测试故事旁边都会添加一个测试状态指示器。您可以点击这些指示器打开一个菜单,其中包含无障碍测试结果。点击该结果将导航到该故事并打开无障碍面板,您可以在其中查看每个违规行为的详细信息以及如何修复它们的建议。
如果您的任何测试有警告或失败,测试小部件将显示警告和失败的数量。您可以点击这些数字以在侧边栏中过滤故事,仅显示有警告或失败的故事。
在 CI 中,当您运行 Vitest 测试时,对于设置了 parameters.a11y.test = 'error'
的故事,无障碍测试会自动运行。
使用 test-runner
如果您使用 test-runner,您可以在终端或 CI 环境中运行无障碍测试。
当您安装了无障碍插件并且 parameters.a11y.test
设置为除 'off'
之外的值时,无障碍测试会包含在您的测试运行中。
调试无障碍违规行为
运行无障碍测试后,结果会在 Storybook UI 中报告。您可以点击违规项查看更多详细信息,包括违反的规则以及如何修复它的建议。
您还可以在 Storybook UI 中打开高亮功能,查看是哪些元素导致了违规,并点击高亮元素在弹出菜单中查看违规详情。
推荐的工作流程
您可以使用配置通过组合多种测试行为逐步实现更具无障碍性的 UI。例如,您可以从 'error'
开始,使无障碍违规行为导致失败,然后切换到 'todo'
标记需要修复的组件,最后在所有故事通过无障碍测试后移除 todo 标记。
-
通过将
parameters.a11y.test
设置为'error'
,更新项目配置以使无障碍违规行为导致失败。这确保了所有新故事都经过测试以符合无障碍标准。.storybook/preview.ts// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. import { Preview } from '@storybook/your-framework'; const preview: Preview = { // ... parameters: { // 👇 Fail all accessibility tests when violations are found a11y: { test: 'error' }, }, }; export default preview;
-
您很可能会发现许多组件存在无障碍故障(并且可能感到有些不知所措!)。
-
记下存在无障碍问题的组件,并通过应用
'todo'
参数值暂时将其故障降级为警告。这使得无障碍问题可见,同时不阻碍开发。这也是提交您的工作作为未来改进基线的好时机。DataTable.stories.ts// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. import { Meta } from '@storybook/your-framework'; import { DataTable } from './DataTable'; const meta = { component: DataTable, parameters: { // 👇 This component's accessibility tests will not fail // Instead, they display warnings in the Storybook UI a11y: { test: 'todo' }, }, } satisfies Meta<typeof DataTable>; export default meta;
-
从您刚刚标记为
'todo'
的组件中选择一个好的起点(我们建议像 Button 这样的组件,因为它简单且很可能在其他组件中使用)。根据插件面板中的建议修复该组件中的问题,确保它通过无障碍测试,然后移除参数。Button.stories.ts// Replace your-framework with the framework you are using, e.g. react-vite, nextjs, vue3-vite, etc. import { Meta } from '@storybook/your-framework'; import { Button } from './Button'; const meta = { component: Button, parameters: { // 👇 Remove this once all stories pass accessibility tests // a11y: { test: 'todo' }, }, } satisfies Meta<typeof Button>; export default meta;
-
选择另一个组件并重复该过程,直到您覆盖所有组件,成为无障碍英雄!
常见问题
基于浏览器和基于 linter 的无障碍测试有什么区别?
基于浏览器的无障碍测试(如 Storybook 中的测试)会评估渲染后的 DOM,因为这能提供最高的准确性。审计尚未编译的代码与实际情况相差一步,因此您无法捕捉到用户可能体验到的所有问题。
为什么我的测试在不同环境中会失败?
使用 Vitest 插件时,您的测试会在 Vitest 中使用您的项目配置以及 Playwright 的 Chromium 浏览器运行。这可能导致 Storybook UI 或 CLI 中报告的测试结果不一致。这种不一致可能是由于 axe-core
在不同环境(例如浏览器版本或配置)中报告的结果不同。如果您遇到此问题,建议通过默认的沟通渠道(例如,GitHub discussions、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-vite, nextjs, vue3-vite, etc.
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;
更多测试资源