返回UI 测试手册
React
章节
  • 引言
  • 视觉
  • 组合
  • 交互
  • 可访问性
  • 用户流程
  • 自动化
  • 工作流程
  • 结论

使用 Storybook 进行可访问性测试

通过集成工具获得快速反馈

美国有26%的成年人至少有一项残障。当你改善可访问性时,会对你当前和未来的客户产生巨大影响。这也是法律要求。

检查可访问性的最准确方法是在真实设备上手动检查。但这需要专业的知识和大量时间,而这些在前端团队中都很稀缺。

这就是为什么许多公司现在结合使用自动化和手动测试。自动化能够以较低的开发人员投入捕获常见的可访问性问题。手动质量保证(QA)则用于需要人工关注的更棘手问题。

有大量资源深入探讨可访问性原则,因此我们在这里不会详细介绍。相反,我们将重点介绍如何使用 Storybook 自动化可访问性测试。这是一种务实的方法,用于查找和修复您可能遇到的大多数问题。

为什么选择自动化?

在我们开始之前,让我们先了解一下常见的残障类型:视觉、听觉、行动、认知、言语和神经系统障碍。这些用户残障会带来以下应用需求:

  • ⌨ 键盘导航
  • 🗣 屏幕阅读器支持
  • 👆 触控友好
  • 🎨 足够的颜色对比度
  • ⚡️ 减少动画效果
  • 🔍 缩放

过去,您需要通过在各种浏览器、设备和屏幕阅读器组合中检查每个组件来验证这些要求。但手动完成这很不切实际,因为应用程序有几十个组件,而且用户界面不断更新。

自动化加速您的工作流程

自动化工具根据基于WCAG规则和其他行业公认最佳实践的一系列启发式方法来审计渲染后的 DOM。它们是质量保证的第一道防线,用于捕获明显的无障碍违规问题。

例如,Axe 平均可以自动找到 57% 的 WCAG 问题。这使得团队能够将专家资源集中用于需要手动审查的更复杂问题。

许多团队使用Axe 库,因为它与大多数现有测试环境集成。例如,Twilio Paste 团队使用 jest-axe 集成。而 Shopify Polaris 和 Adobe Spectrum 团队则使用Storybook 插件版本。

Storybook 插件在浏览器中运行检查(与 Jest 使用的 jsdom 不同),因此可以捕获低对比度等问题。但是,它确实需要您手动验证每个故事(story)。

可访问性测试工作流程

通过在整个开发过程中运行这些检查,您可以缩短反馈周期并更快地修复问题。工作流程如下所示:

  1. 👨🏽‍💻 开发期间: 使用 Storybook 一次只关注一个组件。使用 A11y 插件模拟视力缺陷,并在组件级别运行可访问性审计。
  2. 质量保证(QA): 将 Axe 审计集成到您的功能测试流水线中。对所有组件运行检查以捕获回归问题。

让我们看看这个工作流程的实际应用。

安装可访问性插件

Storybook 的可访问性功能会在当前激活的故事(story)上运行 Axe。它在面板中可视化显示测试结果,并标记所有存在违规问题的 DOM 节点。

要安装该插件,请运行:yarn add --dev @storybook/addon-a11y。然后,在您的 .storybook/main.js 文件中的 addons 数组中添加 '@storybook/addon-a11y'

复制
.storybook/main.js
/** @type { import('@storybook/react-vite').StorybookConfig } */
const config = {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  staticDirs: ['../public'],
  addons: [
    '@storybook/addon-links',
    '@storybook/addon-essentials',
    '@storybook/addon-interactions',
+   '@storybook/addon-a11y',
  ],
  framework: {
    name: '@storybook/react-vite',
    options: {},
  },
};
export default config;

编码时进行可访问性测试

我们已经隔离了 Task 组件,并将其所有用例捕获为故事(stories)。在开发阶段,您可以循环查看这些故事来发现可访问性问题。

复制
src/components/Task.stories.jsx
import Task from './Task';

export default {
  component: Task,
  title: 'Task',
  argTypes: {
    onArchiveTask: { action: 'onArchiveTask' },
    onTogglePinTask: { action: 'onTogglePinTask' },
    onEditTitle: { action: 'onEditTitle' },
  },
};

export const Default = {
  args: {
    task: {
      id: '1',
      title: 'Buy milk',
      state: 'TASK_INBOX',
    },
  },
};

export const Pinned = {
  args: {
    task: {
      id: '2',
      title: 'QA dropdown',
      state: 'TASK_PINNED',
    },
  },
};

export const Archived = {
  args: {
    task: {
      id: '3',
      title: 'Write schema for account menu',
      state: 'TASK_ARCHIVED',
    },
  },
};

const longTitleString = `This task's name is absurdly large. In fact, I think if I keep going I might end up with content overflow. What will happen? The star that represents a pinned task could have text overlapping. The text could cut-off abruptly when it reaches the star. I hope not!`;

export const LongTitle = {
  args: {
    task: {
      id: '4',
      title: longTitleString,
      state: 'TASK_INBOX',
    },
  },
};

注意插件发现了两个违规问题。第一个是 “元素必须满足最低颜色对比度阈值”,这是针对 archived 状态的。本质上这意味着任务标题和背景之间没有足够的对比度。我们可以通过在应用程序的 CSS 文件(位于 src/index.css)中将文本颜色更改为较深的灰色来快速修复此问题。

复制
src/index.css
.list-item.TASK_ARCHIVED input[type="text"] {
- color: #a0aec0;
+ color: #4a5568;
  text-decoration: line-through;
}

第二个违规问题,“某些 ARIA 角色必须包含在特定父级元素中”,表明 DOM 结构不正确。Task 组件只渲染了一个 <li> 元素。因此,我们需要更新我们的故事(stories),将组件包装在一个 <ul> 元素中。

复制
src/components/Task.stories.jsx
import Task from './Task';

export default {
  component: Task,
  title: 'Task',
  argTypes: {
    onArchiveTask: { action: 'onArchiveTask' },
    onTogglePinTask: { action: 'onTogglePinTask' },
    onEditTitle: { action: 'onEditTitle' },
  },
};

/*
*👇 Wraps the component with a custom render function.
* See https://storybook.org.cn/docs/api/csf
* to learn how to use render functions.
*/
export const Default = {
  render: (args) => (
    <ul>
      <Task {...args} />
    </ul>
  ),
  args: {
    task: {
      id: '1',
      title: 'Buy milk',
      state: 'TASK_INBOX',
    },
  },
};

export const Pinned = {
  render: (args) => (
    <ul>
      <Task {...args} />
    </ul>
  ),
  args: {
    task: {
      id: '2',
      title: 'QA dropdown',
      state: 'TASK_PINNED',
    },
  },
};

export const Archived = {
  render: (args) => (
    <ul>
      <Task {...args} />
    </ul>
  ),
  args: {
    task: {
      id: '3',
      title: 'Write schema for account menu',
      state: 'TASK_ARCHIVED',
    },
  },
};

const longTitleString = `This task's name is absurdly large. In fact, I think if I keep going I might end up with content overflow. What will happen? The star that represents a pinned task could have text overlapping. The text could cut-off abruptly when it reaches the star. I hope not!`;

export const LongTitle = {
  render: (args) => (
    <ul>
      <Task {...args} />
    </ul>
  ),
  args: {
    task: {
      id: '4',
      title: longTitleString,
      state: 'TASK_INBOX',
    },
  },
};

现在您可以对所有其他组件重复此过程。

将可访问性测试集成到 Storybook 中可以简化您的开发工作流程。在处理组件时,您无需在不同的工具之间跳转。您所需的一切都在浏览器中。您甚至可以模拟色觉缺陷,例如氘色弱、原色弱或三色盲。

使用测试运行器自动捕获回归问题

通常,对组件的更改可能会无意中引入新的可访问性问题。为了捕获此类回归问题,您需要在打开拉取请求之前测试所有故事(stories)。但是,Accessibility 插件仅在您查看某个故事时运行检查。要一次性测试所有故事,我们可以使用 Storybook 测试运行器。它是一个独立的实用程序(由 JestPlaywright 提供支持),用于检查故事中的渲染错误。

接下来,我们来配置测试运行器以运行 Axe。首先安装 axe-playwright

复制
yarn add --dev axe-playwright

在 Storybook 目录中添加一个新的配置文件,内容如下

复制
.storybook/test-runner.js
const { injectAxe, checkA11y } = require('axe-playwright');

module.exports = {
  async preVisit(page) {
    await injectAxe(page);
  },
  async postVisit(page) {
    await checkA11y(page, "#storybook-root", {
      detailedReport: true,
      detailedReportOptions: {
        html: true,
      },
    });
  },
};

preVisitpostVisit 是方便的钩子,允许您配置测试运行器来执行其他任务。我们使用这些钩子将 Axe 注入到故事(story)中,然后在渲染完成后运行可访问性测试。

您会注意到传递给 checkA11y 函数的一些选项。我们配置了 Axe 从故事的根元素开始,然后遍历 DOM 树来检查问题。它还将根据遇到的问题生成详细报告,并输出违反可访问性规则的 HTML 元素列表。

要运行测试,请在一个终端窗口中通过 yarn storybook 启动 Storybook,在另一个终端窗口中通过 yarn test-storybook 启动测试运行器。

捕获集成问题

用户界面是通过组合组件并将其与数据和 API 连接起来构建的。这会产生很多潜在的故障点。接下来,我们将看看如何使用 Cypress 通过一次性测试系统的所有层来捕获集成问题。

使您的代码与本章同步。在 GitHub 上查看 d16be74。
这份免费指南对您有帮助吗?发推文点赞,并帮助其他开发者找到它。
下一章
用户流程
验证您的 UI 是否端到端正常工作
✍️ 在 GitHub 上编辑 – 欢迎提交 PR!
加入社区
6,975名开发者,仍在增长
为何为何选择 Storybook组件驱动的 UI
开源软件
Storybook - Storybook 中文

特别感谢 Netlify CircleCI