学完这篇,你能用 axe 在几分钟内扫出一个页面的无障碍问题,并知道按什么顺序、改哪几行代码把它们修掉。
axe 是目前最主流的自动化无障碍检测引擎(axe-core),浏览器里的 axe DevTools 扩展、CI 里的 CLI 和 Playwright 集成都是同一套规则。它依据 WCAG 2.0/2.1 的 A、AA 级标准,把问题分成 critical / serious / moderate / minor 四档,正好可以当修复优先级用。
第一步:装好扫描工具
三种入口,按场景选:
- 手动看单个页面:Chrome / Edge 应用商店装 axe DevTools 扩展。
- 命令行批量扫:
npx @axe-core/cli https://example.com --tags wcag2a,wcag2aa --exit
- 写进自动化测试:
npm i -D @playwright/test @axe-core/playwright
安装后验证一下扩展是否可用:打开任意页面 → F12 → 找到 axe DevTools 标签页 → 点「Scan ALL of my page」。如果标签页不存在,多半是扩展被禁用或没权限访问该站点(无痕模式常见)。
第二步:跑一次基线扫描
先用扩展做一次全站抽样(首页、列表页、详情页、表单页各一个),把结果导出成 JSON 或截图存档,这就是你的修复基线。之后所有改动都和它对比。
接入 CI 时用 Playwright,最直观:
import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
test('首页无障碍扫描', async ({ page }) => {
await page.goto('http://localhost:3000/');
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
.analyze();
expect(results.violations).toEqual([]);
});
跑 npx playwright test 就能看到失败详情。
注意:首次接入不要直接 toEqual([]) 卡死整个流水线,老项目一次可能扫出几百条。先只卡 critical 和 serious,用 results.violations.filter(v => ['critical','serious'].includes(v.impact)) 过滤,其余逐步清零。
第三步:读懂报告里到底说了什么
每条 violation 包含这些字段,看准了再动手:
id / help:规则名,比如 image-alt、color-contrast。
impact:影响等级,critical 通常是「屏幕阅读器完全无法使用」。
nodes[].target:CSS 选择器,直接定位到出问题的元素。
nodes[].failureSummary:人话解释,比如「元素没有可访问名称」。
helpUrl:对应 Deque 官方说明页,含正确示例。
第四步:按高频规则逐个修
实际项目里 80% 的问题集中在下面几条,照着改就行:
image-alt —— 所有 <img> 必须有 alt:
<img src="chart.png" alt="2024 年季度销售额增长趋势图">
<img src="divider.png" alt=""> <!-- 纯装饰图,空 alt 是正确写法 -->
color-contrast —— 正文对比度至少 4.5:1,大字号(≥24px,或 ≥18.66px 且加粗)至少 3:1。改 CSS 颜色变量,别逐处硬改。用扩展面板里的取色器能实时看到修正后的色值。
label —— 表单控件必须有关联标签:
<label for="email">邮箱</label>
<input id="email" type="email">
<!-- 视觉上不显示标签时 -->
<input type="search" aria-label="搜索帖子">
button-name / link-name —— 图标按钮没有文字:
<button aria-label="关闭弹窗"><svg>...</svg></button>
html-has-lang —— 根元素声明语言:<html lang="zh-CN">。
heading-order —— 标题层级别跳级,h1 之后是 h2,不要直接跳到 h4。
landmark-one-main —— 页面主体内容包一层 <main>,导航用 <nav>。
注意:不要为了消警告到处加 aria-label。给已经有可见文字的元素再加 aria-label 会覆盖原文,反而让屏幕阅读器读错。优先用原生语义标签,aria-* 只在原生语义不够时补。
第五步:建立回归与豁免机制
修完一轮,把 Playwright 用例挂进 CI,每次提交自动跑。对确实无法修改的部分(第三方组件、广告位),用 exclude 精确排除,而不是全局关规则:
const results = await new AxeBuilder({ page })
.withTags(['wcag2aa'])
.exclude('#third-party-widget')
.analyze();
如果必须禁用某条规则,用 .disableRules(['color-contrast']) 并在代码里写清原因和负责人,别让它变成永久债。
注意:axe 只能自动发现约 30%–50% 的无障碍问题。键盘 Tab 能否走通、焦点是否可见、屏幕阅读器朗读顺序是否合理、动态内容是否有 aria-live 播报,这些它测不出来,仍需要人工过一遍。把 axe 当过滤器,不当验收标准。
小结
- 工具一个就够:扩展看单页、CLI 扫批量、Playwright 进 CI,规则源都是 axe-core。
- 流程固定:抽样出基线 → 按 impact 分级 → 按高频规则修复 → 挂 CI 防回归。
- 修复优先用原生 HTML 语义,
aria-* 是补丁不是主力。
- 豁免要精确到选择器并写清原因,别全局关规则。
- axe 只覆盖部分问题,键盘和屏幕阅读器实测不能省。