尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

无障碍测试自动化实战:基于axe-core与Playwright的合规门禁搭建

无障碍测试自动化实战:基于axe-core与Playwright的合规门禁搭建 1. 无障碍测试为什么值得投入自动化先说一个我自己的观察。做了多年测试和质量保障国内团队对无障碍这件事的态度多数停留在知道但不动的状态。直到有几次我做海外项目客户明确要求产品必须符合 WCAG 2.1 AA 标准否则不予验收团队才被迫正视这个问题。无障碍测试英文叫 Accessibility Testing圈内常简写为 a11y核心是验证产品对残障用户是否可用视障用户能靠屏幕阅读器读页面吗色盲用户能看清状态提示吗只靠键盘能完成整个购物流程吗这类测试本质上是规则校验WCAG 标准把问题拆成了非常明确的可检查项比如文字和背景的对比度不低于 4.5:1、所有图片必须有 alt 文本、所有交互元素必须能通过键盘聚焦。这些规则一旦落到代码层面就是可枚举、可断言、可自动化的校验点。所以我的结论是无障碍测试是整个测试体系里最适合交给自动化的类型之一。它不像 UI 视觉测试那样需要大量人工判断也不像探索性测试那样依赖人的创造力它就是一套明确规则下的扫描和验证。借助自动化工具做常态化扫描能保证每次版本迭代都不会带上旧的合规缺口也能在早期就拦截新增的无障碍问题。这篇内容适合谁看测试工程师、前端开发、质量团队负责人以及所有需要应付海外合规验收或希望把产品体验真正做扎实的团队。我会从方案设计讲起再到工具选型、完整实操最后是真正的坑和排查经验尽量让对方看完就能上手。2. 整体设计方案自动化在合规验证里的位置2.1 合规性验证到底涵盖哪些维度在搭自动化之前先要把无障碍合规拆开看不然容易做成只有对比度检查的假合规看起来报告好看实则没什么用。按我实际项目的经验无障碍合规通常落在三个维度视觉层颜色对比度、非文本对比度、焦点指示的可见性、文字缩放后布局是否可用。操作层仅用键盘能否走完所有流程、Tab 焦点顺序是否符合阅读逻辑、焦点是否会被困在弹窗里。语义层按钮有名字吗图片有替代文字吗表单字段有没有正确的 label 关联ARIA 属性使用是否正确屏幕阅读器能正确朗读内容结构吗自动化能高效覆盖视觉层和语义层因为这两类规则高度明确工具可以直接计算对比度、检查 HTML 属性。操作层里的一部分也能做比如焦点陷阱、Tab 顺序、点击区域大小都是可脚本化的。但有一个现实我必须强调WCAG 的检查项里真正能被自动化工具完全覆盖的比例通常在 30% 到 40% 之间。剩下的部分比如屏幕阅读器的实际听感、语音输入用户的操作流畅度、复杂交互组件的可用性判断目前仍需要人工介入。所以方案设计的第一原则就是不要企图用一套自动扫描替代所有合规验证而是用自动化做巡检防线把规则明确的部分全部自动化把人工精力集中在机器做不了的地方。这套思路和我们做功能测试时UI 自动化 探索性测试的打法完全一样。2.2 用扫描金字塔思路设计测试层级功能测试界有个著名的测试金字塔无障碍测试可以借用同样思路分层设计从底层到顶层分别覆盖不同类型的检查。我的分层方式是第一层静态代码与依赖层。在代码层面检查比如 lint 规则里开启 jsx-a11y 或类似插件开发阶段就能发现 alt 缺失、ARIA 乱用这类问题。速度最快成本最低问题在提交前就被拦截。第二层组件级渲染扫描层。把每个独立组件或页面渲染出来用工具做自动化扫描。这个层级主要跑批量检查适合做全量回归比如用 Lighthouse 的 accessibility 分数做页面级体检或者用 axe-core 直接扫组件。第三层端到端流程层。在真实用户流程中注入扫描比如在 Selenium 或 Playwright 脚本里完成登录、添加购物车、结算的完整操作后在关键节点调用无障碍扫描器做检查。这一层能发现动态交互产生的无障障问题比如弹窗打开后焦点是否被正确管理、数据加载后对比度是否变差。第四层人工辅助层。用屏幕阅读器NVDA、VoiceOver 键盘走查关键流程。这一层不做全量只做核心路径和复杂组件的人工验证。自动化的重点放在前三层。我见过一些团队只跑第三层忽略组件级扫描导致每个页面都要走完整流程才能发现基础问题效率极低。正确的做法是三层并行组件级扫描做全量覆盖E2E 扫描做动态交互保障两者互补。3. 工具选型哪些方案经得起实际项目检验3.1 主流无障碍自动化工具横向对比工具选型我踩过不少坑这里直接给一份实际对比。目前圈内主流的方案有 axe-core、pa11y、Lighthouse、WAVE 这么几类另外还有具备商业背书的工具如 Deque 的 axe DevTools但核心引擎还是 axe。axe-core 是目前事实上的标准引擎。它由 Deque 系统开发谷歌、微软等大厂以及很多开源组件库如 Angular Material、Adobe Spectrum都在用。它的优势是规则丰富、误报率低、社区活跃而且版本更新紧跟 WCAG 2.1/2.2。最重要的它能以 npm 包形式嵌入任意测试框架灵活性极高。pa11y 是另一个受认可的选择底层虽然也用了 axe-core 一部分规则但它自带命令行和 HTML 报告能力适合不想写复杂代码、只想要定期扫描的团队。它把扫描、生成报告做成了开箱即用这在快速试点阶段很有价值。Lighthouse 更适合做整体的性能和无障碍体检它的 accessibility 分数非常直观给管理层的汇报效果好但它的规则深度不如 axe-core定位是体检仪而非合规检查器。我做项目时通常用 Lighthouse 做宏观体检用 axe 做合规门禁。WAVE 是老牌工具浏览器插件方式使用方便适合教研或快速看到页面上问题的可视化标注但要自动化跑批就比较麻烦不适合接入 CI。这里我想特别说明选型逻辑这是一套工具链不是单选题。我们最终落地时选了 axe-core 做核心引擎配 Playwright 做流程注入Lighthouse CI 做整体评分三样搭配使用。3.2 为什么把 axe-core 作为主引擎我推荐所有团队优先考虑 axe-core理由有三点。第一规则覆盖面最广且权威。axe-core 的规则来自 WCAG 2.1、2.2 以及 WAI-ARIA 的实践规范并且它区分了规则等级serious、critical、moderate、minor。它还会把违反的 WCAG 成功标准比如 4.1.2 Name, Role, Value直接标注出来这对生成合规性审计报告极为重要因为在人行审查时需要追溯每一条问题对应的标准条款。第二多框架支持。axe-core 既可以独立跑Node 环境也可以嵌入 Selenium、Playwright、Cypress、WebdriverIO。这意味着只要团队现有的自动化框架不是特别偏门都能直接接入不需要额外维护一套独立测试平台。第三误报率相对低。我自己用下来的体感对比度类检查在复杂渐变背景上偶尔会有误报但大部分规则如 ARIA 属性使用错误、按钮名称空、图片 alt 缺失报出来就是板上钉钉的问题。误报少团队才愿意把它接进门禁流程否则三天两头被无效失败打断自动化很快就会被废弃。3.3 工具链之外的思路AI 辅助能做什么最近不少人聊 AI 自动化测试无障碍测试领域 AI 也有切入空间但要认清边界。现有的开源 AI 或视觉模型能做的事情主要有两类一是利用计算机视觉识别页面元素自动补充 OCR 文案校验但这对无障碍的语义规则帮助不大二是利用 LLM 分析报告文本把 axe 的原始 JSON 结果自动翻译成给开发团队的自然语言修复建议这一点在我们团队已经用起来了把结果丢给大模型它能直接给出修复代码建议开发修复效率提升明显。但要警惕一个误区AI 目前还不能替代标准引擎做规则判定。真正的合规判定逻辑是确定性的对比度是数学计算ARIA 属性合法性是枚举校验这些场景没必要用大模型精确无误的工具引擎更可靠。AI 适合做辅助解释、建议生成、报告汇总这类自然语言工作而不是替代确定性逻辑。4. 实操全流程从零搭建无障碍自动化合规测试4.1 环境准备与基础依赖这一节给出一套可以直接复制的完整流程。我以 Node.js Playwright axe-core/playwright 为主路线演示这是目前最稳的组合再附上 Python Selenium 的路线做参考。前提条件Node.js 16 以上版本npm 可用Python 路线需要 Python 3.8 以上和 Chrome 浏览器。初始化项目并安装依赖mkdir accessibility-compliance cd accessibility-compliance npm init -y npm install --save-dev playwright axe-core/playwright axe-core npx playwright install chromium如果走 Python 路线pip install selenium # 同时需要下载 axe-core将 axe.min.js 放到项目目录 # 官方源https://cdnjs.cloudflare.com/ajax/libs/axe-core/4.7.2/axe.min.js安装过程中最容易出问题的点是 npx playwright install chromium 需要联网下载浏览器内核在部分内网环境会失败需要配置镜像或手动指定浏览器路径。这个细节很多第一次用 Playwright 的同事会卡住提前知道可以省时间。4.2 编写端到端扫描脚本语义合规检查先演示 Playwright 的用法写一个最简单的页面扫描脚本import { chromium } from playwright; import AxeBuilder from axe-core/playwright; (async () { const browser await chromium.launch(); const page await browser.newPage(); await page.goto(https://your-site.com/login); const results await new AxeBuilder({ page }).analyze(); console.log(违规数量: ${results.violations.length}); results.violations.forEach(violation { console.log([${violation.impact}] ${violation.id} - ${violation.description}); violation.nodes.forEach(node { console.log( - ${node.target.join( )}); console.log( ${node.failureSummary}); }); }); await browser.close(); })();这段脚本做的事是打开页面注入 axe 扫描引擎把页面上所有违反无障碍规范的内容打印出来。对初学者来说这个脚本就是最小可用原型。你先跑通这个再谈集成 CI。Python Selenium 路线核心是通过 execute_script 注入 axe.min.js再执行扫描from selenium import webdriver import json driver webdriver.Chrome() driver.get(https://your-site.com/login) with open(axe.min.js, r, encodingutf-8) as f: axe_script f.read() driver.execute_script(axe_script) result driver.execute_script(return axe.run().then(r r);) violations json.loads(json.dumps(result[violations])) print(f违规数量: {len(violations)}) for violation in violations: print(f[{violation[impact]}] {violation[id]}) for node in violation[nodes]: print(f - {node[target]}) driver.quit()Selenium 注入方式的原理是axe.run() 在浏览器上下文里执行 DOM 扫描所有规则判断都在前端完成不需要后端服务所以只要能在页面里跑 JS就能用 axe。这意味着你现有测试框架如果已经基于 Selenium根本不需要推倒重来加一个注入步骤就行。4.3 在真实用户流程中做动态交互检查静态页面扫描只是基础。实际业务系统里很多无障碍问题是在交互发生后暴露的。比如点击按钮弹出 Dialog 后焦点没有移入弹窗表单校验失败的错误提示没有通过 aria-live 广播给屏幕阅读器异步加载内容后新元素的对比度不足。所以在 E2E 流程里嵌入扫描是必经之路。下面是用 Playwright 在登录流程后做合规检查的示例import { test, expect } from playwright/test; import AxeBuilder from axe-core/playwright; test(登录流程无严重无障碍违规, async ({ page }) { await page.goto(/login); await page.getByLabel(用户名).fill(tester); await page.getByLabel(密码).fill(password123); await page.getByRole(button, { name: 登录 }).click(); await page.waitForURL(/dashboard); const results await new AxeBuilder({ page }) .withRules([color-contrast, aria-valid-attr, button-name]) .analyze(); const seriousViolations results.violations.filter( v v.impact serious || v.impact critical ); expect(seriousViolations).toEqual([]); });这段代码里有个关键选择用 withRules 限定只检查 color-contrast、aria-valid-attr、button-name 三类高频规则。为什么强调这个因为全量规则的扫描在复杂页面上可能耗时数秒而且大量规则全部开启会导致一次性暴露几十个问题团队根本无从下手。实际项目里的做法是分层推进第一轮只设必须通过的核心规则严重等级过滤其余规则作为报告参考等团队把严重问题清零后再逐步加严。这和控制功能测试范围的思路完全一致先保证核心场景再逐步扩大覆盖。4.4 定义合规门禁怎样的结果算通过扫描出违规不等于要全部修复要根据影响等级和业务场景设定容忍度。这里透露我常用的标准critical 和 serious 等级的违规设定为门禁必须为零否则 CI 失败。moderate 和 minor 等级的违规允许存在但要在报告里记录并排期修复。对海外合规验收项目最终目标是对照 WCAG AA 标准所有违反项为零。对应到 Playwright 断言里就是 expect 只过滤 critical 和 seriousconst criticalOrSerious results.violations.filter( v v.impact critical || v.impact serious ); expect(criticalOrSerious).toEqual([]);如果要更精细可以在 include 里直接指定 WCAG 级别const results await new AxeBuilder({ page }) .withTags([wcag2a, wcag2aa, wcag21a, wcag21aa]) .analyze();withTags 这个方法非常实用它只保留对应 WCAG 版本和级别的规则。比如客户只要求符合 WCAG 2.1 AA那就指定 wcag21aa不要让它报一堆符合 AAA 的问题不然开发团队会一直处理无关任务最后把整个检查流程玩坏。4.5 接入 CI每次提交自动跑合规扫描自动化测试不接入 CI价值折损一半。合规性测试尤其如此因为它的价值在于持续回归而不是偶尔手动跑一次。给出一个 GitLab CI 的最小示例accessibility-test: stage: test image: mcr.microsoft.com/playwright:v1.40.0-focal script: - npm ci - npx playwright install --with-deps chromium - npm run test:accessibility artifacts: when: always paths: - test-results/ expire_in: 14 days核心逻辑并不复杂拉取包含 Playwright 的镜像安装依赖跑测试脚本然后把报告作为构建产物保留下来。如果门禁失败MR 就无法合并如果门禁通过报告也能让任何人在流水线记录里回溯。这里有个实践教训全量页面扫描全部串行跑的话一个中型系统的耗时可能超过十分钟。建议做三件事一是用 Playwright 的 shard 功能做并行分片二是只对核心页面做全量规则扫描非核心页面做抽样三是在 nightly 任务里跑全量merge 前的校验跑核心路径即可。这样既保证反馈速度又确保覆盖深度。5. 报告生成与合规审计追溯5.1 从 JSON 到可读报告axe 扫描返回的是 JSON 结构里面每个 violation 包含 id、impact、description、helpUrl、nodes 数组以及 nodes 里的 target 和 failureSummary。原始数据信息很全但不直观给开发看还好给项目干系人看就会一头雾水。我们团队的做法有三类输出控制台精简输出开发自测时用、HTML 报告附到 MR 里给团队看、审计归档报告给客户和合规验收用。Playwright 生态里可以直接把结果渲染成 HTML。简单实现方式是把 JSON 结果转存后用模板渲染我提供一个最小实现思路import { writeFileSync } from fs; const htmlReport !DOCTYPE html html headtitleAccessibility Report/title/head body h1无障碍合规扫描报告/h1 p违规总数: ${results.violations.length}/p ${results.violations.map(v div styleborder:1px solid #ddd; padding:12px; margin:8px 0; h3[${v.impact}] ${v.id}/h3 p${v.description}/p a href${v.helpUrl} target_blank规则说明/a ul ${v.nodes.map(n li${n.target.join( )}/li).join()} /ul /div ).join()} /body /html; writeFileSync(test-results/accessibility-report.html, htmlReport);如果想省事也可以用 pa11y 的 HTML 报告作为兜底。但我们实际用的时候发现 pa11y 报告对动态流程支持不如 Playwright 灵活所以最终还是自渲染了简单模板。要花心思的是报告中给每个问题添加上下文信息比如是哪个页面、哪个操作步骤后发现的这样开发才能在定位时少走弯路。5.2 用基线模式管理存量问题存量系统接入无障碍合规检查时第一次扫描往往会冒出一大堆历史违规。此时如果把门禁直接设为零违规团队大概率会造反因为马上要干大量脏活。推荐基线模式第一次全量扫描后把当前所有违规明细存成基线文件后续 CI 里只对新增违规做门禁拦截。这样存量问题被显性记录但不在合并流程里强制阻断团队可以排计划逐步消化。基线对比逻辑不复杂const baseline JSON.parse(readFileSync(accessibility-baseline.json, utf-8)); const baselineKeys new Set( baseline.violations.flatMap(v v.nodes.map(n ${v.id}::${n.target.join( )}) ) ); const newViolations results.violations.filter(v v.nodes.some(n !baselineKeys.has(${v.id}::${n.target.join( )})) ); expect(newViolations).toEqual([]);这种做法在测试领域叫做 allowlist 或 baseline。它的好处是让团队立刻获得可持续的 CI 门禁而不是把所有问题都推给一次技术债清理。新功能引入问题会被拦截存量问题也不会被遗忘因为基线文件会在每次扫描时同步更新违规数只能降不能升。5.3 自动化结果如何驱动团队修复报告有了怎么让团队真的去修我总结了一个行之有效的组合拳。首先在失败信息里写清楚规则的含义和修复代码示例。axe 返回的 helpUrl 直接指向官方文档测试失败信息里要带上它并尽量附加节点定位信息。开发打开失败日志就能定位页面元素而不是反手把测试跳过。其次把合规修复纳入迭代排期。每周选 5 到 10 个违规项集中修修完标记基线。我见过最有效的节奏是每两周一次的自动化回归扫描 每次迭代明确无障碍技术债消化任务三个月基本能把中等规模系统的严重问题清完。最后用趋势数据说话。把每次扫描的 critical/serious 数量画成趋势图管理层看到数字在降就会持续投入资源。这一点对争取团队时间非常有用权重很高别只在开发内部打转要把合规性进展变成业务语言向上汇报。6. 常见问题与排查技巧实录6.1 动态内容和 SPA 页面导致扫描结果不一致SPA 场景里页面标题不变但 DOM 已经换过好几轮扫描结果容易和期望不符。处理技巧在扫描前等待页面稳定最简单的方式是等待某个标志性元素出现或网络请求完成。Playwright 的自动等待已经比较智能但针对接口返回后渲染的模块建议显式等待await page.waitForResponse(resp resp.url().includes(/api/user/info) resp.status() 200 ); await page.waitForLoadState(networkidle);还有一种情况是弹窗、抽屉这类临时容器。如果要对弹窗单独扫描直接用 Playwright 的 locator 定位弹窗根节点再用 axe 的 include 参数指定范围const modal page.locator([roledialog]); const results await new AxeBuilder({ page }) .include(modal) .analyze();include 的本质是把扫描范围限定到指定元素及其子树。很多人不知道这个能力导致要扫某个特定组件时总是连带扫出全页面的问题噪音很大用了 include 之后结果干净非常多。6.2 Shadow DOM 和 iframe 里的问题扫不到现代前端框架大量使用 Shadow DOMsometimes iframe 嵌套也很深。axe-core 对常规 Shadow DOM 是支持的但默认配置在 iframe 场景下不一定完整。解决办法是开启 iframe 选项const results await new AxeBuilder({ page }) .withOptions({ iframes: true }) .analyze();如果是跨域 iframescan 会受同源策略限制扫不进去。此时只能单独去访问那个 iframe 页面做独立扫描或者在设置里加入跨域处理逻辑。这里有个坑要提醒axe 对 Shadow DOM 的支持限定了 open 模式如果是 closed 模式比如某些组件库内部封装工具扫不到。遇到这种情况排查思路不是硬刚扫描器而是回到组件库层面做单测或者要求组件库保留可访问性接口。这是工具边界的真实体现项目落地前要和团队明确这些边界不要等到上线前才发现有盲区。6.3 对比度和焦点管理的自动化盲区颜色对比度检查看起来简单实际复杂场景很多。渐变背景、半透明遮罩、背景图片混排都会导致 axe 计算与实际视觉呈现不一致。比如一个按钮在渐变背景中部axe 取到的只是背景色采样可能算出 4.6:1但视觉上按钮下方背景恰好是低对比度区域实际感知对比度并不过关。对此我的补充建议是自动化跑基础对比度检查对高风险区域如电商的价格标签、表单错误提示单独做人工视觉验证也可以引入 playwright 的视觉回归截图对比。重点区域宁可多花人工时间也不要指望全自动工具能覆盖所有视觉场景。焦点管理是另一个自动化覆盖不全的地方。axe 能判断某个元素是否可聚焦但按 Tab 后焦点是否按设计顺序移动这类行为需要手动模拟按键操作await page.keyboard.press(Tab); await expect(page.locator(:focus)).toHaveAttribute(id, expected-element);这是一个很朴实但有效的焦点顺序验证方式。它确实需要在用例里写死预期的 Tab 顺序维护成本偏高所以我们通常只对核心用户路径做焦点顺序断言其他路径交给人工抽查。6.4 不要忽视真实屏幕阅读器验证这是我最想强调的经验。无论自动化工具覆盖得多全面都无法替代真实的屏幕阅读器体验。我在多个项目里发现某些和实际产品体验相关的问题自动化工具完全发现不了比如一个按钮通过 ARIA 设置名称后工具认为有可访问名但 NVDA 实际朗读时的语境和语义组合不佳用户听着很困惑。建议每个迭代至少做一次屏幕阅读器人工走查固定 3 到 5 个核心任务比如在首页完成一次搜索并读取结果列表、填写一个三字段表单并提交、弹出 Dialog 并成功关闭。Windows 上推荐 NVDAmacOS 上直接用 VoiceOver。走查时要求测试人员记录实际听到的语音反馈和预期文案做对照。自动化合规测试 屏幕阅读器人工走查两者结合才是完整的无障碍质量保障体系。缺了任何一半都有可能在关键验收点翻车。7. 关于落地节奏的最后经验回到具体项目的落地路径我建议按四个阶段推进。第一个阶段是摸底。选 10 个核心页面用 axe 全量扫描一次把违规数量、类型、分布统计出来。这个过程通常一天就能完成目的是让所有人知道现状有多差、问题集中在哪里。第二个阶段是架流水线。把核心页面的扫描脚本集成到 CI设定基线模式先做到新增违规不流入同时把报告自动发到团队群。这个阶段一到两周可以完成团队要的是持续提醒而不是一次惊吓。第三个阶段是清存量。按严重程度排序每轮迭代消化 5 到 10 个问题。修完就更新基线让数字往下降瑕疵越来越少。这个阶段通常是两到三个月取决于存量问题的多少和团队投入。第四个阶段是扩覆盖。从核心页面扩展到所有业务页面从纯扫描扩展到 E2E 流程注入扫描从 UI 自动化扩展到包含触发性交互的复合扫描。Form 完流程之后再加组件库层面的静态检查形成从开发到验收的闭环。我个人的体会是无障碍自动化测试的落地难点从来不在技术本身。扫一下页面、写几个断言这个门槛很低。真正难的是让团队相信这件事值得做并且愿意把修无障碍问题当作正常开发任务来排期。合规验收的压力、真实用户反馈的推动、领导层的理解都可能成为启动的契机。作为质量从业者我们能做的就是把这个领域的专业方案准备好随时可以把合规性测试体系搭起来。
返回列表