axe.run()必须在dom真正就绪后调用,显式配置shadowdom:true和iframes:true,否则spa和web component项目90%会漏检;react中需waitfor或act确保渲染完成,cypress中应先断言元素可见再执行检查。

可访问性审计不是“跑个工具打个分”就完事,而是必须分层嵌入开发流程:键盘导航和屏幕阅读器测试不可跳过,axe.run() 必须等 DOM 真正就绪,且默认不扫 Shadow DOM 和 iframe —— 漏掉这两点,90% 的 SPA 和 Web Component 项目会误判为“无障碍达标”。
axe.run() 怎么调才不会漏检
直接在组件挂载后立刻调用 axe.run(),大概率返回空结果或只扫到骨架屏。它不等异步渲染、不进 Shadow Root、也不递归 iframe。
- React 测试中必须用
waitFor(() => expect(screen.getByRole('button')).toBeInTheDocument())或act()确保状态更新完成,再调axe.run() - Cypress 中优先写
cy.get('[data-testid="main-content"]').should('be.visible'),再执行cy.checkA11y(),而不是依赖cy.visit()后立即扫描 - 含
<slot></slot>或 Lit/Stencil 组件的页面,必须显式传参{ shadowDom: true },否则内部结构完全不可见 - 有 iframe 的管理后台(如嵌入第三方报表),得用
frame.contentFrame().evaluate(() => axe.run(document.body))单独扫子上下文
为什么 Lighthouse 的 a11y 分数不能当交付依据
Lighthouse 的可访问性审计只覆盖 57 项 WCAG 检查点,且默认不运行手动验证项(比如焦点是否可逃出模态框、动态内容是否通知屏幕阅读器)。它给出的 92 分,可能对应一个键盘用户根本无法关闭的弹窗。
- 它不检测焦点陷阱:
document.activeElement被锁在 modal 内、Escape键无响应、关闭后焦点未回归触发点 —— 全部不报错 - 它不校验 ARIA 实时性:
aria-live="polite"区域更新后,屏幕阅读器是否真读出了新内容?Lighthouse 不听,只看属性是否存在 - 它默认忽略
iframe内容,而很多表单、富文本编辑器、地图都以 iframe 形式加载 - CI 中跑
lighthouse --quiet --chrome-flags="--headless=new" ... --output=json --output-path=report.json --view,得到的是静态快照,不是交互流
手动审计绕不开的三个真实场景
自动化工具扫不到的地方,恰恰是残障用户卡住最多的位置。这些必须人来走一遍:
-
键盘导航全程不碰鼠标:用
Tab/Shift+Tab走完整个流程,重点看:焦点是否跳过按钮、是否陷入轮播图、关闭弹窗后焦点是否回到原button;Enter和Space是否对所有可点击元素行为一致 -
VoiceOver / NVDA 开着听完整页:不看屏幕,只靠语音判断:标题层级是否断裂(
h1→h3跳过h2)、img的alt是空字符串还是堆砌关键词、form中每个input是否被label显式关联(for/id或包裹) -
色觉模拟 + 缩放 400%:在 Chrome DevTools 的 Rendering 面板开启
Emulate vision deficiencies,同时把页面缩放到 400%,检查:文字是否重叠、按钮是否消失、对比度是否跌破 4.5:1(尤其灰色小字)
CI 中集成 axe 的关键配置陷阱
本地能过 ≠ CI 能过。JSDOM 缺少浏览器上下文、路径解析错误、HTML 文件未生成,都会让 axe.run() 返回空数组或抛错。
- Jest 环境下,必须手动把
axe注入全局:global.window.axe = axe,否则require('axe-core')加载失败 - 不要用
fs.readFileSync('./dist/index.html')直接读文件 ——axe-core在 Node 环境无法解析 HTML 字符串,得用 Puppeteer 启服务或加载 file:// 协议 URL -
runOnly别乱设成['color-contrast']这种单点规则,合规底线是{ runOnly: { type: 'tag', values: ['wcag2aa'] } },否则审计报告法律无效 - 排除第三方区域要用
exclude: ['#ckeditor-container', '.ad-banner'],别用include反向指定,容易漏掉新增组件
最常被忽略的,是动态内容通知机制 —— 表单提交后显示 success toast,如果没加 aria-live 或没用 role="status",屏幕阅读器用户根本不知道操作成功了。这种问题既不在 axe 报告里,也逃得过 Lighthouse,只能靠人听着确认。
前端入门到VUE实战笔记:立即使用
在学习笔记中,你将探索 前端 的入门与实战技巧!











