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

使用 Storybook 进行可访问性测试

通过集成工具实现快速反馈

美国有 26% 的成年人 至少患有一种残疾。当您提高可访问性时,它会对您当前和未来的客户产生巨大的影响。这也是一项法律要求。

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

这就是为什么许多公司现在使用自动化和手动测试相结合的方式。自动化可以低成本地帮助开发者发现常见的可访问性问题。手动 QA 则保留用于需要人工关注的更棘手的问题。

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

为什么要自动化?

在开始之前,让我们检查一下常见的残疾类型:视觉、听觉、行动、认知、言语和神经系统。这些用户残疾产生了如下应用需求:

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

过去,您需要通过检查浏览器、设备和屏幕阅读器的组合来验证每个组件的这些要求。但这手工操作是不切实际的,因为应用程序有数十个组件,并且 UI 不断更新。

自动化加速您的工作流程

自动化工具会根据 WCAG 规则和其他行业认可的最佳实践,针对一组启发式方法审核渲染的 DOM。它们充当 QA 的第一道防线,以捕获明显的违反可访问性的行为。

例如,Axe 平均可以自动发现 57% 的 WCAG 问题。这使团队能够将其专家资源集中在需要手动审查的更复杂的问题上。

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

Storybook 插件在浏览器中运行检查(与 Jest 的 jsdom 相反),因此可以捕获诸如低对比度之类的问题。但是,它确实需要您手动验证每个 Story。

可访问性测试工作流程

通过在整个开发过程中运行这些检查,您可以缩短反馈循环并更快地修复问题。以下是工作流程的样子:

  1. 👨🏽‍💻 开发期间: 使用 Storybook 专注于一次一个组件。使用 A11y 插件模拟视觉缺陷并在组件级别运行可访问性审核。
  2. 对于 QA: 将 Axe 审核集成到您的功能测试管道中。对所有组件运行检查以捕获回归。

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

安装可访问性插件

Storybook 的 Accessibility 插件在活动的 Story 上运行 Axe。它在面板中可视化测试结果,并概述所有存在违规的 DOM 节点。

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

复制
.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 组件,并将它的所有用例捕获为 Story。在开发阶段,您可以循环浏览这些 Story 以发现可访问性问题。

复制
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> 元素。因此,我们需要更新我们的 Story,将组件包装在 <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 中可以简化您的开发工作流程。您不必在处理组件时在不同的工具之间跳转。您需要的一切都在浏览器中。您甚至可以模拟视觉障碍,例如红色盲、绿色盲或蓝色盲。

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

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

让我们继续配置测试运行器以运行 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 设置为从 Story 的根元素开始,然后遍历 DOM 树以检查问题。它还将根据它遇到的问题生成详细的报告,并输出违反可访问性规则的 HTML 元素列表。

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

捕获集成问题

UI 通过组合组件并将它们连接到数据和 API 来组装。这有很多潜在的故障点。接下来,我们将研究如何使用 Cypress 通过一次性测试系统的所有层来捕获集成问题。

让你的代码与本章保持同步。在 GitHub 上查看 d16be74。
这个免费指南对您有帮助吗? 发推文以示赞赏并帮助其他开发者找到它。
下一章
用户流程
验证您的 UI 端到端工作
✍️ 在 GitHub 上编辑 – 欢迎 PR!
加入社区
6,721位开发者以及更多
为什么为什么选择 Storybook组件驱动的 UI
开源软件
Storybook - Storybook 中文

特别感谢 Netlify CircleCI