我们如何使用 Playwright 和 Axe 自动化可访问性测试

发布: (2025年12月10日 GMT+8 21:18)
5 min read
原文: Dev.to

Source: Dev.to

我们的工具箱:Axe + Playwright

我们选择了 Axe,这是来自 Deque Systems 的开源库,作为我们的可访问性测试引擎。它提供了在浏览器中直接运行测试的 JavaScript API,而 @axe-core/playwright 包则让集成变得无缝。

因为我们已经在使用 Playwright 进行视觉回归测试和端到端套件,在此基础上添加可访问性检查是显而易见的下一步——无需学习新工具,只需在我们熟悉的 Playwright 工作流中加入 Axe 引擎即可。

配置

首先,我们创建了一个帮助函数来获取预配置好的 Axe 实例。我们的配置侧重于 WCAG 2.1 Level A 和 AA 标准。

WCAG 是什么? 网络内容可访问性指南(WCAG)由 W3C 制定,旨在让网络内容更易于访问。

  • Level A: 最低合规级别。
  • Level AA: 我们目标的中等级别,解决更高级的障碍。
  • Level AAA: 最高、最严格的级别。

我们还明确排除了一些我们无法直接控制的元素(例如第三方广告),以避免误报。

// /test/utils/axe.ts
import { Page } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';

export const getAxeInstance = (page: Page) => {
  return new AxeBuilder({ page })
    // Target WCAG 2.1 A and AA success criteria
    .withTags(['wcag2a', 'wcag2aa'])
    // Exclude elements we don't control, like ads
    .exclude('[id^="google_ads_iframe_"]')
    .exclude('#skinadvtop2')
    .exclude('#subito_skin_id');
};

实现:生成并保存报告

接下来,我们添加了一个 generateAxeReport 辅助函数,用于运行分析并将结果写入 JSON 文件。

// /test/utils/axe.ts
import { Page } from '@playwright/test';
import AxeBuilder from '@axe-core/playwright';
import { Result } from 'axe-core';
import * as fs from 'fs';
import * as path from 'path';
import { getAxeInstance } from './axe'; // assuming same file

export const generateAxeReport = async (
  name: string,
  page: Page,
  isMobile: boolean,
  includeSelector?: string
) => {
  let axe = getAxeInstance(page);

  // Optionally scope the analysis to a specific selector
  if (includeSelector) {
    axe = axe.include(includeSelector);
  }

  const results = await axe.analyze();
  const violations = results.violations;

  // Save the results to a JSON file
  await saveAccessibilityResults(name, violations, isMobile);

  return violations;
};

async function saveAccessibilityResults(
  fileName: string,
  violations: Array,
  isMobile: boolean
) {
  const outputDir = 'test/a11y/output';

  if (!fs.existsSync(outputDir)) {
    fs.mkdirSync(outputDir, { recursive: true });
  }

  const filePath = path.join(
    outputDir,
    `${fileName}-${isMobile ? 'mobile' : 'desktop'}.json`
  );

  // Map violations to a clean object for serialization
  const escapedViolations = violations.map((violation) => ({
    id: violation.id,
    impact: violation.impact,
    description: violation.description,
    help: violation.help,
    helpUrl: violation.helpUrl,
    nodes: violation.nodes,
  }));

  fs.writeFileSync(filePath, JSON.stringify(escapedViolations, null, 2));
  console.log(`Accessibility results saved to ${filePath}`);
}

可访问性测试

有了这些辅助函数,在任何 Playwright 测试中加入可访问性检查都非常简单。

// /test/a11y/example.spec.ts
import { test } from '@playwright/test';
import { generateAxeReport } from '../utils/axe';

test('check Login page', async ({ page }) => {
  await page.goto('/login_form');
  await page.waitForLoadState('domcontentloaded');

  // Run the helper
  await generateAxeReport('login-page', page, false);
});

运行测试后会生成一个 JSON 报告(例如 login-page-desktop.json),其中包含所有可访问性发现。

可访问性报告示例

与持续集成 (CI) 的集成

我们的 CI 工作流在每次 staging 部署时触发。它:

  1. 运行 对预定义关键页面的可访问性测试。
  2. 生成 JSON 报告。
  3. 在检测到违规时 更新或创建一个专门的 GitHub Issue 来记录结果。

自动创建的 Issue 如下所示:

带有可访问性报告的 GitHub Issue

以及违规的详细列表:

违规详情

为什么使用 GitHub Issue?(而不是让构建失败)

与我们的视觉回归测试不同,后者会打开 PR 并发送 Slack 通知,我们选择在 GitHub Issue 中记录可访问性发现。

我们仍在逐步建立可访问性覆盖率,如果对每个违规都让流水线失败将难以维系。通过 Issue:

  • 我们保留了可访问性债务的持久记录。
  • 仓库所有者负责对这些问题进行分拣、优先级排序和安排修复。

下面是一个示例 Pull Request,展示了如何处理之前在 GitHub Issue 中记录的某条违规。

(图片略)

Back to Blog

相关文章

阅读更多 »

第1283天:尝试与尝试

专业的相当轻松的一天。参加了几场会议,演示了我一直在做的工作,每次都很成功。我回复了一些社区的问题……