关于 ci-cd
面向维护无障碍自动化检查的 CI/CD 技能,说明如何按用户任务和状态分层测试,并将自动化结果作为人工评估的证据,而非合规结论。内容还涉及检查规则、风险处理和工作流安全。
主要功能
- 01按层次组合静态、组件及浏览器测试
- 02在交互后的关键状态运行 axe-core
- 03使用语义断言和 ARIA 快照
- 04区分已确认缺陷与风险指标
- 05制定 CI 门禁和工作流安全控制
适用场景
- 01为 Pull Request 设计无障碍检查
- 02覆盖主题、视口和交互状态的回归测试
- 03审核自动化检查的阻断与报告策略
- 04加固运行无障碍检查的 CI 工作流
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mgifford/ accessibility-skills --skill ci-cd安装后在 Claude Code 中输入/ci-cd确认已加载。
附带文件SYNC.md
详细介绍
CI/CD 无障碍技能
规范来源:
mgifford/ACCESSIBILITY.md中的examples/CI_CD_ACCESSIBILITY_BEST_PRACTICES.md此技能源自该文件。如有疑问,以示例为准。 规范指南指出,与此技能的同步不会自动进行 ——请定期对照规范文件重新检查此文件。
添加、审查或维护 CI/CD 无障碍检查时,请遵循以下规则。
有关审核时何时使用 AI 技能、何时使用自动扫描器的指导,
请参阅本仓库中的 AI_AUDIT_GUIDE.md。
核心要求
CI 可以防止已知的无障碍回归、保留经过测试的语义,并为人工审查提供证据。**它本身无法确立 WCAG 合规性。**自动化规则、评分、快照和 AI 输出都是无障碍评估的输入,而非评估的替代品。本指南针对范围内的内容设定 WCAG 2.2 AA 目标——该目标本身并不代表符合性声明。在此 AA 目标下确认的 AAA 级问题,默认属于 obligation: aspirational(可见但不阻碍流程的进阶目标),而非 advisory;除非项目明确将该特定准则提升为 required。
原则:测试完整的用户任务和相关状态,而不仅是页面加载;在本地运行快速、确定性的检查,并在 CI 中重复运行;使用明确且便于审查的规则拦截已知回归;将无障碍、性能、安全和功能信号区分开来;测试最终渲染、清理和优化后的输出;由人工负责判定严重程度、替代方案、例外情况和发布决策;仅赋予工作流和 Agent 完成任务所需的最低权限。
严重程度等级(本技能)
| 等级 | 含义 |
|---|---|
| 严重(最高) | 无法完成核心任务或安全关键任务,且不存在合理的无障碍替代方案 |
| 严重 | 主要任务受阻,或需要采用不安全、不可靠或负担极重的变通方式 |
| 中等 | 任务仍可完成,但会造成严重困难、困惑、延迟或信息缺失 |
| 轻微 | 影响有限,任务仍易于理解和操作 |
工具标签(axe 的 critical/serious/moderate/minor、Lighthouse 评分)
是分流参考,而非项目严重程度——只有在评估其对受影响任务的实际用户影响后,
才将其映射到此分类体系。
分层测试模型
没有任何单一工具能够覆盖无障碍要求。应根据受影响的任务以及回归的可能性和后果选择测试层:
| 层 | 适用场景 | 无法确立 |
|---|---|---|
| 静态分析 / lint | 快速反馈可检测的源代码模式 | 运行时行为或最终输出 |
| 单元 / 组件测试 | 稳定的组件契约(名称、角色、状态) | 整页阅读体验和焦点顺序 |
| 浏览器规则扫描(axe-core) | 渲染状态下可检测的规则 | 完整的 WCAG 覆盖或可用性 |
| 语义断言(角色查询、ARIA 快照) | 无障碍树结构及其变化 | 屏幕阅读器的确切读出内容 |
| 视觉检查 | 主题 / 布局回归、焦点指示器 | 含义、键盘访问或文本替代 |
| 端到端测试 | 可操作性、焦点、状态变化、错误 | 单独验证辅助技术互操作性 |
| 人工和辅助技术测试 | 质量、顺序、交互、兼容性 | 所有配置下的通用行为 |
对于自动化无法完成的检查,请使用 skills/manual-testing/SKILL.md。
重要:选择工具前先定义 CI 契约
对于每项检查,请记录:覆盖的用户任务/页面/组件/状态;命令和配置文件;工具/浏览器/运行时/规则集版本;结果是发出警告、阻止操作,还是仅生成产物;基准/预期结果;失败时的负责人和响应流程;已知误报和未测试的要求;以及为审核保留的证据。
除非分支保护确实要求,否则不要将某项检查描述为合并门禁。不要将警告描述为失败。
分数不代表符合性百分比。 Lighthouse 无障碍得分为 100,并不意味着符合 100% 的 WCAG;得分为 90 也不代表页面的无障碍程度为 90%——它衡量的是 Lighthouse 在一种配置下运行的加权审计结果。请使用单项审计结果调查回归问题。分数阈值可以作为额外信号,但不应作为唯一门禁;还应针对团队认定为阻断条件的具体规则/任务设置失败判定。请将性能预算分开处理——性能得分不得提高或降低无障碍严重性。
{
"ci": {
"collect": { "staticDistDir": "./_site", "numberOfRuns": 1 },
"assert": {
"assertions": { "categories:accessibility": ["warn", { "minScore": 0.9 }] }
},
"upload": { "target": "filesystem", "outputDir": ".lighthouseci" }
}
}
这只会发出警告,不会阻止操作。调查后,提高阈值(或改为 "error")可能会有帮助,但阈值仍不能证明符合性。
重要:在每个 PR 上使用 axe-core 检查各种状态和偏好设置
页面就绪后,以及相关交互完成后,都要扫描有意义的状态——如果用户必须打开菜单、提交表单或完成其他操作,请不要只扫描初始路由。
import AxeBuilder from '@axe-core/playwright';
import { expect, test } from '@playwright/test';
const colorSchemes = ['light', 'dark'] as const;
for (const colorScheme of colorSchemes) {
test(`home page in ${colorScheme} mode`, async ({ page }) => {
await page.emulateMedia({ colorScheme });
await page.goto('/');
await expect(page.getByRole('main')).toBeVisible();
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21aa', 'wcag22aa'])
.analyze();
expect(results.violations).toEqual([]);
});
}
test('navigation is tested while expanded', async ({ page }) => {
await page.goto('/');
await page.getByRole('button', { name: 'Menu' }).click();
await expect(page.getByRole('navigation', { name: 'Main' })).toBeVisible();
const results = await new AxeBuilder({ page }).analyze();
expect(results.violations).toEqual([]);
});
扫描结果干净,只表示在测试范围内未发现违规——并不意味着不存在障碍。PR 上缺少 axe-core 检查属于严重问题;缺少配色方案检查也是覆盖缺口,因为主题特定的回归问题往往只会出现在未经测试的配色方案中。
覆盖适用的组合,但不必构建成本过高的矩阵:桌面和窄屏重排视口;系统/浅色/深色主题;默认和增强对比度;强制颜色;减少动态效果;默认/悬停/焦点/激活/禁用/加载/成功/错误状态;已登录/已退出/超时/权限状态。常规变更采用成对或基于风险的覆盖;对于高风险组件(身份验证、支付、安全、核心导航),则测试所有组合。
严重:语义断言和 ARIA 快照
按角色和无障碍名称进行查询,可建立实用的契约:
test('contact form exposes its controls and error', async ({ page }) => {
await page.goto('/contact');
const email = page.getByRole('textbox', { name: 'Email address' });
await expect(email).toBeVisible();
await page.getByRole('button', { name: 'Send message' }).click();
await expect(page.getByText('Enter an email address')).toBeVisible();
await expect(email).toHaveAttribute('aria-invalid', 'true');
});
test('main navigation retains its semantic structure', async ({ page }) => {
await page.goto('/');
await expect(page.getByRole('navigation', { name: 'Main' }))
.toMatchAriaSnapshot(`
- navigation "Main":
- list:
- listitem:
- link "Home"
`);
});
审核快照变更;不要机械地更新它们。ARIA 快照表示无障碍树,而不是每种屏幕阅读器都会播报内容的逐字稿——语音输出还取决于辅助技术产品、浏览器、操作系统、设置、模式和用户操作。
Guidepup 虚拟屏幕阅读器(单元级别,精确的播报输出):
import { virtual } from '@guidepup/virtual-screen-reader';
it('announces the dialog title and action buttons', async () => {
document.body.innerHTML = `
<dialog open aria-labelledby="dlg-title">
<h2 id="dlg-title">Confirm deletion</h2>
<button>Delete</button>
</dialog>`;
await virtual.start({ container: document.body });
const spoken = await virtual.spokenPhraseLog();
expect(spoken).toContain('Confirm deletion');
await virtual.stop();
});
严重:视觉和偏好设置回归测试
视觉测试可以发现文本被截断、焦点指示器缺失、主题特定的对比度变化以及重排回归问题。请为差异显著的主题和强制颜色行为分别保留经过审核的基线。不要仅使用截图一致性测试对比度——抗锯齿、透明度、渐变和渲染差异都会导致像素比较不可靠;请确认计算后的颜色,并在对比度至关重要时进行目视检查。为保证测试具有确定性,请模拟减少动态效果,但切勿仅为通过截图测试而从产品中移除动画。
重要:风险指标与构建门禁
Reflow 风险和 Focus Visible 测试等行为检查(参见
skills/behavioral-a11y/SKILL.md)会产生指标,而非符合性判定。对于每个目标而言,即使假阳性概率很低,随着页面、组件、状态或构建数量增加,假阳性也会累积;此外,网站上的发现通常会因共享模板和组件而彼此相关。将每个指标都转化为阻断构建的失败,会导致 CI 噪声过多、可信度降低,并促使人们绕过检查,而不是修复根本模式。
| 结果 | CI 默认处理方式 |
|---|---|
| 确认的确定性失败 | 可以阻断 |
| 相对于已审核基线确认的回归 | 可以阻断 |
| 风险指标 | 报告并转交审核 |
cantTell | 报告为覆盖不完整 |
| 测试错误 | 报告为扫描健康状况问题 |
| 已审核的例外 | 在审核到期前不阻断 |
不要仅仅因为结果可由机器读取,就规定自动阻断。首次出现的风险指标尚未经过审核,也未排除规范中的例外情况(例如 SC 1.4.10 的二维布局例外);对此类指标进行阻断,相当于将未经审核的信号视为已确认的失败。
将“CI 默认处理方式”映射到 policy.handling:确定的已确认失败或经过审核的回归属于 report(可以阻断);风险指标或 cantTell 属于 review,在人工确认或驳回之前绝不能设为 suppress;只有提供下文所述的完整抑制详情,已审核的例外才能设为 suppress。handling 与 obligation 相互独立——不要用 CI 处理方式代替对底层标准是否为 required 的判断。切勿因为某个目标存在较多噪声就禁用整条规则或整个引擎;应只抑制范围明确的具体发现。参见无障碍发现跟踪:策略分类。
全站和全站点范围的扫描还必须做到:区分唯一根因集群与受影响实例,并分别报告二者——不能针对每个受影响的 URL 都报告一个问题;在确定严重程度前,将新发现与已审核的基线进行比较;记录扫描所用的检测器、浏览器和基线版本;为每个经过审核的例外指定负责人、说明理由并设置到期日期;定期抽样查看看似正常的结果并调查假阴性,因为没有发现问题的检查并不能证明其假阴性为零。
有关上述唯一发现、受影响实例、受影响页面和根因集群的定义,以及 fixture/PR/定期扫描的频次安排,请参见 skills/behavioral-a11y/SKILL.md。
关键:CI 工作流安全
对无障碍工作流应用与构建/部署工作流相同的控制措施:
- 将第三方 Action 固定到经过审查的完整提交 SHA,而非标签——标签可能移动,提交 SHA 不会
- 在工作流或作业级别明确声明
GITHUB_TOKEN的最低权限 - 如果后续步骤不需要 Git 访问权限,请在 checkout 时设置
persist-credentials: false - 对会执行不可信 PR 代码的工作流,避免使用
pull_request_target - 通过环境变量或文件传递不可信值,并为 shell 扩展加引号——绝不要将这些值插入可执行脚本文本
- 不要向来自 fork 的代码或其他不可信代码暴露机密
- 在可行的情况下,限制 AI/转换步骤的网络访问和工具
- 添加超时、并发控制和有界的爬取限制
- 将上传的 HTML、JSON、屏幕截图、跟踪记录和日志视为可能包含敏感信息
- 产物保留时间不得超过审查流程所需时长
- 对任何可能部署到外部系统或修改外部系统的工作流,使用环境并设置必需的审查者
- 除非令牌权限、事件上下文、工具和平台控制措施确实能够证明,否则不要声称某个工作流“无法访问机密”或“无法合并”
name: Accessibility checks
on:
pull_request:
push:
branches: [main]
workflow_dispatch:
permissions:
contents: read
concurrency:
group: accessibility-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
jobs:
accessibility:
runs-on: ubuntu-latest
timeout-minutes: 20
steps:
- name: Check out repository
uses: actions/checkout@<reviewed-full-commit-sha>
with:
persist-credentials: false
- name: Set up Node.js
uses: actions/setup-node@<reviewed-full-commit-sha>
with:
node-version-file: .nvmrc
cache: npm
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npm run build
- run: npm run test:a11y:ci
- name: Upload debugging evidence after failure
if: failure()
uses: actions/upload-artifact@<reviewed-full-commit-sha>
with:
name: accessibility-test-evidence-${{ github.run_id }}
path: test-results
retention-days: 7
启用前:确认当前官方文档中存在相应 Action 和输入项;将每个 Action 版本解析为完整提交 SHA;审查传递依赖的 Action 代码和发行说明;验证 fork 场景下的行为,确保不可信 PR 不会获得特权机密;确认构建路径与生产环境一致;先在测试分支中运行工作流。
重要:定时扫描与 GitHub 无障碍扫描器
定时扫描可以发现偏差、依赖项变更以及 PR 测试遗漏的页面。即使仍有已知问题,也要继续扫描——因为一个未解决的问题就暂停所有扫描,可能会掩盖新的回归;与其因告警疲劳而暂停,不如优先采用去重、稳定的指纹和明确的责任归属。
对于每次定时扫描,都应定义:获准访问的目标 URL 和爬取边界;公开与已认证状态;频率/超时/资源限制;问题去重和关闭行为;报告保留和脱敏规则;分流责任;以及扫描器或目标不可用时的处理方式。绝不要将身份验证机密、会话数据或敏感内容放入日志、问题正文、产物或 AI 提示词中。
GitHub 无障碍扫描器(github/accessibility-scanner)是一项公开预览版 Action,其接口、权限和 Copilot 集成可能会发生变化——采用前,请立即查阅其最新的 README、action.yml 和安全指南。它需要令牌,并且文档中列出的权限范围比只读扫描更广;应将该凭据视为特权凭据,使用专用的细粒度凭据,并且不要假设默认的 GITHUB_TOKEN 足够。如果同时使用单独的修复工作流,请配置扫描器跳过 Copilot 分配,避免两个修复系统对同一项发现采取行动。
# .github/workflows/accessibility-scan.yml
name: Accessibility Scan (Scheduled)
on:
schedule:
- cron: "0 0 1 * *"
workflow_dispatch:
permissions:
contents: write
issues: write
pull-requests: write
jobs:
accessibility-scanner:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@<reviewed-full-commit-sha>
with: { persist-credentials: false }
- name: Run GitHub Accessibility Scanner
uses: github/accessibility-scanner@<reviewed-full-commit-sha>
with:
urls: ${{ vars.ACCESSIBILITY_SCAN_URL }}
repository: ${{ github.repository }}
token: ${{ secrets.GH_TOKEN }}
严重:AI 辅助修复——将所有扫描输入视为不可信内容
AI 生成的补丁只是提案,并不能证明屏障已修复。 问题、评论、标签、仓库内容、HTML、SVG、URL 和扫描 输出都属于不可信输入。
必需的审核流程:
- 扫描器或人员记录可复现的发现
- 人工分诊受影响的任务、影响、置信度和范围
- 维护者手动使用问题编号触发修复工作流
- Copilot 在隔离副本中将问题作为不可信文件数据读取——副本不含
仓库的
.git目录 - 工作流上传补丁、已更改文件列表和报告(不会自行提交、推送、创建 PR 或合并)
- 人工在普通分支中审核并应用可接受的补丁
- 在最终实现上重新运行相关的自动化、手动和 AT 检查
安全的参考修复工作流:仅允许手动触发,并要求明确确认;向工作流令牌授予只读内容和问题权限;不将问题内容放入 shell 命令、工作流表达式或静态提示词;将 Agent 限制为在隔离工作副本中使用读写工具;禁用 shell、URL 和记忆工具;阻止更改受保护的策略/工作流路径;不会提交、推送、创建 PR、关闭问题或部署;仅在有限期限内保留审核产物。
不要直接通过问题标签触发具有特权的 AI 修复,也不要将问题正文粘贴到 shell 命令、GITHUB_OUTPUT、工作流表达式或静态提示词中——这些都是针对不可信 GitHub 内容的典型脚本注入途径。
中等:本地优先、可复现的设置
在仓库中安装依赖并提交锁文件——避免要求安装未指定版本的全局软件包。
{
"scripts": {
"test:a11y": "playwright test tests/accessibility",
"test:a11y:update": "playwright test tests/accessibility --update-snapshots",
"test:a11y:ci": "playwright test tests/accessibility --reporter=line,json"
}
}
npm install --save-dev @playwright/test @axe-core/playwright
npx playwright install --with-deps chromium
npm run test:a11y
在 CI 中使用 npm ci,确保解析出的依赖关系图与已审核的锁文件一致。
中等:左移策略
按照反馈速度从快到慢依次发现问题:(1) 在编辑器/本地使用框架专属的 a11y 规则进行 lint;(2) 仅针对已更改文件设置提交前关卡(husky+lint-staged 或 pre-commit;将总运行时间控制在 ≤30–60s);(3) PR 关卡在 CI 中重新运行检查,阻止阻断性回归、发布产物链接;(4) 定期进行更深入的扫描,并自动为发现的问题添加标签、记录趋势指标。除非本地/提交前检查通过且 PR CI 检查全绿,否则不得接受任何影响 UI 的提交。
基线、抑制与例外
优先防止新增回归,同时分配并修复已有发现。基线是临时清单,并不能证明现有屏障可接受。每项抑制/例外都应包括:受影响的具体规则/文件/元素/路由/任务;原因和佐证;用户影响及可用的解决方法;负有责任的负责人及关联问题;到期或审核日期;以及尽可能窄的适用范围。若仅有一个经过审核的实例属于例外,不要抑制整条规则。不要仅为让 CI 变绿而更新快照或基线。
这与 policy.suppression 所要求的详细信息相同(范围、原因、证据、负责人、审核或到期日期)——在机器可读的发现记录中使用这些术语。抑制绝不等同于解决;已抑制的发现仍会被记录,且仅从指定的报告或强制执行范围中排除。
可持续自动化
每个 PR 都运行快速、针对性检查;完整爬取和大型浏览器矩阵留给定期任务、发布或高风险变更。在不掩盖更新的前提下,缓存经过审核的依赖项以提升速度。如果不会丢弃必需证据,则取消过时的运行。仅在必要期限内保留产物。避免使用多个扫描器重复提供相同证据。只要确定性工具能够回答问题,就应优先于 AI 工具。
完成标准检查清单
- 为每项检查定义受影响的用户任务、页面、组件和状态
- 对依赖项、浏览器、操作和配置进行版本管理,并确保可供审查
- 测试最终渲染的输出(而非仅测试源模板)
- 涵盖适用的主题、偏好设置、视口和交互状态
- 在其文档说明的局限范围内解读自动化结果 (评分 ≠ 符合要求;工具严重程度 ≠ 项目严重程度)
- 完成必需的手动测试和 AT 测试,或进行明确的交接
- 不得明知会引入新的 Critical 和 Serious 障碍
- 调查结果包含可复现的证据和负责人
- 抑制规则和例外范围明确、理由充分、有记录并注明日期
- 审查工作流权限、不可信输入处理、机密信息和构建产物
- Action 引用使用经过审查的完整提交 SHA,而非标签
- 在后续步骤不需要 Git 访问权限的情况下,设置
persist-credentials: false - 避免在执行不可信 PR 代码的工作流中使用
pull_request_target - AI 修复工作流仅允许手动触发,令牌权限范围只读, 禁止使用 shell/URL/memory 工具,且绝不自行提交/推送/合并
- 正式使用前,先在测试分支中验证最终工作流
- 保持文档与此技能同步(非自动完成)
关键 WCAG 标准(自动化覆盖范围)
- 1.1.1 非文本内容(A)— 可由 axe-core / Lighthouse 检测
- 1.3.1 信息和关系(A)— 可由 axe-core 检测
- 1.4.3 对比度(最低)(AA)— 可由 Lighthouse / axe-core 检测(仍需目视检查)
- 4.1.2 名称、角色、值(A)— 可由 axe-core + aria 快照检测
- 4.1.3 状态消息(AA)— axe-core 可部分检测
自动化最多只能覆盖约 30–40% 的 WCAG 问题。应结合手动测试 和辅助技术测试 — 参见
skills/manual-testing/SKILL.md。
参考资料
- 完整最佳实践指南
skills/behavioral-a11y/SKILL.md— Reflow 风险和 Focus Visible 风险指标、结果术语,以及针对大规模误报的指导- 左移自动化指南
- GitHub Accessibility Scanner 集成
- GitHub Actions 安全使用参考
- GitHub:脚本注入
- Lighthouse CI 文档
- axe-core/playwright
- Playwright aria 快照
- Guidepup 虚拟屏幕阅读器
- GitHub Accessibility Scanner
标准展望: 这些规则以 WCAG 2.2 AA 为目标。 请关注:https://www.w3.org/TR/wcag-3.0/