axe-core必须在Playwright/Puppeteer中运行,因其依赖真实浏览器的computed styles、焦点管理、ARIA实时状态和无障碍树,而JSDOM缺乏这些能力,无法准确执行WCAG规则校验。

axe-core 为什么必须在 Playwright/Puppeteer 里跑,不能只用 JSDOM
因为 axe-core 的核心检测依赖真实浏览器的 computed styles、焦点管理、ARIA 实时状态和无障碍树(Accessibility Tree)——JSDOM 没有这些。它连 getComputedStyle 都返回空对象,更别说判断 color-contrast 或 heading-order 是否合规。
常见错误现象:本地用 JSDOM + axe.run() 全绿,CI 里却报一堆“missing alt”“empty heading”,实际是 DOM 渲染未完成或样式未加载,axe 看到的是空壳结构。
- React/Vue 应用务必等 hydration 完成再扫描:
await page.waitForFunction(() => document.querySelector('#root')?.children.length > 0) - 自定义字体未就绪会导致
color-contrast误报,加await page.waitForFunction(() => document.fonts.check('1em "Inter"')) - 不要用
page.accessibility.snapshot()替代 axe —— 它只返回基础属性,不执行 WCAG 规则校验
GitHub Actions 中 axe-core 扫描失败但流水线没中断?检查这三处
axe-core 默认不自动 fail,它只返回 JSON 结果;你得自己判断 results.violations.length 并 throw 错误,否则 CI 就当一切正常。
典型静默失败场景:脚本里写了 npx axe-playwright --ci src/index.html,但没配 --fail-on-violation,或者用了 || true 吞掉退出码。
立即学习“前端免费学习笔记(深入)”;
将调研报告、方案对比、数据分析、需求梳理、会议纪要、设计说明、代码评审、学习笔记等任务产物输出为可在浏览器直接打开的单文件 HTML,替代 Markdown 作为最终交付物。Markdown 在信息密度、视觉清晰度和可分享性上已跟不上 AI 产物的丰富度,HTML 能同时承载排版、表格、SVG 等。
- 用官方封装工具时,必须显式加
--fail-on-violation参数(如npx axe-playwright --ci --fail-on-violation src/index.html) - 自己写
page.evaluate(() => axe.run())时,别忘了加断言:expect(results.violations).toHaveLength(0) - 确保
axe-core脚本已通过page.addScriptTag({ path: require.resolve('axe-core') })注入,否则window.axe是 undefined
HTMLHint 和 axe-core 在 CI 里怎么分工才不重复不漏检
HTMLHint 查源码结构,axe-core 查运行时 DOM —— 它们不是替代关系,是前后两道防线。
比如一个 <img> 标签:HTMLHint 能立刻发现缺 alt 属性(模板级问题),而 axe-core 能发现 JS 动态插入的 <img src="avatar.jpg"> 没补 alt(运行时缺陷)。
- HTMLHint 放 pre-commit 或 lint 阶段,规则如
"alt-require": true和"input-requires-label": true必须显式启用 - axe-core 放 E2E 测试后、部署前,扫描已启动的服务(如
http://localhost:3000) - CI 报告要分开:HTMLHint 失败直接阻断构建;axe-core 发现 violation 建议标记为 “需人工确认”,因部分结果依赖上下文(例如某张图是否真为装饰性)
CI 里可访问性分数总比本地低?优先排查环境差异
不是代码退化,而是 CI 环境下 DOM 渲染链更短、资源加载更不可控。axe 看到的,往往不是你本地调试时那个“完整页面”。
最常被忽略的三个点:字体未加载、CSS-in-JS 未注入、第三方 script(如 analytics)阻塞 DOM 就绪。
- 加
page.waitForLoadState('networkidle')比page.waitForTimeout(2000)更可靠 - 禁用非必要第三方脚本:
await page.route('**/analytics.js', route => route.abort()) - 若用 Tailwind 或 CSS-in-JS,确保构建产物中 class 名已生成,且未被 purge 掉关键无障碍相关 class(如
sr-only)


















