mcpField
C
ci-cd

ci-cd

github.com/mgifford测试与质量57138 次安装7 周前更新
代码仓库

关于 ci-cd

面向维护无障碍自动化检查的 CI/CD 技能,说明如何按用户任务和状态分层测试,并将自动化结果作为人工评估的证据,而非合规结论。内容还涉及检查规则、风险处理和工作流安全。

主要功能

  1. 01按层次组合静态、组件及浏览器测试
  2. 02在交互后的关键状态运行 axe-core
  3. 03使用语义断言和 ARIA 快照
  4. 04区分已确认缺陷与风险指标
  5. 05制定 CI 门禁和工作流安全控制

适用场景

  1. 01为 Pull Request 设计无障碍检查
  2. 02覆盖主题、视口和交互状态的回归测试
  3. 03审核自动化检查的阻断与报告策略
  4. 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 和扫描 输出都属于不可信输入。

必需的审核流程:

  1. 扫描器或人员记录可复现的发现
  2. 人工分诊受影响的任务、影响、置信度和范围
  3. 维护者手动使用问题编号触发修复工作流
  4. Copilot 在隔离副本中将问题作为不可信文件数据读取——副本不含 仓库的 .git 目录
  5. 工作流上传补丁、已更改文件列表和报告(不会自行提交、推送、创建 PR 或合并)
  6. 人工在普通分支中审核并应用可接受的补丁
  7. 在最终实现上重新运行相关的自动化、手动和 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。


参考资料

标准展望: 这些规则以 WCAG 2.2 AA 为目标。 请关注:https://www.w3.org/TR/wcag-3.0/

评论

支持 Markdown