QualiBooth

development

CI/CD 中的自动化无障碍测试:实用指南

将无障碍左移:在每个 pull request 上运行自动化 WCAG 检查、设置构建门禁与阈值、建立基线,并集成到 GitHub Actions、GitLab CI、Jenkins 等 CI/CD 流水线。

2 min read QualiBooth
一条 CI/CD 流水线,配有自动化无障碍门禁,在每个 pull request 合并到主分支之前进行检查。

最便宜的无障碍缺陷是那个永远到不了你主分支的缺陷。等到一次定期审计暴露出缺失的表单标签或被破坏的焦点顺序时,问题代码早已发布,又有另外三个功能在它之上构建,并且可能已经让一位真实用户感到沮丧。修复更难,回归风险更高,而成本——在工程时间和信任两方面——已经成倍增加。

CI/CD 中的自动化无障碍测试颠覆了这种经济学。你不再是在几周或几个月后于下游发现问题,而是在可自动化的问题被引入的那一刻就捕获它们——就在引入它们的那个 pull request 上。本文是一份面向工程团队的实用指南:如何将无障碍左移、在流水线的何处放置检查、如何在不用噪音淹没开发者的情况下为构建设门禁、如何与主流 CI 系统集成,以及——至关重要的——自动化在何处止步而人工测试必须接手。

为什么要将无障碍左移

“左移”意味着将质量检查移到开发生命周期的更早阶段,更接近代码被编写的那一刻。这一原则在安全和功能测试方面已被充分理解,而无障碍出于完全相同的原因从中受益。

当无障碍被当作后期阶段的审计活动来对待时,会出三件坏事。第一,缺陷累积:一次发布前的审计会产生令人望而生畏的积压,团队会将其与交付压力相权衡——无障碍通常会落败。第二,上下文丢失;三个 sprint 前引入了一个无标签图标按钮的开发者已经转去做别的了,重建当初的意图很慢。第三,同一类问题随着每个新功能反复出现,因为日常工作流中没有任何东西能阻止它们。

将检查放入 CI/CD 闭合了这个循环。反馈在代码尚新、作者仍在上下文中时到达。回归在累积之前就被阻止。而无障碍成为一个正常的、自动化的质量门禁——就像单元测试、类型检查和代码检查(linting)一样——而不是一个发生在别人身上的特殊事件。如果你想了解这些检查所处位置的更宏观图景,我们关于软件开发生命周期中的无障碍的概述描绘了从设计到发布的每个阶段。

这里也需要一个清醒的预期。左移并不意味着把一切都左移。自动化处理 WCAG 2.2 合规中一个特定的、有价值的切片。其余部分仍然需要人。我们会详细回到这条边界。

在每个 pull request 上检查

运行无障碍检查杠杆最高的地方就是 pull request。这里审阅者本来就在看,这里 diff 很小且可审阅,而且这里阻止是社会上可接受的,因为没有人指望一个未完成的分支是完美的。

一套良好的 PR 级配置具有三个特性:

  • 快速。 PR 检查与开发者的注意力时长相竞争。把它们限定在改动的范围内——diff 触及的页面或组件——而不是每次推送都爬取整个站点。全站扫描应放在计划任务里,而不是每次提交时。
  • 内联。 发现应出现在开发者工作的地方:作为 PR 上的评论、改动文件上的注释,或带有详情链接的状态检查。一个埋在没人打开的 CI 日志里的结果,是一个没人会去处理的结果。
  • 可操作。 每个发现都需要它违反的规则、它找到的元素、它映射到的 WCAG 成功标准,以及理想情况下的修复提示。“axe-core 规则 button-name:此 <button> 没有可访问的名称”是有用的;“无障碍错误”则不是。

QualiBooth 的扫描器正是为以这种模式运行而构建的——通过 CLI 或 API 从你的流水线中调用,将发现回报到 pull request 上,并在仪表板中跟踪它们,使团队能够看到无障碍债务随时间趋于下降。在不同平台上设置这一切的具体机制,我们的 CI/CD 无障碍集成服务有所涵盖。

构建门禁与阈值

报告发现是必要的但不充分。一份什么都不阻止的报告,在截止日期压力下会被忽略。一个门禁——一个能让构建失败的检查——才是让无障碍在流水线中有牙齿的东西。艺术在于选择对什么设门禁。

天真的做法是任何无障碍违规都让构建失败。在全新项目上这可以奏效。在有一堆已知问题积压的现有代码库上,这是一场灾难:第一次运行就失败,从此每次构建永远失败,团队会在一天之内禁用这个检查。门禁必须被校准。

一套可行的阈值策略是这样的:

  • 只对新的、严重的回归设门禁。 将当前扫描与基线(下一节涵盖)进行比较。当 diff 引入达到或超过你所选严重级别的违规时——例如严重和重大——让构建失败,而让较低严重级别或已有的问题以警告形式通过。
  • 区分严重级别。 并非所有违规都相同。一个彻底的键盘陷阱值得硬性失败;一条次要的最佳实践提示也许只是信息性的。将规则的影响级别映射到门禁行为,使门禁反映真实的用户损害。
  • 有意地允许范围受限的例外。 有时一个已知问题被跟踪并已排期。支持一种明确的、可审阅的抑制机制——带注释且有时限——而不是让开发者一刀切地禁用整个检查。

目标是一个团队信任的门禁。一个因正当理由而失败的门禁会受到尊重;一个因噪音而失败的门禁会被绕开。将阈值调校到适合你的代码库,是建立这种信任的一部分,也是无障碍流程改进的核心部分。

为现有问题建立基线

几乎没有哪个真实代码库是从零个无障碍缺陷起步的。实际的问题不是”我们怎样才能没有问题?“,而是”我们如何在偿还旧问题的同时停止添加新问题?“。基线就是答案。

基线是你开启门禁时已经存在的无障碍问题的一份记录快照。此后每次扫描都与它比较。门禁针对相对于基线而言的内容失败;现有的积压被承认但不阻止构建。这让你能立即开启强制执行,而不必停下开发。

有几项实践能保持基线健康:

  • 让基线成为受跟踪的产物。 把它提交到仓库,或存储在你的无障碍平台中,使对它的更改可见、可审阅,而非悄无声息。
  • 只让它缩小。 基线应随着问题被修复而逐级下降,绝不应增长以吸纳新违规。如果一次修复消除了一个问题,就重新生成基线,这样以后重新引入该问题时门禁就会失败。
  • 安排有意的偿还。 基线中捕获的积压不会自行消失。将门禁与一个消化它的计划配对——sprint 分配、一个专门的清理 epic,或一种循环的审计节奏。我们关于循环无障碍审计的讲解描述了如何构建这项持续的工作。

基线正是让”今天就开启门禁”对一个已经发布多年的团队来说变得现实的东西。

组件与 Storybook 测试

针对已渲染页面的 PR 检查很有价值,但它们捕获问题较晚——在一个有缺陷的组件已经被组合进页面之后。在组件层面测试则在源头捕获它们,赶在单个可访问名称缺陷扩散到四十个屏幕之前。

如果你的团队使用像 Storybook 这样的组件浏览器,它就是为此而设的理想载体。每个 story 在隔离状态下、以其各种状态渲染一个组件,这正是自动化无障碍引擎所需要的:一个确定性的、聚焦的 DOM 供其评估。

一套典型的组件测试配置:

  • 在每个 story 上运行无障碍检查。 像 Storybook a11y 插件(基于 axe-core 构建)这样的工具可以自动扫描每个 story,而同样的检查可以在 CI 中无头运行,从而让组件违规使流水线失败,而不仅仅是本地 UI。
  • 覆盖各种状态,而不只是默认状态。 渲染并测试禁用状态、错误状态、加载状态、打开和关闭状态。无障碍缺陷偏爱边缘状态——一条没有程序化关联的错误消息,一个不困住焦点的模态框。
  • 修一次,处处受益。 一个正确构建、经过测试的组件成为一项可复用的保证。每个使用它的页面都继承这次修复。这是杠杆最高的投资之处,并且它自然地与你团队已经在用的更广泛的无障碍工具包无障碍扫描软件相配合。

组件测试不能取代页面级测试——组合会引入任何孤立组件都无法揭示的问题,比如重复的 landmark 区域或被破坏的整体标题大纲——但它极大地减少了到底有多少缺陷会到达页面。

与你的 CI 系统集成

集成模式在各平台上都是相同的:安装或调用扫描器,针对目标(一个 URL、一个构建产物,或组件 story)运行它,并将其退出码和报告转换为流水线的通过/失败以及一个对开发者可见的产物。由于 QualiBooth 通过 CLI 和 API 集成,它几乎适配任何系统。以下是主流系统在实践中的差异。

GitHub Actions

最常见的配置。添加一个由 pull_request 触发的工作流,启动你的应用(或部署一个预览),针对它运行无障碍 CLI,并将结果作为 check run 或 PR 评论发布。GitHub Actions 让内联注释和必需的状态检查变得简单直接,因此一个失败的无障碍门禁可以通过分支保护规则阻止合并。缓存浏览器二进制文件和依赖项能让作业保持快速。

GitLab CI

.gitlab-ci.yml 中定义一个 accessibility 作业,通常放在构建之后的一个专用阶段。GitLab 可以在 merge request 小部件中呈现结果,你也可以将 JSON 报告存为作业产物以供下载和趋势跟踪。Merge request 审批规则让你能将门禁设为阻塞性的。

Jenkins

Jenkinsfile 中添加一个运行扫描器并归档报告的阶段。Jenkins 在企业和本地部署环境中很常见,在这些环境中能在防火墙后运行一切很重要。使用合适的 publisher 插件来呈现结果,并在退出码非零时让该阶段失败以阻止构建。

CircleCI

.circleci/config.yml 添加一个作业,使用一个可用浏览器的执行器,并用 store_artifacts 存储报告。CircleCI 的 workflow 让你能将无障碍作业与其他检查并行运行,使其不会延长流水线总时间,而且你可以要求它在部署作业运行之前先通过。

Azure DevOps

向你的 YAML 流水线添加一个运行 CLI 的任务,然后用发布产物任务发布报告。Azure DevOps 的分支策略可以要求无障碍检查在 pull request 完成之前先通过,从而为你提供与其他平台相同的硬性门禁。

无论你使用哪个系统,正确的范围划定策略都是一致的:在 pull request 上做快速的、限定于改动范围的扫描;在每夜或发布前的计划任务里做更完整的爬取。我们作为 CI/CD 无障碍集成的一部分,帮助团队端到端地搭建这一切,并为更愿意自己实现的平台团队提供建议。

减少误报

没有什么能比误报更快地摧毁团队对无障碍门禁的信任。如果检查标记出非问题,开发者就会学会忽略它、整体抑制它或绕过它——门禁就成了表演。让信号保持高质量不是可选项;它正是让整项努力得以持久的东西。

自动化引擎在设计上是保守的,有时会报告一些在上下文中并非真正失败的东西。常见的噪音来源:

  • 隐藏或尚未渲染的内容。 关闭菜单背后或懒加载区段中的元素可能被脱离上下文地标记。请扫描实际已渲染、已交互的状态。
  • 引擎误读的自定义组件。 一个正确实现、带有恰当 ARIA 的自定义控件仍可能触发某条通用规则。这些值得一个经过审阅、有文档记录的例外——而不是一刀切的禁用。
  • 动态时序。 在应用完成水合(hydrate)之前扫描会产生幻影式的失败。请等待一个稳定状态再进行评估。
  • 第三方嵌入。 在你无法控制的 iframe 内部的问题应单独跟踪,这样你的门禁衡量的才是你的质量。

实用的防御手段是:将规则集调校到适合你的技术栈、把抑制范围划得既窄又可审阅、扫描真实的状态,以及只对代表真正用户损害的严重级别设门禁。为某个特定代码库把这套校准做对,正是我们的无障碍咨询所涵盖的那类工作。

诚实的界限:自动化只能捕获 WCAG 的一部分

这是每个工程团队都需要内化、而我们永远不会模糊的界限:自动化测试可靠地检测到的只有约 30–40% 的 WCAG 成功标准。其余 60–70% 需要人的判断,再多的流水线工程也改变不了这一点。

原因是结构性的。自动化擅长机器可核查的事实:这张图片上有替代文本吗?这段文字达到对比度比例了吗?这个表单字段有程序化的标签吗?标题标记存在吗?这些是真实而重要的检查,在每个 PR 上自动捕获它们确实有价值。

但是相当多的 WCAG 要求是语义性体验性的,机器无法评估它们:

  • 替代文本有意义吗,还是 "image123.jpg"?扫描器确认替代文本存在;只有人才能判断它是否传达了正确的信息。
  • 对于用键盘导航的人来说,焦点顺序说得通吗,还是技术上存在却不合逻辑?
  • 这个页面用屏幕阅读器真的可用吗,从头到尾,能完成一项真实的任务吗?
  • 错误消息能帮助一个困惑的用户恢复吗,还是仅仅在标记中被正确关联了?
  • 内容可理解吗,语言清晰吗,交互可预测吗?

这些都是关于人类体验的问题,它们由人工测试来回答——理想情况下由残障人士进行的审计来完成,他们每天使用辅助技术,能发现任何自动化工具和任何视力正常的开发者永远都不会注意到的问题。一次彻底的人工无障碍审计仍然是真正合规的基础。

所以正确的心智模型是分层的,而不是非此即彼:

  1. CI/CD 自动化让机器可核查的问题永不发布,并防止回归——持续地、低成本地、在每次变更时。
  2. 人工与辅助技术测试覆盖自动化无法触及的、占多数的体验性 WCAG。
  3. **循环审计**随着产品演进重新核验整体全貌,因为合规是一个移动的目标,而不是一次性的证书。

这种分层也正是现实世界的法规体系所期望的。无论你的义务来自欧洲无障碍法案(European Accessibility Act)ADA 还是 Section 508,合规都是对照完整标准来衡量的——而不是对照扫描器碰巧覆盖的那个切片。一条绿色的流水线是必要的,但不充分。

还有一件事要说清楚:无障碍叠加层(overlay)——那些承诺即时合规的 JavaScript 小部件——不能替代上面任何一层,QualiBooth 也不为它们背书。它们不修复底层代码,经常干扰用户所依赖的那些辅助技术,并且对最重要的体验性标准毫无作为。真正的无障碍来自于将它构建进产品本身,而这恰恰是 CI/CD 集成加上人工测试所交付的。

把它们组合起来

一条成熟的无障碍流水线不是一个工具或一条规则——它是一组各司其职的分层:

  • 组件级检查(例如在 Storybook 中)在源头捕获缺陷。
  • PR 级检查在每次变更上给出快速、内联、可操作的反馈,限定于 diff。
  • 带基线的构建门禁阻止新的严重回归,同时不停下针对遗留问题的工作。
  • 计划的全量扫描捕获组合层面的问题,并随时间跟踪整个代码库。
  • 趋势仪表板把原始的 CI 输出变成关于债务与进展的清晰图景。
  • 人工审计覆盖自动化在结构上无法企及的那 60–70% 的 WCAG。

从小处着手。在最重要的页面或组件上添加一个 PR 检查,为现有问题建立基线以使门禁在第一天就是绿色的,然后从那里逐级推进。目标是一个工作流,在其中无障碍回归变得像失败的单元测试一样难以合并,而自动化无法捕获的问题被引导给能够处理它们的人。

如果你想要帮助来设计或实现那条流水线,我们的 CI/CD 无障碍集成服务做的正是这件事——你可以在免费扫描在线演示中看到它背后的扫描引擎。

常见问题

自动化无障碍测试能取代人工审计吗?

不能,任何声称相反的供应商都在误导你。自动化检查可靠地捕获的只有约 30–40% 的 WCAG 成功标准——那些机器可核查的。占多数的体验性部分,比如替代文本是否有意义,或屏幕阅读器用户能否完成一项任务,需要人工测试。CI/CD 自动化防止回归并及早捕获简单问题;它自身并不能证明合规。

无障碍检查不会拖慢我们的构建吗?

如果范围划定得当就不会。在 pull request 上运行快速的、限定于改动范围的扫描,把全站爬取留给每夜或发布前的计划任务。无障碍作业也可以与你的其他 CI 检查并行运行,因此对流水线总时间增加甚微。缓存浏览器二进制文件和依赖项能让每次运行的成本保持低位。

我们如何避免门禁因现有积压而失败?

为它建立基线。记录一份你开启门禁时存在的问题的快照,并将门禁配置为只对相对于该基线而言的违规失败。你现有的积压被承认并跟踪,但不阻止构建,因此你可以立即启用强制执行,并按一个有意的计划偿还积压。

这能与哪些 CI 系统集成?

常见的那些——GitHub Actions、GitLab CI、Jenkins、CircleCI 和 Azure DevOps——以及实际上任何其他系统,因为 QualiBooth 通过 CLI 和 API 集成。模式处处相同:运行扫描器,将其退出码转换为通过/失败,并在开发者会看到的地方呈现报告。

我们应该从哪里开始?

在你流量最高的页面或共享组件库上添加一个 PR 级检查,为当前问题建立基线,只对新的严重回归设门禁,然后从那里扩展。从一开始就把它与一个人工测试计划配对,因为自动化只覆盖标准的一部分。如果你宁愿不独自构建它,可以咨询专家关于在你的流水线中实现它,或在我们的价格页面上比较各种选项。

将无障碍接入你的流水线