用 axe-core 做可访问性快照比对需序列化 violations 为结构化 JSON 并用 Jest 的 toMatchInlineSnapshot() 固化基线,必须指定 runOnly 规则集、避免滚动干扰、屏蔽动态内容,并以 violations[].id + nodes[0].target 为稳定比对依据。

怎么用 axe-core 做可访问性快照比对
axe-core 本身不带“回归测试”能力,但配合 jest-axe 或自定义脚本,能对同一页面多次运行结果做 diff。关键不是截图,而是把每次扫描输出的违规项(violations)序列化为结构化 JSON,再用 Jest 的 toMatchInlineSnapshot() 固化基线。
常见错误现象:jest-axe 默认只返回违规列表,没包含规则 ID、节点路径、影响等级等上下文,导致 snapshot 变动频繁、不可读;手动拼接字符串又容易漏字段。
- 必须启用
options: { runOnly: ['wcag2a', 'wcag2aa'] },否则每次 axe 版本升级可能引入新规则,snapshot 失效 - 用
axe({ restoreScroll: false, setBrowser: true })避免滚动干扰,尤其在 Puppeteer/Playwright 环境中 - 对动态内容(如加载中的 skeleton、实时时间戳),提前用
document.body.setAttribute('data-axe-ignore', 'true')或 CSS class 掩盖,而不是靠 ignore 选项——后者不保证 DOM 节点不被 axe 访问到
为什么不能只依赖 Lighthouse 的 a11y 分数做回归
Lighthouse 的可访问性审计是采样式、非确定性的:它只检查视口内可见元素,跳过 display: none 或 visibility: hidden 的节点,且不触发 focus 或 hover 状态下的交互逻辑。这意味着模态框焦点陷阱、ARIA-live 区域更新失败、键盘导航断点等真实问题大概率漏检。
更麻烦的是,Lighthouse 每次运行会随机化部分检测顺序,score 值浮动 ±3% 属常态;用它当回归阈值,CI 里频繁误报。
立即学习“前端免费学习笔记(深入)”;
文章转信息图。将文章/笔记转化为手机可读的 HTML 信息图,自动匹配视觉风格。触发场景:文章转图、笔记转图、信息图、转小红书图、做张图、可视化这篇文章、文生图。
- 别把
lighthouse --only-categories=accessibility当回归主干,它适合趋势观察,不适合断言 - 若硬要用,至少加
--preset=desktop锁定设备配置,并用--chrome-flags="--headless=new"统一 Chromium 版本 - 真正要回归的,是 axe 输出的
violations[].id+violations[].nodes[0].target组合,这两项才稳定可比
如何让可访问性测试跑进 CI 并不拖慢构建
axe 扫描单页耗时通常在 300–800ms,但加载完整页面(含 JS、字体、API)常超 5s。直接塞进 CI 会导致构建卡顿或超时,尤其在 GitHub Actions 这类资源受限环境。
- 用 Playwright 的
page.route()拦截非必要请求(如 analytics、ads、第三方 fonts),只留 HTML/CSS/JS 主资源 - 测试前注入
document.documentElement.setAttribute('data-axe-test', 'true'),再在 CSS 中写[data-axe-test] .user-avatar { display: none; },比 JS 动态移除更可靠 - 把测试拆成两级:第一级用静态 HTML 快速扫语义结构(
axe-core+ JSDOM),第二级用浏览器环境扫交互逻辑(Playwright + axe);前者进 pre-commit,后者进 CI
基线怎么维护才不会变成新债
可访问性回归测试最大的坑不是写不起来,而是基线越积越多、没人敢删、最后形同虚设。一个 .snap 文件里存着 17 条已知但未修复的 violation,新人根本分不清哪些该修、哪些是“暂时放过”。
真实项目里,基线必须带元信息:谁批准的、为什么豁免、预计修复时间、对应 Jira 编号。否则半年后你看到 color-contrast 违规还在 snapshot 里,连自己都忘了当初是因设计稿没给够色值才绕过的。
- 每个 snapshot 文件顶部加注释块,用
/* @axe-baseline: { "issue": "JIRA-123", "approvedBy": "design-team", "expires": "2026-10-01" } */ - CI 脚本里加校验:如果 snapshot 里有
expires已过期的条目,自动 fail 构建并提示负责人 - 拒绝接受“全页面扫描”生成的基线——必须按组件或路由粒度切分,比如
header.snap、product-list.snap,改一个不影响全部
基线不是存档,是活契约。它得能被质疑、被更新、被到期作废,否则很快就会变成没人敢碰的“幽灵债务”。


















