关于 playwright-testing
面向使用 Playwright 的团队,提供浏览器无障碍测试参考与测试模板。涵盖键盘、动态状态、视口、对比度和无障碍树检查,并说明 axe-core 的使用方式。
主要功能
- 01键盘顺序与陷阱检测
- 02交互后页面状态扫描
- 03多视口与触控目标检查
- 04计算样式对比度扫描
- 05浏览器无障碍树快照
适用场景
- 01检查网页键盘导航和焦点管理
- 02验证弹窗及手风琴展开后的内容
- 03在 CI 中运行无障碍测试
MCP 相关
README 列出了可用的 MCP 工具,用于键盘扫描、状态扫描、视口扫描、对比度扫描和无障碍树快照。部分扫描需要 @axe-core/playwright。
安装
复制下面的命令,在终端中运行。
$
npx skills add https:/ / github.com/ community-access/ accessibility-agents --skill playwright-testing安装后在 Claude Code 中输入/playwright-testing确认已加载。
详细介绍
Playwright 无障碍测试
使用 Playwright 和 @axe-core/playwright 进行基于浏览器的无障碍测试的可复用知识模块。
可用的 MCP 工具
| 工具 | 用途 | 是否需要 @axe-core/playwright |
|---|---|---|
run_playwright_keyboard_scan | Tab 顺序遍历、键盘陷阱检测 | 否 |
run_playwright_state_scan | 点击触发器,使用 axe-core 扫描显示的内容 | 是 |
run_playwright_viewport_scan | 多视口 axe-core 扫描 + 触控目标测量 | 是 |
run_playwright_contrast_scan | CSS 级联后的计算样式对比度 | 否 |
run_playwright_a11y_tree | 浏览器无障碍树快照 | 否 |
@axe-core/playwright 模式
全页面扫描
import AxeBuilder from '@axe-core/playwright';
const results = await new AxeBuilder({ page })
.withTags(['wcag2a', 'wcag2aa', 'wcag21a', 'wcag21aa', 'wcag22aa'])
.analyze();
指定范围的元素扫描
const results = await new AxeBuilder({ page })
.include('.modal-content')
.withTags(['wcag2a', 'wcag2aa'])
.analyze();
单条规则验证
const results = await new AxeBuilder({ page })
.include('#hero-image')
.withRules(['image-alt'])
.analyze();
expect(results.violations).toEqual([]);
交互后扫描
await page.click('[aria-expanded="false"]');
await page.waitForSelector('.accordion-content', { state: 'visible' });
const results = await new AxeBuilder({ page })
.include('.accordion-content')
.analyze();
键盘遍历模式
记录 Tab 顺序
const tabStops = [];
for (let i = 0; i < maxTabs; i++) {
await page.keyboard.press('Tab');
const info = await page.evaluate(() => {
const el = document.activeElement;
return {
tagName: el?.tagName,
role: el?.getAttribute('role'),
name: el?.getAttribute('aria-label') || el?.textContent?.trim().slice(0, 50),
id: el?.id,
tabIndex: el?.tabIndex
};
});
tabStops.push(info);
}
检测键盘陷阱
连续按下 Tab 后,若同一元素再次获得焦点,则检测到键盘陷阱:
let trapCount = 0;
let lastSelector = '';
for (const stop of tabStops) {
const currentSelector = `${stop.tagName}#${stop.id}`;
if (currentSelector === lastSelector) {
trapCount++;
if (trapCount >= 3) { /* TRAP DETECTED */ }
} else {
trapCount = 0;
}
lastSelector = currentSelector;
}
模态框打开后的焦点管理
await page.click('[data-modal-trigger]');
await page.waitForSelector('[role="dialog"]', { state: 'visible' });
const focusedRole = await page.evaluate(() =>
document.activeElement?.closest('[role="dialog"]') ? 'inside-dialog' : 'outside-dialog'
);
// focusedRole should be 'inside-dialog'
焦点管理测试模板
模态框焦点陷阱测试
test('modal traps focus correctly', async ({ page }) => {
await page.goto(url);
await page.click('[data-open-modal]');
await page.waitForSelector('[role="dialog"]', { state: 'visible' });
// Focus should be inside the dialog
const inDialog = await page.evaluate(() =>
document.activeElement?.closest('[role="dialog"]') !== null
);
expect(inDialog).toBe(true);
// Tab through dialog — should not escape
for (let i = 0; i < 20; i++) {
await page.keyboard.press('Tab');
const stillInDialog = await page.evaluate(() =>
document.activeElement?.closest('[role="dialog"]') !== null
);
expect(stillInDialog).toBe(true);
}
// Escape should close and return focus to trigger
await page.keyboard.press('Escape');
const focusedId = await page.evaluate(() => document.activeElement?.id);
expect(focusedId).toBe('modal-trigger-id');
});
跳过链接测试
test('skip link moves focus to main content', async ({ page }) => {
await page.goto(url);
await page.keyboard.press('Tab'); // Focus skip link
await page.keyboard.press('Enter'); // Activate it
const focusedId = await page.evaluate(() => document.activeElement?.id);
expect(focusedId).toBe('main-content');
});
CI 集成
使用 Playwright 的 GitHub Actions
name: Accessibility Tests
on: [push, pull_request]
jobs:
a11y:
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
- name: Start dev server
run: npm run dev &
env:
CI: true
- name: Wait for server
run: npx wait-on http://localhost:3000 --timeout 30000
- name: Run accessibility tests
run: npx playwright test tests/a11y/
- uses: actions/upload-artifact@v4
if: failure()
with:
name: a11y-test-results
path: test-results/
无障碍测试的 Playwright 配置
// playwright.config.js (a11y section)
export default {
testDir: './tests/a11y',
use: {
baseURL: process.env.BASE_URL || 'http://localhost:3000',
// Use Chromium only — @axe-core/playwright is Chromium-validated
browserName: 'chromium',
},
webServer: {
command: 'npm run dev',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
};
优雅降级
检测模式
let _playwrightAvailable = null;
async function isPlaywrightAvailable() {
if (_playwrightAvailable !== null) return _playwrightAvailable;
try {
await import('playwright');
_playwrightAvailable = true;
} catch {
_playwrightAvailable = false;
}
return _playwrightAvailable;
}
降级矩阵
| Playwright | @axe-core/playwright | 可用扫描 |
|---|---|---|
| 是 | 是 | 全部 5 种工具(键盘、状态、视口、对比度、树) |
| 是 | 否 | 3 种工具(键盘、对比度、树) |
| 否 | — | 无 — 改用代码审查 + axe-core CLI |
面向用户的消息
不可用时:
Playwright not installed. Behavioral testing (keyboard traversal, dynamic states,
responsive viewport, rendered contrast) is unavailable.
Install: npm install -D playwright @axe-core/playwright && npx playwright install chromium
部分可用时:
@axe-core/playwright not installed. State scanning and viewport scanning are
unavailable. Keyboard, contrast, and accessibility tree scans will proceed.
Install: npm install -D @axe-core/playwright
WCAG 覆盖范围映射
| WCAG SC | 描述 | Playwright 工具 |
|---|---|---|
| 1.3.1 | 信息和关系 | 无障碍树、状态扫描 |
| 1.4.3 | 对比度(最低) | 对比度扫描 |
| 1.4.6 | 对比度(增强) | 对比度扫描 |
| 1.4.10 | 重排 | 视口扫描 |
| 2.1.1 | 键盘 | 键盘扫描 |
| 2.1.2 | 无键盘陷阱 | 键盘扫描 |
| 2.4.3 | 焦点顺序 | 键盘扫描 |
| 2.4.7 | 焦点可见 | 键盘扫描 |
| 2.5.5 | 目标尺寸(增强) | 视口扫描 |
| 2.5.8 | 目标尺寸(最低) | 视口扫描 |
| 4.1.2 | 名称、角色、值 | 无障碍树、状态扫描 |
评论
支持 Markdown