5月27日 23:02

Cypress 可访问性测试怎么做?cypress-axe 集成与 WCAG 合规实战

Cypress 可访问性测试怎么做?cypress-axe 集成与 WCAG 合规实战

可访问性测试(Accessibility Testing,简称 a11y)验证 Web 应用能否被残障人士正常使用。Cypress 本身不内置可访问性检查能力,但通过集成 axe-core 引擎,可以用 cy.checkA11y() 一行命令扫描页面上违反 WCAG 标准的元素。

这篇文章覆盖从插件安装、测试编写到 CI 集成的完整流程,并补充 Cypress Accessibility Cloud 和 wick-a11y 两个新方案。

cypress-axe 插件怎么安装和配置

cypress-axe 是 Cypress 社区使用最广的可访问性插件,封装了 Deque 公司的 axe-core 规则引擎。安装分三步:

第一步,装包:

bash
npm install cypress-axe --save-dev

第二步,在 cypress/support/e2e.js 中引入:

javascript
import 'cypress-axe';

第三步,在每个测试前注入 axe-core 到页面。cypress-axe 提供了 cy.injectAxe() 命令,通常放在 beforeEach 里:

javascript
beforeEach(() => { cy.visit('/login'); cy.injectAxe(); });

injectAxe() 的作用是把 axe-core 的脚本注入到当前页面的 window 对象上。不调用它,cy.checkA11y() 会报错。

有几点需要注意:

  • cypress-axe 不是 Cypress 官方包,它依赖 axe-core,两者版本要兼容。查看 cypress-axe 的 changelog 确认支持的 axe-core 版本
  • 如果项目用 TypeScript,可能需要 cypress-axe 的类型声明:npm install -D @types/cypress-axe
  • injectAxe() 必须在页面加载之后调用,否则找不到 document 对象

cy.checkA11y() 的基本用法和参数

全页面扫描

最简单的用法,检查整个页面:

javascript
it('登录页没有可访问性问题', () => { cy.visit('/login'); cy.injectAxe(); cy.checkA11y(); });

测试失败时,Cypress 命令行会输出每个违规项的详细信息:规则 ID、影响级别(critical / serious / moderate / minor)、违规元素选择器、修复建议。

指定扫描范围

只检查某个容器内的元素:

javascript
cy.checkA11y('.main-content'); // 或者用 Cypress 链式查找 cy.get('form').checkA11y();

自定义规则和运行参数

checkA11y 的第二个参数是 axe 的配置对象:

javascript
cy.checkA11y(null, { runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa'] // 只检查 WCAG 2.x A 和 AA 级别 }, rules: { 'color-contrast': { enabled: false }, // 暂时跳过颜色对比度 'region': { enabled: true } // 强制检查地标区域 } });

排除特定元素

第三方组件或广告区域可能无法修改,可以排除:

javascript
cy.checkA11y({ exclude: ['.ad-banner', '#third-party-widget'] });

自定义违规回调

不希望测试直接失败,而是收集违规信息做进一步处理:

javascript
cy.checkA11y(null, null, (violations) => { violations.forEach((v) => { cy.log(`${v.id}: ${v.nodes.length} 个元素违规`); }); }, true); // 第四个参数 skipFailures = true,不导致测试失败

实际项目中的测试策略

按页面或功能模块编写测试

不建议把所有可访问性检查塞进一个巨大的测试文件。按页面拆分更清晰:

javascript
// cypress/e2e/accessibility/login.spec.js describe('登录页可访问性', () => { beforeEach(() => { cy.visit('/login'); cy.injectAxe(); }); it('初始状态符合 WCAG 2.1 AA', () => { cy.checkA11y(null, { runOnly: { type: 'tag', values: ['wcag2a', 'wcag2aa'] } }); }); it('表单报错时的提示可被屏幕阅读器识别', () => { cy.get('button[type="submit"]').click(); // 等待错误提示出现 cy.get('.error-message').should('be.visible'); cy.checkA11y(); }); });

键盘导航和焦点管理

axe-core 无法检测所有键盘交互问题。需要手动编写测试来补充:

javascript
it('可以用 Tab 键在表单元素间导航', () => { cy.get('input[name="email"]').focus(); cy.focused().tab(); cy.focused().should('have.attr', 'name', 'password'); cy.focused().tab(); cy.focused().should('have.attr', 'type', 'submit'); }); it('焦点不会跳到隐藏的模态框', () => { cy.get('[role="dialog"]').should('not.be.visible'); cy.get('body').tab({ shift: true }); cy.focused().should('not.have.attr', 'role', 'dialog'); });

ARIA 属性断言

对关键 ARIA 属性做显式断言,比依赖自动扫描更可靠:

javascript
it('导航菜单的 ARIA 属性正确', () => { cy.get('nav').should('have.attr', 'role', 'navigation'); cy.get('nav').should('have.attr', 'aria-label'); }); it('按钮有可访问名称', () => { cy.get('button.submit') .should('have.attr', 'aria-label') .or('have.text'); // 至少有 aria-label 或文本内容 });

cypress-axe 之外的选择

wick-a11y

wick-a11y 是 cypress-axe 的替代方案,提供了更清晰的 HTML 报告和截图标注:

bash
npm install -D wick-a11y
javascript
// cypress/support/e2e.js import 'wick-a11y';

使用 cy.checkAccessibility() 代替 cy.checkA11y()

javascript
it('首页可访问性检查', () => { cy.visit('/'); cy.checkAccessibility(); });

wick-a11y 的优势在于测试失败时直接在 Cypress 截图上标注违规元素位置,比纯文本日志更容易定位问题。

Cypress Accessibility Cloud

2025 年 Cypress 推出了 Cypress Accessibility 平台,集成在 Cypress Cloud 中。它不需要额外安装插件,而是基于已有的测试录制自动生成可访问性报告。

使用方式很简单:只要测试运行时开启了云录制,Cypress Cloud 会自动分析页面快照,标记可访问性问题。这对不想维护额外测试代码的团队是个低门槛选项。

不过它目前只覆盖部分 WCAG 规则,深度检查仍然需要 cypress-axe 或 wick-a11y。

CI 集成和报告

GitHub Actions 配置

yaml
name: E2E with A11y on: [push] jobs: test: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - uses: cypress-io/github-action@v6 with: spec: cypress/e2e/accessibility/**/*.spec.js

生成 HTML 报告

cypress-axe-reporter 或 Mochawesome 生成可读性更好的报告:

bash
npx cypress run --reporter mochawesome --spec 'cypress/e2e/accessibility/**'

对于需要给产品经理或合规团队看的场景,HTML 报告比命令行输出实用得多。

处理已知问题

项目中常有一些暂时无法修复的可访问性问题(比如第三方 SDK 内嵌的 iframe)。两种处理方式:

一是用 skipFailures 参数让测试不挂:

javascript
cy.checkA11y(null, null, null, true);

二是用 A11y 错误日志专门记录,在代码注释中标记 issue 编号,后续跟踪修复。

常见坑和排查思路

injectAxe 时机不对

如果页面有重定向或 SPA 路由切换,injectAxe() 需要在每次页面变化后重新调用:

javascript
it('SPA 路由切换后重新注入', () => { cy.visit('/'); cy.injectAxe(); cy.checkA11y(); cy.get('a[href="/about"]').click(); cy.injectAxe(); // 路由切换后重新注入 cy.checkA11y(); });

动态内容渲染时机

不要用 cy.wait(1000) 等待动态内容。Cypress 本身会自动等待 DOM 变化,配合 .should() 断言更可靠:

javascript
// 错误做法 cy.wait(1000); cy.checkA11y(); // 正确做法 cy.get('.dynamic-content').should('be.visible'); cy.checkA11y();

iframe 内的检查

axe-core 默认不检查 iframe 内部内容。如果页面嵌入了 iframe,需要配置 iframes 选项:

javascript
cy.checkA11y(null, { iframes: true });

但跨域 iframe 受浏览器安全策略限制,无法访问。这种情况只能手动测试或通过 iframe 内部页面的独立测试覆盖。

第三方组件的可访问性问题

组件库(如 MUI、Ant Design)生成的 DOM 结构可能存在 ARIA 属性缺失或错误。两种思路:

一是在组件级别写测试,而不是页面级别,缩小排查范围:

javascript
// 测试自定义封装的 DatePicker 组件 cy.mount(<DatePicker />); cy.injectAxe(); cy.checkA11y();

二是向组件库提 issue,大部分主流组件库对可访问性 bug 响应积极。

自动化测试的边界

axe-core 能检测大约 30%~40% 的 WCAG 问题。以下情况必须手动验证:

  • 屏幕阅读器的实际朗读顺序和内容
  • 键盘 Tab 顺序是否符合视觉布局的逻辑顺序
  • 颜色对比度在不同显示器和亮度下的实际效果
  • 视频是否提供字幕和音频描述
  • 表单 placeholder 不能替代 label 的语义

自动化测试通过不代表应用完全可访问。它只是第一道关卡,后续还需要手动复核和用户测试。

标签:Cypress