一个按钮引发的血案:如何用axe-core把网页无障碍测试从加班噩梦变成5分钟日常
【免费下载链接】axe-coreAccessibility engine for automated Web UI testing项目地址: https://gitcode.com/gh_mirrors/ax/axe-core
周五下午四点,距离上线还有三天。产品经理丢过来一张截图:某无障碍审计报告,满屏红色"Violations",涉及十几个页面。你打开浏览器扩展一个个点开看,发现"按钮没有可访问名称""图片缺少alt""颜色对比度不足"……一个下午过去,连一半都没看完。
如果你也经历过这种"无障碍测试 = 手动挨个页面排查 = 周末加班"的死循环,那么这篇 axe-core无障碍测试实战指南就是为你写的。axe-core(Accessibility engine for automated Web UI testing)是目前开源界最流行的网页无障碍自动化检测引擎,平均能自动发现约57%的WCAG问题,关键是——它能直接嵌进你现有的测试体系,把"事后补课"变成"日常巡检"。
初次尝试:装上了,跑起来了,然后看不懂了
先别急着写代码。我第一次接触 axe-core 时,也以为这是"装个库跑一下"那么简单。
npm install axe-core --save-dev在页面里引入node_modules/axe-core/axe.min.js,然后在需要检测的时机调用:
axe.run().then(results => { if (results.violations.length) { throw new Error('Accessibility issues found'); } });跑通了。结果对象里也确实有violations、incomplete、passes、inapplicable四类数据。但问题来了:我第一次看到incomplete数组里躺着几十条记录时,以为全是 bug。
其实不是。那是 axe-core 最值得尊重的设计之一:它宁可告诉你"我拿不准",也不硬给你一个结论。
拆解底层原理:一条规则,其实是几个"小法官"在投票
很多人的误区是把 axe-core 当成一个"魔法黑盒"——一调用就吐一堆结论。真正理解它,你只需要理解三层结构。
规则(Rule):负责"找谁测"
每个规则是一个 JSON 文件,躺在lib/rules/目录下。它做的事有两件:选出要测的元素,指定用哪些检查来测。
以最经典的button-name规则("按钮必须有可读文本")为例:
{ "id": "button-name", "impact": "critical", "selector": "button", "matches": "no-explicit-name-required-matches", "any": [ "button-has-visible-text", "aria-label", "aria-labelledby", "non-empty-title", "implicit-label", "explicit-label", "presentational-role" ], "all": [], "none": [] }selector告诉引擎"去把所有<button>揪出来";matches是一个过滤函数,用来排除一些特殊情况(比如某些元素明确不需要名称);然后就是关键的any、all、none三个数组。
检查(Check):负责"投一票"
每个检查是一个evaluate函数,返回 true/false/undefined,配上消息模板和可配置项。规则里那个any数组的意思是:7个检查里至少1个通过,按钮就合格。
any:至少一个返回 true → 通过("有一个算一个")all:全部返回 true → 通过("缺一个都不行")none:全部返回 false → 通过("碰一个就挂")
这一套"多个小法官投票"的机制,是 axe-core 能把误报压到接近零的核心。为什么?因为现代前端里一个可访问名称的来源实在太多了:aria-label、aria-labelledby、<label>、title、可见文本、甚至 SVG 里的<title>……任何单一检查都容易误判,但让它们互相兜底,结论就可靠得多。
结果分类:比"对/错"多两档
你看到的四类结果,其实对应引擎内部四个状态(见lib/core/constants.js):
| 结果 | 内部状态 | 含义 |
|---|---|---|
inapplicable | NA | 页面上根本没这类元素,跳过 |
passes | PASS | 确定通过 |
incomplete | CANTTELL | 拿不准,需要人工复核(也叫 needs review) |
violations | FAIL | 确定违规 |
incomplete是你必须学会"看见"的一档。以color-contrast为例,如果文字背景是渐变色、背景图、或者元素被其他元素遮挡,引擎根本无法算出精确的对比度——它不会硬报一个 3.9 就完事,而是把元素扔进incomplete,附上原因(bgImage、bgGradient、bgOverlap……),等你人工确认。这份"诚实",比那些张口就报的工具有价值得多。
上手实战:用一条自定义规则解决你们独有的坑
理解了"规则 + 检查"的机制,你就拥有了定制能力。团队里常见的场景是:你们的组件库有个祖传的样式,每个图标按钮都漏了可访问名称,偏偏默认规则扫不出来。
Axe-core 的目录结构为这种需求留好了位置:规则定义在lib/rules/,检查逻辑在lib/checks/,可复用工具函数在lib/commons/,执行引擎在lib/core/。写一条规则其实就三步:
- 写检查器:在
lib/checks/下新增一个xxx-evaluate.js,导出一个接收node、virtualNode、options的函数; - 注册检查:配套写一个 JSON,声明
evaluate和messages(pass/fail/incomplete 三条消息模板); - 定义规则:在
lib/rules/下写规则 JSON,把selector、matches、any/all/none串起来。
项目里还提供了脚手架命令pnpm run rule-gen,会帮你生成一套规则骨架文件,省去手工拼 JSON 的麻烦。构建时跑pnpm run build,开发时用pnpm run develop监听文件变化自动重构建。
一个容易忽略的细节:如果你的规则涉及 DOM 层级判断(查父级、查子级),请用
virtualNode而不是node。因为在 Shadow DOM 里,扁平化树上的父子关系才是真实的。用错 API,规则在普通 DOM 上跑得好好的,一进 Shadow DOM 就翻车。
把规则和检查文件"翻译"成你们团队的语言
Axe-core 支持多语言。locales/目录下已经躺着一堆da.json、ja.json、zh_CN.json……构建时用pnpm run build -- --lang=zh_CN就能生成中文版构建产物。不过更常见的做法是运行时配置:
axe.configure({ locale: { rules: { 'button-name': { help: '按钮必须包含可辨识的文本' } } } });这样你团队里的开发同学看到的中文提示,就不再是机器翻译腔了。
生产环境实战要点:让无障碍测试真正跑进CI
接入 CI 才是最值钱的环节。我的经验是三个"别":
- 别只测首页。无障碍问题集中在表单页、弹窗、深链页面。把 axe-core 挂到每条 PR 的 E2E 流程里,新增页面全量扫描。
- 别在 JSDOM 里测对比度。axe-core 对 JSDOM 是"有限支持"——文档里明确写了
color-contrast规则在 JSDOM 下不工作。node 环境测试记得关掉这条规则,否则你会收获一批莫名其妙的"失败"。 - 别忘 iframe。axe-core 能深入任意层级的 iframe 做检测(这是它的招牌能力之一),但前提是每个 iframe 里都要引入
axe.min.js。只在外层页面引入,iframe 里的内容就是盲区。
还有一个性能窍门:如果页面很大、结果很多,可以给axe.run传resultTypes,比如只保留violations和incomplete的完整节点信息,能明显缩短扫描时间。
新手最常踩的配置坑(避坑清单)
- 把
incomplete当失败上报。那是"待人工复核",不是"违规"。误报会把同事对自动化测试的信任一次性耗尽。 - 扫隐藏内容。默认规则不会测隐藏区域(未激活的菜单、关闭的弹窗)。要测它们,得先把内容激活/渲染可见,再跑一次。
- 忽略
matches函数。没有它,规则会误伤大量本不该测的元素(比如presentational-role明确豁免的情况)。 - 直接改构建产物。应该改
lib/下的源码再pnpm run build,而不是手改axe.min.js。 - 跨 iframe 的规则不写
after。需要统计全页数量的规则(比如 landmark 是否唯一),光在单个 frame 里 evaluate 是算不出来的,必须用after汇总各 frame 的数据。
想深入源码,从这些路径开始
- 规则定义:
lib/rules/(如lib/rules/button-name.json) - 检查器逻辑:
lib/checks/(如lib/checks/color/color-contrast.json) - 公共工具函数:
lib/commons/ - 执行引擎与结果管理:
lib/core/(lib/core/constants.js里定义四类结果) - 规则开发指南:
doc/rule-development.md - API 文档:
doc/API.md - 规则清单:
doc/rule-descriptions.md - 多语言目录:
locales/ - 测试:
test/rule-matches/、test/checks/、test/integration/full/
常见问题(FAQ)
Q1:axe-core 和无障碍浏览器扩展有什么区别?浏览器扩展是"事后人工点查"的工具,适合抽查;axe-core 是引擎,能嵌进单元测试、E2E、CI,实现"每次构建自动扫"。两者是互补关系:CI 跑 axe-core 抓确定性问题,扩展留给人工做最终确认。
Q2:npm 安装和从源码构建,该怎么选?只是接入项目用npm install axe-core --save-dev即可;想开发自定义规则、改源码或贡献新规则,才需要 clone 仓库(地址:https://gitcode.com/gh_mirrors/ax/axe-core),然后pnpm install、pnpm run build。
Q3:为什么我的 color-contrast 总是返回 incomplete?大概率是背景图、渐变、透明度或元素遮挡导致引擎算不出精确对比度。这是设计行为——axe-core 的原则是"不确定就不下结论"。可以配合人工复核,或用无背景图的测试 fixture 覆盖该规则。
Q4:axe-core 能测出100%的无障碍问题吗?不能。它平均只能自动发现约57%的WCAG问题,其余要么需要人工判断,要么需要真实用户测试。正确心态是:让自动化兜住确定性的那一大半,把专家精力留给真正需要判断力的部分。
Q5:旧浏览器支持到什么程度?Chrome 42+、Firefox 38+、Edge 40+、Safari 7+ 都在支持范围内(IE11 已标记废弃)。但要注意它只支持原生实现或正确 polyfill 的环境,v0 版旧 Shadow DOM 不受支持。
回头看,那把"无障碍测试"当成临上线前的折磨,本质是把一件应该持续发生的事压缩成了一夜之间的事。axe-core 真正改变的不是"多了一个检测工具",而是它让无障碍检测从"懂无障碍的人才敢碰的专家活",变成了每个前端都能在日常构建里顺手做完的普通测试。
自动化引擎负责兜住那57%的确定性问题,把稀缺的人工判断留给真正需要它的时候——这大概就是"为所有人创造平等访问机会"最务实的一种落地方式。现在,就从你项目里那个一直没人管的按钮开始吧。🚀
【免费下载链接】axe-coreAccessibility engine for automated Web UI testing项目地址: https://gitcode.com/gh_mirrors/ax/axe-core
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考