Skip to content
返回质量保障

CI 中无障碍

在构建管道自动 a11y 检查使回归在 PR 抓非发布后。

为什么

无障碍静默回归——开发者加 <div onClick> 而非 <button>、设计师选不过对比的新灰、重构移 aria-label——这些都不被功能测试抓。到有人跑手动 a11y 审计时(如果有,年度),应用累积 50 违规,修是大努力。CI 自动在 PR 抓回归: axe-core 每页跑、jsx-a11y 每文件 lint、引入新违规则坏构建。这把 a11y 从周期慌乱变维护基线——同测试和 lint 为 bug 和代码质量提供的左移。成本是每 PR 几分钟 CI 时间;收益是从不累积 a11y 债的应用。

何时使用

从项目第一天。首 PR 前加 axe-core 和 jsx-a11y 到 CI,使 a11y 是基线非追溯。

如何做

1. 加 eslint-plugin-jsx-a11y lint 时检查
   - 写时在编辑器 PR 前抓问题
   - 规则: alt-text、aria-role、label-has-associated-control、no-static-element-interactions
   - 比 axe-core 快(无需浏览器),抓约 20% 问题

2. 加 axe-core 到 Playwright 集成测试
   - 导航到每页后跑,断言无违规
   - withTags(["wcag2a", "wcag2aa"]) A 级和 AA
   - 用 withRules 禁假阳性规则带记录原因
   - 抓约 30% 问题含 jsx-a11y 漏的

3. 加 Lighthouse a11y 审计到 CI
   - lighthouse-ci 查部署预览上 a11y 分
   - 设最低分(如 90)降则坏构建
   - 抓渲染页对比、alt 和标签问题

4. 处理违规
   - 修违规(首选——代码错)
   - 或带记录原因禁规则(假阳性)
   - 永不全局禁——每元素禁带注释解释为何

5. 随时间追踪 a11y 分
   - 每 PR 贴 Lighthouse a11y 分(如测试覆盖徽章)
   - 降超 5 分告警
   - 回顾审查分趋势

示例

// 1. eslint-plugin-jsx-a11y(lint 时)
// .eslintrc.json
{
  "extends": ["plugin:jsx-a11y/recommended"],
  "rules": {
    "jsx-a11y/no-autofocus": "error",
    "jsx-a11y/click-events-have-key-events": "error",
    "jsx-a11y/no-static-element-interactions": "error"
  }
}

// 2. Playwright 中 axe-core(集成)
import { test, expect } from "@playwright/test";
import AxeBuilder from "@axe-core/playwright";

const pages = ["/", "/products", "/cart", "/checkout", "/account"];

for (const path of pages) {
  test(`${path} 无 a11y 违规`, async ({ page }) => {
    await page.goto(path);
    const results = await new AxeBuilder({ page })
      .withTags(["wcag2a", "wcag2aa"])
      .disable(["color-contrast"]) // 禁带原因: 单独测
      .analyze();
    expect(results.violations).toEqual([]);
  });
}

// 3. Lighthouse CI
// lighthouserc.json
{
  "ci": {
    "collect": {
      "url": ["https://staging.example.com/", "https://staging.example.com/checkout"]
    },
    "assert": {
      "assertions": {
        "categories:accessibility": ["error", { "minScore": 0.9 }]
      }
    }
  }
}

// 4. 处理假阳性(每元素禁非全局)
// eslint-disable-next-line jsx-a11y/no-static-element-interactions
// 原因: 此 div 是拖放放置区,通过单独按钮键盘可访问。见 issue #1234。
<div onDrop={handleDrop} />

// 5. CI 工作流
// .github/workflows/a11y.yml
name: 无障碍
on: [pull_request]
jobs:
  a11y:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx eslint .  # jsx-a11y 规则
      - run: npx playwright test  # axe-core 检查
      - run: npx lhci autorun  # Lighthouse a11y 分

常见问题

  • ⚠加 axe-core 到 CI 但禁每失败规则使构建过——检查跑但抓无,团队信 a11y"已处理"而违规累积。
  • ⚠仅首页跑 axe-core 漏结账、账户和管理页——首页过,但结账流程(用户花钱处)有 15 违规发到生产。
  • ⚠Lighthouse a11y 分阈值设 50"开始"永不提——分留 50,应用半无障碍,阈值无改进压力。
  • ⚠把 axe-core 违规当唯一 a11y 工作——CI 过但无人做键盘或屏幕阅读器测试,axe 不能抓的 70% 问题发到生产。

最佳实践

  • ✓第一天加 jsx-a11y(lint)+ axe-core(集成)+ Lighthouse(分)到 CI——分层自动化在 PR 抓机械问题,手动测试在发布前抓其余。
  • ✓每页跑 axe-core 非仅首页——建页列表循环,使结账和账户页与着陆页同严查。
  • ✓永不全局禁规则——每元素禁带注释解释原因并链 issue,使禁在审查可见且底层问题修时可移。
  • ✓把 CI a11y 检查当地板非天花板——抓 30% 问题,另 70% 需每发布前安排的手动键盘和屏幕阅读器测试。