可访问性测试

可访问性是使网站包容所有人的做法。

这意味着支持例如:键盘导航、屏幕阅读器支持、触摸友好、可用的颜色对比度、减少运动和缩放支持。

可访问性测试根据一组基于 WCAG 规则和其他行业公认的最佳实践的启发式方法来审核呈现的 DOM。
它们充当 QA 的第一道防线,以发现明显的可访问性违规行为。

Storybook 的官方 a11y 插件 会在开发组件时运行可访问性审核,以便提供快速反馈循环。
它由 Deque 的 axe-core 提供支持,可自动捕获多达 57% 的 WCAG 问题


设置 a11y 插件

要使用 Storybook 可访问性测试,需要安装 @storybook/addon-a11y 插件。 运行以下命令:

1
2

yarn add --dev @storybook/addon-a11y

更新 Storybook 配置(在 .storybook/main.js 中)以包含可访问性插件:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11

// .storybook/main.js

module.exports = {
  stories:[],
  addons:[
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/preset-create-react-app',
    '@storybook/addon-a11y', //👈 The a11y addon goes here
};

启动 Storybook,在 UI 中看到一些明显的差异。 一个新的工具栏图标和可访问性面板,可以在其中检查测试结果。


它如何工作?

Storybook 的 a11y 插件在选定的故事上运行 Ax。 允许开发过程中发现和修复可访问性问题。

例如,如果正在处理按钮组件并包含以下故事集:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

// Button.stories.ts|tsx

import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { Button } from './Button';

export default {
  title: 'Accessibility testing',
  component: Button,
  argTypes: {
    backgroundColor: { control: 'color' },
  },
} as ComponentMeta<typeof Button>;


const Template: ComponentStory<typeof Button> = (args) => <Button {...args} />;

// This is an accessible story
export const Accessible = Template.bind({});
Accessible.args = {
  primary: false,
  label: 'Button',
};

// This is not
export const Inaccessible = Template.bind({});
Inaccessible.args = {
  ...Accessible.args,
  backgroundColor: 'red',
};

反复浏览这两个故事,会看到无法访问的故事包含一些需要修复的问题。

在可访问性面板中打开违规选项卡可以清楚地描述可访问性问题以及解决该问题的指南。


配置

开箱即用,Storybook 的可访问性插件包括一组涵盖大多数问题的可访问性规则。

还可以微调插件配置或覆盖 Axe 的规则集以最适合需求。

全局配置 a11y

如果需要在所有故事中取消可访问性规则或修改其设置,可以将以下内容添加到 storybook/preview.js

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

// .storybook/preview.js

export const parameters = {
  a11y: {
    // 要检查的可选选择器
    element: '#root',
    config: {
      rules: [
        {
          // 自动完成规则将不会根据提供的 CSS 选择器运行
          id: 'autocomplete-valid',
          selector: '*:not([autocomplete="nope"])',
        },
        {
          // 将 enabled 选项设置为 false 将禁用对所有故事的此特定规则的检查。
          id: 'image-alt',
          enabled: false,
        },
      ],
    },
    // Axe 的选项参数
    options: {},
    // 防止自动检查的可选标志
    manual: true,
  },
};

组件级 a11y 配置

还可以为组件的所有故事自定义自己的规则集。 更新故事的默认导出并添加具有所需配置的参数:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31

// MyComponent.stories.js|jsx|ts|tsx

import { MyComponent } from './MyComponent';

export default {
  title: 'Configure a11y addon',
  component: MyComponent,
  parameters: {
    a11y: {
      // 要检查的可选选择器
      element: '#root',
      config: {
        rules: [
          {
            // 自动完成规则将不会根据提供的 CSS 选择器运行
            id: 'autocomplete-valid',
            selector: '*:not([autocomplete="nope"])',
          },
          {
            // 将 enabled 选项设置为 false 将禁用对所有故事的此特定规则的检查。
            id: 'image-alt',
            enabled: false,
          },
        ],
      },
      options: {},
      manual: true,
    },
  },
};

故事级 a11y 配置

通过更新故事以包含新参数,自定义故事级别的 a11y 规则集:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36

// MyComponent.stories.ts|tsx

import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { MyComponent } from './MyComponent';

export default {
  title: 'Configure a11y addon',
  component: MyComponent,
} as ComponentMeta<typeof MyComponent>;

const Template: ComponentStory<typeof MyComponent> = () => <MyComponent />;

export const ExampleStory = Template.bind({});
ExampleStory.parameters = {
  a11y: {
    element: '#root',
    config: {
      rules: [
        {
          // 自动完成规则将不会根据提供的 CSS 选择器运行
          id: 'autocomplete-valid',
          selector: '*:not([autocomplete="nope"])',
        },
        {
          // 将 enabled 选项设置为 false 将禁用对所有故事的此特定规则的检查。
          id: 'image-alt',
          enabled: false,
        },
      ],
    },
    options: {},
    manual: true,
  },
};

如何禁用 a11y 测试

通过将以下参数分别添加到故事的导出或组件的默认导出,禁用故事或组件的可访问性测试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21

// MyComponent.stories.ts|tsx

import React from 'react';
import { ComponentStory, ComponentMeta } from '@storybook/react';
import { MyComponent } from './MyComponent';

export default {
  title: 'Disable a11y addon',
  component: MyComponent,
} as ComponentMeta<typeof MyComponent>;

const Template: ComponentStory<typeof MyComponent> = () => <MyComponent />;

export const NonA11yStory = Template.bind({});
NonA11yStory.parameters = {
  a11y: {
    // 此选项禁用对此故事的所有 a11y 检查
    disable: true,
  },
};

使用 Jest 自动化可访问性测试

使用 Storybook 进行可访问性测试缩短了反馈循环,这意味着可以更快地解决问题。

在 Jest 测试中重用故事,并使用 jest-axe 集成对它们运行可访问性审核。

这也解锁了将可访问性测试集成到功能测试管道中的能力。

例如,包含以下测试文件以对故事运行可访问性测试:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19

// MyComponent.test.js

import { render } from '@testing-library/react';
import { composeStories } from '@storybook/testing-react';
import { axe, toHaveNoViolations } from 'jest-axe';
import * as MyComponentStories from './MyComponent.stories';

const { Accessible } = composeStories(MyComponentStories);

expect.extend(toHaveNoViolations);

test('Example accessiblity test', async () => {
  const { container } = render(<Accessible />);

  const AxeResults = await axe(container);

  expect(AxeResults).toHaveNoViolations();
});

当执行测试脚本时,它将运行可访问性审核以及可能拥有的任何交互测试。

基于浏览器和基于 linter 的可访问性测试有什么区别?

基于浏览器的可访问性测试(如 Storybook 中的内容)会评估渲染的 DOM,因为这可以为您提供最高的准确度。

尚未编译的审计代码是从真实代码中删除的一步,因此不会捕获用户可能遇到的所有内容。


知识点

  • a11y 插件