关于 a11y-playwright-testing
面向使用 Playwright、TypeScript 和 axe-core 的团队的无障碍测试技能,支持 WCAG 2.2 AA 扫描及键盘、焦点和语义检查。自动化检测不能替代完整合规审查,仍需人工测试。
主要功能
- 01axe-core WCAG 2.2 AA 扫描
- 02键盘导航与焦点管理测试
- 03语义结构和 ARIA 验证
- 04表单标签及错误提示检查
- 05颜色对比度与读屏器测试模式
适用场景
- 01检查页面和组件的 WCAG 违规
- 02验证对话框的焦点陷阱与返回
- 03测试表单键盘操作和标签
- 04为无障碍回归测试添加自动检查
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ fugazi/ test-automation-skills-agents --skill a11y-playwright-testing安装后在 Claude Code 中输入/a11y-playwright-testing确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
LICENSE.txtreferences/aria-patterns-mistakes.mdaria-patterns-widgets-1.mdaria-patterns-widgets-2.mdaxe-tags-reference.mdsnippets-keyboard-focus-semantic.mdsnippets-setup-and-scanning.mdsnippets-visual-names-checklist.mdwcag21aa-checklist.md详细介绍
Playwright 无障碍测试(TypeScript)
使用 Playwright、TypeScript 和 axe-core 进行自动化无障碍测试的综合工具包。支持验证是否符合 WCAG 2.2 AA 级标准(包含 WCAG 2.1 的全部要求),测试键盘操作能力、语义有效性,并防止无障碍回归。
激活条件: 在进行无障碍测试、WCAG 合规性检查、axe-core 扫描、键盘导航测试、焦点管理、ARIA 验证或屏幕阅读器兼容性测试时,会触发此技能。
何时使用此技能
- 使用 axe-core 进行自动化无障碍扫描,以检查是否符合 WCAG 2.2 AA 级标准
- 测试键盘导航,确保 Tab、Enter、Space、Escape 和方向键均可操作
- 验证对话框、菜单和动态内容的焦点管理
- 断言地标、标题和 ARIA 的语义结构
- 测试表单的无障碍性,包括标签、错误信息和说明
- 验证颜色对比度和视觉无障碍性
- 测试屏幕阅读器兼容性模式
请勿用于
- Selenium/Java 无障碍测试(请使用
accessibility-selenium-testing)。 - 编写 Playwright 功能/UI E2E 规范(请使用
playwright-e2e-testing)。 - 完整的符合性认证——自动化 axe 扫描只能发现约 30-40% 的问题;仍需进行人工审查和辅助技术测试。
前提条件
| 要求 | 详细信息 |
|---|---|
| Node.js | 建议使用 v18+ |
| Playwright | 已安装 @playwright/test |
| axe-core | @axe-core/playwright 软件包 |
| TypeScript | 已在项目中配置 |
快速设置
# Add axe-core to existing Playwright project
npm install -D @axe-core/playwright axe-core
首先要确认的问题
编写无障碍测试前,请先确认:
- 范围:哪些页面/流程在测试范围内?明确排除哪些内容?
- 标准:采用 WCAG 2.2 AA(默认)还是特定的组织政策?
- 优先级:哪些组件风险最高(表单、模态框、导航、结账)?
- 例外情况:是否存在已知限制(旧版标记、第三方小组件)?
- 辅助技术:哪些屏幕阅读器/浏览器需要进行人工测试?
核心原则
1. 自动化的局限性
[!] 重要: 自动化工具只能检测约 30-40% 的无障碍问题。使用自动化来防止回归并发现常见故障;要全面符合 WCAG 标准,必须进行人工审查。
2. 优先使用语义化 HTML
优先使用原生 HTML 语义,而不是 ARIA。只有原生元素无法实现所需语义时,才使用 ARIA。
// [ok] Semantic HTML - inherently accessible
await page.getByRole("button", { name: "Submit" }).click();
// [no] ARIA override - requires manual keyboard/focus handling
await page.locator('[role="button"]').click(); // Often a <div>
3. 定位器策略也是无障碍性信号
如果无法通过角色或标签定位元素,这通常意味着存在无障碍缺陷。
| 定位器成功示例 | 无障碍性信号 |
|---|---|
getByRole('button', { name: 'Submit' }) [ok] | 按钮具有无障碍名称 |
getByLabel('Email') [ok] | 输入框已正确标注 |
getByRole('navigation') [ok] | 地标存在 |
locator('.submit-btn') [!] | 可能缺少无障碍名称 |
关键工作流
自动化 Axe 扫描(WCAG 2.2 AA)
import AxeBuilder from "@axe-core/playwright";
import { test, expect } from "@playwright/test";
test("page has no WCAG 2.2 AA violations", async ({ page }) => {
await page.goto("/");
const results = await new AxeBuilder({ page })
.withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22a", "wcag22aa"])
.analyze();
expect(results.violations).toEqual([]);
});
限定范围的 Axe 扫描(组件级)
test("form component is accessible", async ({ page }) => {
await page.goto("/contact");
const results = await new AxeBuilder({ page })
.include("#contact-form") // Scope to specific component
.withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22a", "wcag22aa"])
.analyze();
expect(results.violations).toEqual([]);
});
键盘导航测试
test("form is keyboard navigable", async ({ page }) => {
await page.goto("/login");
// Tab to first field
await page.keyboard.press("Tab");
await expect(page.getByLabel("Email")).toBeFocused();
// Tab to password
await page.keyboard.press("Tab");
await expect(page.getByLabel("Password")).toBeFocused();
// Tab to submit button
await page.keyboard.press("Tab");
await expect(page.getByRole("button", { name: "Sign in" })).toBeFocused();
// Submit with Enter
await page.keyboard.press("Enter");
await expect(page).toHaveURL(/dashboard/);
});
对话框焦点管理
test("dialog traps and returns focus", async ({ page }) => {
await page.goto("/settings");
const trigger = page.getByRole("button", { name: "Delete account" });
// Open dialog
await trigger.click();
const dialog = page.getByRole("dialog");
await expect(dialog).toBeVisible();
// Focus should be inside dialog
await expect(dialog.getByRole("button", { name: "Cancel" })).toBeFocused();
// Tab should stay trapped in dialog
await page.keyboard.press("Tab");
await expect(dialog.getByRole("button", { name: "Confirm" })).toBeFocused();
await page.keyboard.press("Tab");
await expect(dialog.getByRole("button", { name: "Cancel" })).toBeFocused();
// Escape closes and returns focus to trigger
await page.keyboard.press("Escape");
await expect(dialog).toBeHidden();
await expect(trigger).toBeFocused();
});
跳过链接验证
test("skip link moves focus to main content", async ({ page }) => {
await page.goto("/");
// First Tab should focus skip link
await page.keyboard.press("Tab");
const skipLink = page.getByRole("link", { name: /skip to (main|content)/i });
await expect(skipLink).toBeFocused();
// Activating skip link moves focus to main
await page.keyboard.press("Enter");
await expect(page.locator('#main, [role="main"]').first()).toBeFocused();
});
POUR 原则参考
| 原则 | 重点领域 | 示例测试 |
|---|---|---|
| 可感知 | 替代文本、字幕、对比度、结构 | 图像替代文本、颜色对比度比值 |
| 可操作 | 键盘、焦点、时序、导航 | Tab 顺序、焦点可见性、跳过链接 |
| 可理解 | 标签、说明、错误、一致性 | 表单标签、错误消息、可预测的行为 |
| 健壮 | 有效的 HTML、ARIA、名称/角色/值 | 语义结构、无障碍名称 |
Axe-Core 标签
默认值:wcag2a、wcag2aa、wcag21a、wcag21aa、wcag22a、wcag22aa(WCAG 2.2 AA)。使用 best-practice 可进行额外检查。完整标签列表请参阅 references/axe-tags-reference.md。
异常处理
无法避免异常时:
- 缩小范围 - 仅限具体组件/路由
- 记录影响 - 涉及哪些 WCAG 标准,及对用户的影响
- 设置到期时间 - 负责人 + 修复日期
- 跟踪工单 - 链接到修复问题
// [no] Avoid: Global rule disable
new AxeBuilder({ page }).disableRules(["color-contrast"]);
// [ok] Better: Scoped exclusion with documentation
new AxeBuilder({ page })
.exclude("#third-party-widget") // Known issue: JIRA-1234, fix by Q2
.withTags(["wcag2a", "wcag2aa", "wcag21a", "wcag21aa", "wcag22a", "wcag22aa"])
.analyze();
故障排除
| 问题 | 原因 | 解决方案 |
|---|---|---|
| Axe 检测到 0 项违规,但应用未通过人工审计 | 自动化仅覆盖约 30-40% | 添加人工测试清单 |
| 动态内容出现误报 | 内容尚未完全呈现 | 等待状态稳定后再扫描 |
| 颜色对比度检测错误地未通过 | 背景图像/渐变 | 对已知误报使用 exclude |
| 无法按角色找到元素 | 缺少语义化 HTML | 修复标记 - 这是一个真实缺陷 |
| 焦点不可见 | 缺少 :focus 样式 | 添加可见的焦点指示器 CSS |
| 对话框焦点未被限制 | 缺少焦点限制逻辑 | 实现焦点限制(参见代码片段) |
| 跳过链接不起作用 | 目标缺少 tabindex="-1" | 为主要内容添加 tabindex |
CLI 快速参考
| 命令 | 描述 |
|---|---|
npx playwright test --grep "a11y" | 仅运行无障碍测试 |
npx playwright test --headed | 使用可见浏览器运行以便调试 |
npx playwright test --debug | 使用 Inspector 逐步执行 |
PWDEBUG=1 npx playwright test | 带暂停功能的调试模式 |
危险信号
- 将 axe 扫描干净视为完全符合 WCAG — 自动化仅覆盖约 30-40% 的标准。
- 全局禁用规则(例如
color-contrast),而不是使用有明确工单记录的限定范围.exclude()。 - 在页面状态稳定前扫描 — 异步内容会导致误报“0 项违规”。
- 因 axe 测试通过而跳过键盘/焦点测试 — 焦点顺序和焦点陷阱需要显式测试。
参考资料
| 文档 | 内容 |
|---|---|
| 代码片段:设置与扫描 | axe-core 设置、辅助函数和扫描模式 |
| 代码片段:键盘、焦点、语义 | 键盘导航、焦点管理、语义结构 |
| 代码片段:视觉、名称、清单 | 视觉无障碍、无障碍名称、关键页面 |
| WCAG 2.2 AA 清单 | 按 POUR 原则划分的手动审查清单 |
| ARIA 模式:小组件第 1 部分 | 基础知识、对话框、标签页、菜单小组件 |
| ARIA 模式:小组件第 2 部分 | 手风琴、组合框、实时区域、工具提示 |
| ARIA 模式:错误与参考 | 常见 ARIA 错误和角色速查参考 |
外部资源
| 资源 | URL |
|---|---|
| WCAG 2.2 规范 | https://www.w3.org/TR/WCAG22/ |
| WCAG 快速参考 | https://www.w3.org/WAI/WCAG22/quickref/ |
| WAI-ARIA 编写实践 | https://www.w3.org/WAI/ARIA/apg/ |
| axe-core 规则 | https://dequeuniversity.com/rules/axe/ |
验证
- axe-core 审计通过 —
AxeBuilder.analyze()返回的严重违规项数量为零 - 已测试键盘导航 — 所有交互元素都可通过 Tab 访问;焦点顺序符合逻辑
- 颜色对比度足够 — 符合 WCAG 2.2 AA 最低对比度要求(普通文本为 4.5:1,大号文本为 3:1)
- 符合 WCAG 2.2 AA — 扫描中包含标签
wcag22a/wcag22aa(焦点不被遮挡、拖动操作、目标尺寸下限)
评论
支持 Markdown