可访问性测试实战:从 WCAG 2.1 到 CI 质量门禁

在现代软件工程中,可访问性(Accessibility,简称 a11y)测试长期被视为"锦上添花"的边缘需求,但随着全球数字包容意识的觉醒和各国法规的密集出台,它已迅速演变为一条不可触碰的法律合规底线。从视障用户依赖的屏幕阅读器到运动障碍用户使用的键盘导航,从听障用户需要的字幕支持到认知障碍用户依赖 …

在现代软件工程中,可访问性(Accessibility,简称 a11y)测试长期被视为"锦上添花"的边缘需求,但随着全球数字包容意识的觉醒和各国法规的密集出台,它已迅速演变为一条不可触碰的法律合规底线。从视障用户依赖的屏幕阅读器到运动障碍用户使用的键盘导航,从听障用户需要的字幕支持到认知障碍用户依赖的清晰设计——可访问性测试所守护的,是一个产品开发团队对"所有用户"的基本承诺。

本文将从可访问性的法律法规背景出发,系统梳理 WCAG 2.1 标准的核心要求,深入实践 axe-core、Lighthouse CI、Pa11y 三大自动化工具的集成方案,并结合屏幕阅读器手动测试与常见问题的修复策略,最终构建一套可嵌入 CI/CD 的质量门禁体系。这不仅是一篇技术指南,更是一次关于"技术向善"的工程实践。

1. 为什么可访问性测试不是"锦上添花"而是法律合规底线?

1.1 被忽视的用户群体规模

根据世界卫生组织(WHO)2023 年的数据,全球有超过 13 亿人(约占全球人口的 16%)患有某种形式的残疾。这一群体在数字世界中面临的障碍远比我们想象的更为严峻:视障用户无法感知色彩鲜艳但缺少文字描述的界面;听障用户无法获取纯音频内容;运动障碍用户无法通过精细的鼠标操作完成任务;而认知障碍用户则在面对复杂混乱的信息架构时倍感挫败。

更令人深思的是,这些"特殊需求"实际上覆盖了远比残疾人群更广的场景。一位司机在驾驶时需要语音控制(临时运动受限),一位老年用户在阅读时需要更大的字体(视力自然衰退),一位在强光下使用手机的用户需要高对比度模式——可访问性的设计不仅仅服务于"少数群体",而是在提升所有用户的体验质量。

1.2 法律武器已出鞘:全球 a11y 合规监管风暴

可访问性已经从一个"道德倡议"升级为具有强制约束力的法律要求。在美国,《美国残疾人法案》(ADA,Americans with Disabilities Act)的适用范围已被法院判例扩展到了数字领域。最常被引用的案例是 Domino’s Pizza 案(Robles v. Domino’s Pizza,2019),一位盲人用户因无法使用 Domino’s 的网站和移动应用订购披萨而提起诉讼。虽然案件最终以和解告终,但它确立了关键判例:商业网站和应用程序必须遵守 ADA 的可访问性要求。类似的,Target 公司在 2008 年就因网站无障碍问题支付了 600 万美元的和解金,并被要求投入额外资金进行整改。

在欧洲,《欧洲无障碍法案》(EAA,European Accessibility Act)将于 2025 年 6 月正式全面生效。该法案覆盖电子产品和服务(包括计算机操作系统、支付终端、电子商务、银行等),要求这些产品和服务必须满足特定的无障碍标准。不合规的企业将面临严厉的处罚,甚至被禁止在欧盟市场销售相关产品。

在中国,**《无障碍环境建设法》**已于 2023 年 9 月正式实施。这部法律明确要求"为残疾人、老年人等社会成员平等、充分、便捷地参与和融入社会生活提供支持",并将 ICT(信息通信技术)产品和服务的无障碍纳入了监管范围。随着执法体系的完善,中国的数字产品无障碍合规也将从"倡导"走向"强制"。

1.3 可访问性 = SEO:语义化的双重回报

可访问性测试的一个意外收益是它对搜索引擎优化(SEO)的巨大提升。屏幕阅读器依赖的语义化 HTML 结构(正确使用 <header>、<nav>、<main>、<article>、<footer> 等标签)同样是搜索引擎爬虫理解页面内容的关键线索。一个可访问性良好的页面,通常也具有更好的 SEO 表现——清晰的标题层次(<h1> 到 <h6> 的有序使用)、描述性的 alt 文本、有意义的链接文本,这些既是视障用户理解页面的桥梁,也是搜索引擎排名算法的评估信号。

1.4 键盘可达性:超越视障的普适价值

键盘可达性(Keyboard Accessibility)是可访问性测试中最基础但也最常被忽视的一项。许多人误以为"键盘操作"只是为无法使用鼠标的用户准备的,但这一设计对更广泛的用户群体都有直接价值:“Power User"通过键盘快捷键大幅提升操作效率,开发者在无鼠标的服务器环境中调试组件时依赖键盘操作,所有用户在填写长表单时都更倾向于使用 Tab 键切换字段而非频繁移动鼠标。因此,键盘可达性不是"特殊需求”,而是"好的交互设计"的基本特征。

2. WCAG 2.1 标准速览:从 A 到 AAA 的要求

Web Content Accessibility Guidelines(WCAG)是由万维网联盟(W3C)发布的全球公认的网络内容可访问性标准。当前广泛推行的版本是 WCAG 2.1,它将所有要求归纳为 POUR 四大原则:

2.1 POUR 四大原则详解

  • 可感知(Perceivable):信息和用户界面组件必须以可感知的方式呈现。例如,图像必须有替代文本,视频必须有字幕,音频必须有文字转录,颜色不能是传达信息的唯一方式。
  • 可操作(Operable):用户界面组件和导航必须可操作。例如,所有功能必须可通过键盘操作,用户必须有足够的时间阅读和使用内容。
  • 可理解(Understandable):信息和用户界面操作必须可理解。例如,文本内容必须可读、可理解,输入必须有标签提示,错误提示必须明确。
  • 健壮性(Robust):内容必须足够健壮,以便能被各种辅助技术可靠地解释。例如,HTML 必须有效、标记必须语义正确,名称/角色/值必须通过 ARIA 正确传达。

2.2 关键合规检查点详解

WCAG 2.1 将每项检查点按照影响程度分为三个级别:A(基础级别)、AA(增强级别)、AAA(卓越级别)。其中,AA 是最常被政府法规和企业合规要求所引用的标准级别。

以下是前端开发中最常被审计的 5 个关键检查点:

检查点级别核心要求典型违规场景
1.4.3 颜色对比度AA普通文本对比度 ≥ 4.5:1,大文本(18pt+)≥ 3:1浅灰色按钮文字在白色背景上可读性极差
2.1.1 键盘可达性A所有功能必须可通过键盘操作图片轮播只有左右箭头可点击,Tab 无法导航
2.4.6 标题与标签AA表单输入必须有关联的 <label> 或 aria-label<input> 没有 id 与 label 的 for 关联
3.3.1 错误识别A输入错误必须被自动检测并以文本形式标识仅通过红色边框提示错误,没有文字说明
4.1.2 名称/角色/值AUI 组件必须可被辅助技术感知自定义下拉框是纯 div 堆砌,屏幕阅读器无法识别

上表所列的 5 个检查点涵盖了可访问性测试中最常见的违规类型。值得注意的是,A 级别虽然是最低门槛,但它的符合率是 AA 的基础——如果键盘根本就无法操作页面(2.1.1 A 级),那么讨论对比度是否达标就没有意义了。因此,在实际工程中,团队通常会先确保 100% 的 A 级检查点达标,再逐步攻克 AA 级要求。

2.3 A / AA / AAA 级别的适用场景

级别目标用户群工程复杂度法规引用频率
A(基础)重度障碍用户低:核心功能可用即可作为最低门槛
AA(增强)中度至重度障碍用户中:需系统性调整设计与对比度最常被法规引用
AAA(卓越)所有用户无障碍体验高:往往影响品牌形象与设计自由度较少强制要求

AA 级别的要求之所以被广泛引用,是因为它找到了"合规负担"与"用户体验改善"之间的最佳平衡点。将对比度标准提升到 AAA 等级(普通文本 ≥ 7:1)虽然对生产力工具类网站是加分项,但对于内容丰富的媒体网站来说,可能会导致设计美感受损。因此,绝大多数企业和政府机构的可访问性合规目标都设定在 WCAG 2.1 AA 级别。

3. axe-core:最权威的自动化可访问性测试引擎

3.1 axe-core 的技术架构与规则库

axe-core 是由 Deque Systems 开发的开源可访问性测试引擎,被广泛认为是业界最权威、最准确的自动化 a11y 检测工具。它目前已集成到 Google Lighthouse、Microsoft Accessibility Insights 等众多主流工具中。axe-core 的核心竞争力在于其规则的准确性和低误报率(Low False Positive Rate)——它只报告那些几乎可以确定存在问题的项,而不会将未被严格证明违规的可疑标记为错误。

axe-core 的规则库覆盖了 WCAG 2.1 的 A 和 AA 级别要求,以及最佳实践建议。每条规则都有明确的"影响度"评级:

  • Critical: 阻碍内容访问(如缺少页面标题、图片无 alt)
  • Serious: 严重降低可用性(如表单缺少 label、颜色对比度不足)
  • Moderate: 中度影响(如缺少首选语言声明)
  • Minor: 低度影响(ARIA 属性值不规范)

3.2 浏览器插件:开发者的第一入口

对于前端开发者来说,axe DevTools 浏览器扩展是最直接的问题发现工具。安装后,开发者可以在 Chrome Devtools 中一键扫描当前页面,axe 会高亮所有检测到的违规元素,并给出具体的修复建议。这种即时反馈机制将 a11y 检测从"发布前检查"转变为"编码时自检",大大减少了问题进入代码仓库的概率。

3.3 Playwright + axe-core 端到端测试

将 axe-core 集成到端到端测试框架中,是确保可访问性质量不因迭代而退化的关键手段。以下是使用 @axe-core/playwright 的完整测试示例:

import { test, expect } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

test.describe('可访问性审计', () => {
  test('首页应通过 axe-core WCAG 2.1 AA 检查', async ({ page }) => {
    await page.goto('https://example.com');

    // 执行 axe 扫描,限制规则集为 WCAG 2.1 AA
    const accessibilityScanResults = await new AxeBuilder({ page })
      .withTags(['wcag2a', 'wcag2aa', 'wcag21aa'])
      .analyze();

    // 断言没有严重或可访问性违反
    expect(accessibilityScanResults.violations).toEqual([]);
  });
});

上述测试代码会在页面加载完成后自动运行 axe-core 分析,并将结果与空数组对比。如果检测到任何违规,测试将失败并在报告中输出详细的违规信息。withTags 方法用于精确控制测试覆盖的规则集,这里选择了 WCAG 2.1 的 A 级别和 AA 级别规则。

对于更复杂的场景,你可能需要排除某些已知的假阳性区域。例如,一个由第三方嵌入的地图组件可能存在无法直接修复的可访问性问题,此时可以通过 exclude 方法跳过该区域:

const results = await new AxeBuilder({ page })
  .withTags(['wcag2aa'])
  .exclude('.third-party-map-widget')  // 排除第三方组件
  .options({
    rules: {
      'color-contrast': { enabled: false }  // 临时禁用颜色对比度检查(如在暗模式测试中)
    }
  })
  .analyze();

3.4 axe-core 的覆盖范围与内在局限

尽管 axe-core 在自动化检测方面表现出色,但开发者和测试工程师必须清醒地认识到它的内在局限性。根据 Deque 的官方数据,axe-core 的自动化规则只能覆盖大约 20% 到 30% 的潜在可访问性问题。这意味着即使你项目的 axe-core 扫描结果为零违规,也不能声称"已经完全可访问"。

axe-core 无法检测的问题类型包括:

  1. 语义正确性:HTML 语法有效但语义错误(如使用 <h1> 做纯装饰)
  2. 键盘导航逻辑:Tab 顺序是否合理、焦点返回值是否正确
  3. 内容可读性:文本是否使用了难懂的专业术语,是否适合认知障碍用户
  4. 动态状态变化:ARIA live regions 是否正确宣告了状态变更
  5. 多步骤流程可用性:完整的用户任务流在不同辅助技术下的体验

这些局限也正是手动测试和用户研究在可访问性测试中不可替代的根本原因。

4. Lighthouse CI:性能与 a11y 的双重门禁

4.1 Lighthouse 的 a11y 评分机制

Lighthouse 是 Google Chrome 内置的网页审计工具,它的可访问性(Accessibility)评分基于 axe-core 的底层检测能力,但将结果转换为了更易理解的 0-100 分数制。与 axe-core 的 pass/fail 二元结果不同,Lighthouse 的评分考虑了"手动检查项"的建议——即使某些测试无法被自动化验证,Lighthouse 也会将其列为"Additional items to manually check",提醒团队进行人工审计。

Lighthouse a11y 评分的组成包括:

  • 自动检测项:基于 axe-core 的问题(权重最高)
  • 手动检查项:需要人工验证的项(如"页面内容顺序逻辑合理")
  • 通过项:已确认无问题的检查点

4.2 Lighthouse CI 配置实战

将 Lighthouse 审计集成到 CI 中,可以在每次代码提交时自动获取性能与可访问性的基线数据。以下是 lighthouserc.js 的完整配置示例:

// lighthouserc.js
module.exports = {
  ci: {
    collect: {
      url: [
        'https://staging.example.com/',
        'https://staging.example.com/login',
        'https://staging.example.com/dashboard',
      ],
      numberOfRuns: 3,  // 运行 3 次取中位数以消除抖动
    },
    assert: {
      assertions: {
        'categories:accessibility': ['error', { minScore: 0.9 }],  // a11y 分数 ≥ 90
        'categories:performance': ['warn', { minScore: 0.8 }],     // 性能 ≥ 80(仅警告)
        'color-contrast': 'error',    // 颜色对比度必须为通过
        'label': 'error',             // 表单标签必须为通过
        'tabindex': 'error',          // tabindex 使用必须为通过
        'duplicate-id-active': 'error',
      },
    },
    upload: {
      target: 'temporary-public-storage',  // 上传报告到 Google 临时存储
    },
  },
};

上述配置中,'categories:accessibility': ['error', { minScore: 0.9 }] 是核心门禁:如果 Lighthouse 的可访问性审计分数低于 90 分,CI 构建将被标记为失败。同时,通过单独的断言项(如 color-contrast、label),可以确保某些关键检查点绝对不能被跳过,即使总体分数刚好达标。

4.3 分数解读:89/100 意味着什么?

一个 89/100 的 a11y 分数通常表明项目在大部分自动检测项上表现良好,但至少存在一个或多个 Serious 级别的违规项。具体的扣分项需要通过 Lighthouse 的详细报告来定位。常见的 89 分"杀手"包括:

  • 缺少页面级 <title> 或 <html lang="zh-CN"> 声明(每项扣 2-4 分)
  • 一个表单 <input> 缺少关联的 <label>(扣 4-7 分)
  • 一个按钮的对比度刚好低于 4.5:1(扣 3-5 分)
  • ARIA 属性使用错误(如 aria-required="true" 但表单未标记 required,扣 2 分)

因此,89/100 不是一个"差不多"的分数——它意味着至少有一个用户因为某个具体的问题而无法正常使用你的网站。将门禁线设定在 90 或 95 的目的,就是杜绝"得过且过"的侥幸心理。

4.4 与性能审计协同的价值

Lighthouse CI 的一大优势在于它同时提供了性能(Performance)和可访问性(Accessibility)两项关键指标。在 PR 审查中,开发者只需查看一份 Lighthouse 报告,就能同时了解变更对页面加载速度和可访问性的影响。这种"一站式"审计体验降低了团队引入 a11y 门禁的认知成本,使得可访问性测试不再是"额外的负担",而是"已有工作的自然延伸"。

5. Pa11y:命令行批量可访问性扫描

5.1 Pa11y 的技术原理

Pa11y 是一款基于 Node.js 的开源命令行可访问性测试工具,它底层使用了 HTML_CodeSniffer 规则引擎来检测 WCAG 2.1 合规性问题。与 axe-core 和 Lighthouse 不同,Pa11y 的设计哲学更偏向"批量扫描"和"报告生成",非常适合需要对大量页面进行定期巡航扫描的场景。

Pa11y 支持通过 Puppeteer 驱动真实的浏览器实例,这意味着它可以测试需要 JavaScript 渲染的动态内容——这是早期静态 a11y 扫描工具(如 achecker)无法做到的。

5.2 命令行批量扫描

Pa11y 的命令行接口设计简洁,最基础的用法只需要一个 URL:

# 对单个页面进行 WCAG 2.1 AA 级别的审计
pa11y https://example.com --standard WCAG2AA

# 以 JSON 格式输出,便于 CI 解析
pa11y https://example.com --standard WCAG2AA --reporter json > pa11y-report.json

# 忽略特定规则(如 iframe 相关的已知问题)
pa11y https://example.com --ignore "WCAG2AA.Principle2.Guideline2_4.2_4_1.H64.1"

对于批量场景,Pa11y CI 提供了更高层的封装,可以直接读取 URL 列表文件并执行并行扫描:

# sitemap.txt 中每行一个 URL
echo "https://example.com/" > sitemap.txt
echo "https://example.com/about" >> sitemap.txt
echo "https://example.com/contact" >> sitemap.txt

# 批量扫描所有 URL
pa11y-ci --sitemap https://example.com/sitemap.xml --standard WCAG2AA

5.3 GitHub Actions 集成

Pa11y CI 与 GitHub Actions 的集成非常直接:

# .github/workflows/pa11y.yml
name: pa11y Accessibility Audit
on: [pull_request]

jobs:
  pa11y:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20'
      - name: Install pa11y-ci
        run: npm install -g pa11y-ci
      - name: Start development server
        run: npm run dev &
      - name: Wait for server
        run: npx wait-on http://localhost:3000
      - name: Run pa11y audit
        run: pa11y-ci --sitemap http://localhost:3000/sitemap.xml --threshold 0

此工作流配置中,--threshold 0 的含义是:即使只有 1 个可访问性问题,也标记 CI 为失败。这种零容忍策略适用于已经开始进行 a11y 整改的团队,但对于刚开始引入 a11y 测试的项目,建议先设置一个合理的阈值(如 --threshold 10),然后随着问题的减少逐步降低阈值。

5.4 Pa11y Dashboard 可视化报告

Pa11y 生态还提供了 Pa11y Dashboard,一个基于 web 的可视化报告面板。它可以定时扫描你配置的所有 URL,生成历史趋势图表,帮助团队追踪可访问性分数的演进轨迹。虽然其 UI 设计相对朴素,但对于需要向管理层汇报 a11y 改进进展的团队来说,它是一个零成本的报告工具。

6. 屏幕阅读器与辅助技术的手动测试

6.1 为什么自动化工具无法替代手动测试?

在讨论了大量自动化工具之后,我们必须回归一个核心事实:没有任何自动化工具可以完全替代屏幕阅读器的手动测试。axe-core 可以告诉你"这个图片缺少 alt 文本",但它无法告诉你"这个 alt 文本的描述是否真正帮助视障用户理解了图片的意义"。Lighthouse 可以检测到"缺少 label",但它无法评估"label 的文案是否清晰可理解"。

测试维度自动化工具能力手动测试能力互补策略
HTML 语法合规强:标签嵌套、属性完整性中:可发现极端异常自动化为主
语义正确性弱:无法判断语义是否恰当强:屏幕阅读器直接验证手动为主
交互流程可用性弱:测试单页面,不覆盖多步流程强:完整任务流验证手动为主
视觉对比度强:精确像素级计算弱:人眼不精确自动化为主
内容可读性无:无法评估文本质量强:真实用户反馈手动为主
键盘导航弱:不能判断导航逻辑强:逐 Tab 验证手动为主

上表清晰地展示了自动化工具和手动测试在可访问性审计中的互补关系。前者的优势在于"检测广度"——它可以快速扫描所有页面上的语法级问题;后者的优势在于"检测深度"——它可以评估真实用户在使用辅助技术时的完整体验。一个成熟的可访问性测试策略,需要将两者有机结合,而不是偏废其一。

6.2 NVDA 与 VoiceOver 测试实践

NVDA(NonVisual Desktop Access) 是 Windows 平台上最常用的免费屏幕阅读器。使用 NVDA 进行可访问性测试的基本流程是:

  1. 安装 NVDA 并启动
  2. 打开待测试网页,按 Insert + Space 进入浏览模式
  3. 使用方向键或 H(标题)、F(表单)、L(链接)等快捷键导航
  4. 验证每个交互元素是否有清晰的名称和角色播报
  5. 测试表单提交后的焦点返回是否正确
  6. 关注模态框弹出后焦点是否被"锁定"在对话框内

VoiceOver 是 macOS 和 iOS 内置的屏幕阅读器,测试流程类似:

  1. 按 Command + F5 启动 VoiceOver
  2. 使用 VO + 方向键(其中 VO = Control + Option)导航页面
  3. 使用 VoiceOver Rotor(VO + U)查看页面结构(标题列表、链接列表等)
  4. 验证页面结构的逻辑性——如果在 Rotor 中标题列表是混乱的,说明页面结构存在严重问题

6.3 WAVE 工具辅助评估

WAVE(Web Accessibility Evaluation Tool) 是由 WebAIM 提供的免费可访问性评估工具。它以浏览器扩展和在线服务的形式提供,最大的特点是可视化标注——它会在网页上直接以图标形式标注出每个可访问性元素(如 alt 文本、ARIA 角色、标题层次),让开发者可以"直观地看到"页面的可访问性结构。WAVE 也常被用于向设计师和产品经理展示 a11y 问题,因为它比纯文本的审计报告更具视觉说服力。

7. 常见可访问性问题与修复策略

7.1 色彩对比度不足:从根源解决设计问题

色彩对比度不足是 a11y 审计中最常见的违规项之一,也是最容易引起设计师和开发者争议的问题。WCAG 2.1 AA 要求普通文本的对比度至少达到 4.5:1,大文本(≥ 18pt 或 ≥ 14pt bold)至少 3:1。

检测工具:

  • Chrome DevTools 元素面板中的对比度指示器(可直接点击测试)
  • Figma 的 Stark 插件(实时对比度计算)
  • axe-core 的 color-contrast 规则自动检测

修复策略:
在设计阶段就定义好符合对比度要求的调色盘。不要等到开发完成后才发现某个灰色的提示文本对比度只有 3:1。建议在 Design System 层面为"正文文本"、“辅助文本”、“禁用状态"等各层级预设通过对比度检验的颜色值。

/* 反模式:对比度不足 */
.secondary-text {
  color: #999999;  /* 在白色背景上约为 2.8:1 —— 不达标 */
}

/* 修复:使用通过 WCAG AA 验证的颜色 */
.secondary-text {
  color: #595959;  /* 在白色背景上约为 7:1 —— 远超标准 */
}

7.2 缺失 alt 文本:语义化策略而非"给每张图片加 alt”

alt 属性的目的是为视障用户提供图片的文字替代,但"给每张图片都加 alt"是一个常见的误解。正确的策略是:

  • 信息性图片:需要提供描述性 alt(如"2024 年全站销售额环比增长了 23% 的趋势图")
  • 装饰性图片:必须设置 alt="" 或 role="presentation",让屏幕阅读器跳过它
  • 功能性图片(如图标按钮):alt 应该描述功能而非视觉外观(如"搜索"而非"放大镜图标")
  • 复杂图表:简短的 alt 不足以描述信息,应使用 aria-describedby 指向详细的文本描述
<!-- 反模式:装饰性图片有冗余 alt -->
<img src="decorative-divider.png" alt="漂亮的分割线图片">

<!-- 修复:装饰性图片 alt 留空 -->
<img src="decorative-divider.png" alt="">

<!-- 信息性图片:描述图片内容而非文件名 -->
<img src="revenue-chart-2024.png" alt="2024 年 Q1 到 Q4 的季度收入,从 120 万增长到 210 万">

<!-- 功能性图标按钮:描述功能 -->
<button>
  <img src="search-icon.png" alt="搜索">
</button>

7.3 表单错误通知:ARIA live regions 的正确使用

当用户提交表单遇到错误时,视障用户无法看到视觉上的红色提示框。此时需要利用 ARIA live regions 让屏幕阅读器自动宣告错误信息。

<!-- 反模式:错误提示没有 ARIA 关联 -->
<div class="error-text">用户名不能为空</div>

<!-- 修复:使用 aria-live 让屏幕阅读器自动播报 -->
<div id="form-errors" aria-live="polite" class="error-text">
  <ul>
    <li>用户名不能为空</li>
    <li>密码必须包含至少 8 个字符</li>
  </ul>
</div>

aria-live="polite" 表示屏幕阅读器会在当前朗读完毕后再播报新内容,不会粗暴打断用户。对于会立即影响操作安全的情景(如"您即将删除账户"),可以使用 aria-live="assertive"。

7.4 模态框焦点陷阱(Focus Trap)的实现

当模态框(Modal)打开时,键盘焦点必须被限制在模态框内部。如果用户按 Tab 键时焦点跑到了模态框背后的页面上,那么视障用户将无法正确关闭模态框或理解当前状态。

// 使用 focus-trap 库实现焦点管理
import { createFocusTrap } from 'focus-trap';

const modalElement = document.getElementById('confirm-modal');
const focusTrap = createFocusTrap(modalElement, {
  onActivate: () => modalElement.classList.add('is-open'),
  onDeactivate: () => modalElement.classList.remove('is-open'),
  // 当按下 Escape 键时自动关闭模态框
  escapeDeactivates: true,
  // 关闭后将焦点返回到触发模态框的按钮
  returnFocusOnDeactivate: true,
});

// 打开模态框时激活焦点陷阱
focusTrap.activate();

// 关闭模态框时释放焦点
function closeModal() {
  focusTrap.deactivate();
}

7.5 语义化 HTML 误用:div 伪装成 button

这是现代前端开发中最常见的反模式之一。开发者为了实现"自定义样式"的按钮,使用 <div> 或 <span> 添加点击事件,而不是使用原生的 <button> 元素。

<!-- 反模式:div 伪装成按钮 —— 屏幕阅读器无法识别,键盘也无法操作 -->
<div class="btn btn-primary" onclick="submitForm()">提交</div>

<!-- 修复:使用语义化 button,通过 CSS 覆盖默认样式 -->
<button type="button" class="btn btn-primary" onclick="submitForm()">提交</button>

使用原生 <button> 的好处是:默认支持键盘 Enter / Space 激活、自动参与 Tab 导航、屏幕阅读器正确播报"按钮"角色——这些功能如果要在一个 <div> 上通过 ARIA 和 JavaScript 手动模拟,需要额外编写数十行代码,且仍然可能遗漏边缘情况。

8. CI/CD 中的可访问性门禁设计

8.1 完整的 GitHub Actions 配置

以下是一个将 axe-core、Lighthouse CI 和 Pa11y 三方工具整合到一个工作流中的完整配置:

flowchart TD
    A[开发者提交 PR] --> B[GitHub Actions 触发]
    B --> C[构建应用并启动 dev 服务器]
    C --> D[并行运行三大 a11y 审计]
    D --> E[axe-core Playwright 测试]
    D --> F[Lighthouse CI 审计]
    D --> G[Pa11y 批量页面扫描]
    E --> H{axe-core 通过?}
    F --> I{Lighthouse a11y ≥ 90?}
    G --> J{Pa11y 违规数 = 0?}
    H -->|否| K[PR 阻断 + 报告中高亮违规]
    I -->|否| K
    J -->|否| L[仅警告:将违规写入 PR 评论]
    H -->|是| M
    I -->|是| M
    J -->|是| M
    M[全部通过 + 生成趋势报告] --> N[允许合并]
    L --> N

从流程图可以看出,axe-core 和 Lighthouse CI 的"硬性门禁"标准用于确保核心合规检查点不被突破,而 Pa11y 的批量扫描则用于捕获可能遗漏的整站级问题,并将结果以非阻断警告的形式呈现。这种"硬门禁 + 软监控"的分层策略,既保证了基础合规的不可妥协,又避免了因初期存量问题过多而导致开发流程完全停滞。

# .github/workflows/a11y-gate.yml
name: Accessibility Quality Gate

on:
  pull_request:
    branches: [main]

jobs:
  axe-core-test:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npx playwright install --with-deps chromium
      - run: npm run test:a11y  # 运行 axe-core Playwright 测试

  lighthouse-ci:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm install -g @lhci/cli@0.13.x
      - run: npm run build
      - run: lhci autorun  # 自动运行 lighthouserc.js 中的配置
        env:
          LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}

  pa11y-scan:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-node@v4
        with:
          node-version: '20'
      - run: npm ci
      - run: npm run dev &
      - run: npx wait-on http://localhost:3000
      - run: npx pa11y-ci --sitemap http://localhost:3000/sitemap.xml
        continue-on-error: true  # Pa11y 失败不阻断合并,仅生成报告
      - uses: actions/upload-artifact@v4
        with:
          name: pa11y-report
          path: ./pa11y-report.json

8.2 质量门禁的分级设计

工具门禁严格度失败策略适用阶段
axe-core高:零容忍 Serious+阻断 PR 合并持续集成阶段
Lighthouse CI中高:分数 < 90 触发阻断 PR 合并构建后审计
Pa11y低:报告但不阻断仅生成报告和 PR 评论整站定期巡航

上表展示了一个分层门禁的设计思路。axe-core 和 Lighthouse CI 承担"底线守卫"的角色——任何 Serious 级别的违规或低于阈值的分数都会导致合并被阻止。这种不可妥协的标准确保了新代码不会引入新的可访问性问题。Pa11y 则承担"全域扫描"的角色,它帮助团队发现和跟踪存量问题,但由于整站扫描的初始违规可能很多,将其设为"报告但不阻断"可以避免团队在初期陷入"无法合并任何代码"的僵局。

8.3 可访问性基线的建立与增量改进

对于从零开始推进 a11y 合规的团队,不建议一次性追求所有页面 100% 达标。更现实的路径是:

  1. 建立基线:使用 Pa11y 扫描全站,生成初始问题清单
  2. 划分优先级:P0(阻断用户使用的 Critical 问题)→ P1(严重影响体验)→ P2(建议优化)
  3. 核心流程优先:首页、登录、注册、支付等关键用户路径率先达标
  4. 增量门禁:新页面/新组件必须通过 axe-core 零违规,存量页面逐步整改
  5. 趋势跟踪:在 CI 报告中展示每月新增 / 修复问题的趋势图

8.4 团队培训:从编码阶段避免 a11y 问题

最高效的可访问性策略不是"发现后修复",而是"编码时就避免"。建议团队推行以下培训与实践:

  • 组件库内置 a11y:在内部 UI 组件库中强制所有组件通过 axe-core 校验(如 Ant Design、Radix UI、Headless UI 等现代库已将 a11y 作为第一优先级)
  • Code Review Checklist:在代码审查清单中加入 a11y 检查项(每个 <img> 有 alt,每个 <input> 有 label,自定义组件有正确 ARIA)
  • 自动化教育:在 PR 中通过 bot 评论输出 Lighthouse a11y 分数变化(“本次变更使 a11y 分数从 92 降低到 87,请检查新增的表单元素”)
  • 真实用户体验测试:每季度邀请视障用户参与产品可用性测试,将"用户的真实声音"带入团队视野

9. 可访问性测试的局限性与超越自动化

9.1 自动化工具的能力天花板

在本文的最后,我们必须坦诚地面对一个核心局限:即使是 axe-core、Lighthouse 和 Pa11y 三者同时使用,也只能覆盖大约 30% 的可访问性问题。这意味着 70% 的问题仍然隐藏在自动化的盲区中。

这些盲区包括:

  • 体验层面的流畅性:屏幕阅读器是否在用户执行操作后给出了恰当的反馈?
  • 内容的可理解性:文本是否使用了过于晦涩的专业术语?步骤说明是否清晰?
  • 自定义组件的行为一致性:一个"手风琴"组件在不同的浏览器和屏幕阅读器组合下是否表现一致?
  • 跨设备一致性:同一个页面在 iOS 的 VoiceOver 和 Android 的 TalkBack 下的导航体验是否等效?

9.2 用户研究和包容设计的终极价值

解决这些盲区的方法只有一个:让真实的用户参与测试。邀请视障用户、听障用户、运动障碍用户和认知障碍用户参与产品的原型评审和可用性测试,是获取"自动化无法发现的问题"的唯一途径。

更重要的是,从"合规达标"到"包容设计(Inclusive Design)“的心态转变。当可访问性仅仅被视为"避免被诉"的法律负担时,团队只会做最低限度的合规;但当团队真正理解"包容设计让产品对所有人更好"时,测试工作流将自然地从"检测缺陷"进化为"设计更好的体验”。

9.3 持续改进的旅程

可访问性不是一次性的项目,而是一个持续改进的旅程。WCAG 标准会更新(WCAG 3.0 已在制定中),产品的功能会扩展,新的交互模式(如语音界面、VR/AR)会带来新的挑战。建立可持续的可访问性测试文化,意味着将 a11y 检查嵌入每一次代码提交、每一次设计评审和每一次用户研究。

最终,可访问性测试所追求的不仅是分数的提升和法规的合规,而是让每个用户——无论他们的能力如何——都能平等、独立、有尊严地使用我们构建的数字产品。这不仅是一项工程责任,更是一种技术的人文关怀。

继续阅读

探索更多技术文章

浏览归档,发现更多关于系统设计、工具链和工程实践的内容。

全部文章 返回首页

「测试工程」更多文章

  1. 移动端自动化测试实战:Appium、Maestro 与 Detox 选型指南
  2. 混沌工程与韧性测试:在运行中验证系统自愈能力
  3. 可视化测试与视觉回归:像素级守卫 UI 质量