@a11y- oracle/ playwright- 插件
A11y-Oracle 的 Playwright 集成. 提供测试固定和包装类,通过Chrome DevTools协议读取浏览器的可访问树,发送本地键盘事件,并分析视觉焦点指标.
QQ 建议插件 . Playwright提供直接的一等CDP会话访问,使这成为A11y-Oracle最稳定和最能表现的集成. 如果您选择了 E2E 框架, 则使用此框架 .
import { test, expect } from '@a11y-oracle/playwright-plugin';
test('dropdown button announces correctly', async ({ page, a11y }) => {
await page.goto('/dropdown-nav.html');
const speech = await a11y.press('Tab');
expect(speech).toContain('Home');
expect(speech).toContain('menu item');
});
安装
npm install -D @a11y-oracle/playwright-plugin @a11y-oracle/core-engine @playwright/test
只有铬. Playwright的Firefox或WebKit不提供 CDP会话.
使用量
测试固定( 建议)
插件导出扩展名 test 函数 a11y 修补。 CDP会话在每次测试前创建,之后自动清理.
import { test, expect } from '@a11y-oracle/playwright-plugin';
test.describe('Navigation', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/my-page.html');
});
test('Tab to button announces name and role', async ({ a11y }) => {
const speech = await a11y.press('Tab');
expect(speech).toBe('Submit, button');
});
test('checkbox announces checked state', async ({ a11y }) => {
await a11y.press('Tab');
await a11y.press('Tab');
const speech = await a11y.press('Space');
expect(speech).toContain('checkbox');
expect(speech).toContain('checked');
});
test('navigation landmark exists', async ({ a11y }) => {
const tree = await a11y.getFullTreeSpeech();
const nav = tree.find(r => r.speech.includes('navigation landmark'));
expect(nav).toBeDefined();
});
});
统一国家
这个 pressKey() 方法返回完整 A11yState 将语音输出、重点要素信息和重点指标分析结合起来的快照:
test('focus indicator meets WCAG 2.4.12 AA', async ({ page, a11y }) => {
await page.goto('/my-page.html');
const state = await a11y.pressKey('Tab');
// Speech
expect(state.speech).toContain('Submit');
expect(state.speechResult?.role).toBe('button');
// Focused element
expect(state.focusedElement?.tag).toBe('BUTTON');
expect(state.focusedElement?.id).toBe('submit-btn');
expect(state.focusedElement?.tabIndex).toBe(0);
// Focus indicator CSS analysis
expect(state.focusIndicator.isVisible).toBe(true);
expect(state.focusIndicator.contrastRatio).toBeGreaterThanOrEqual(3.0);
expect(state.focusIndicator.meetsWCAG_AA).toBe(true);
});
test('Shift+Tab navigates backward', async ({ page, a11y }) => {
await page.goto('/my-page.html');
await a11y.pressKey('Tab');
const state1 = await a11y.pressKey('Tab');
const state2 = await a11y.pressKey('Tab', { shift: true });
expect(state2.focusedElement?.id).toBe(state1.focusedElement?.id);
});
标签顺序和键盘陷阱检测
test('page has correct tab order', async ({ page, a11y }) => {
await page.goto('/my-page.html');
const report = await a11y.traverseTabOrder();
expect(report.totalCount).toBeGreaterThan(0);
expect(report.entries[0].tag).toBe('A');
});
test('modal does not trap keyboard focus', async ({ page, a11y }) => {
await page.goto('/modal.html');
const result = await a11y.traverseSubTree('#modal-container', 20);
expect(result.isTrapped).toBe(false);
expect(result.escapeElement).not.toBeNull();
});
审计和问题报告
使用 OracleAuditor 从 @a11y-oracle/audit-formatter 自动检查 WCAG 对每次交互的规则并积累任何问题:
npm install -D @a11y-oracle/audit-formatter
import { test, expect } from '@a11y-oracle/playwright-plugin';
import { OracleAuditor } from '@a11y-oracle/audit-formatter';
test('all focus indicators pass oracle rules', async ({ page, a11y }) => {
await page.goto('/my-page.html');
const auditor = new OracleAuditor(a11y, {
project: 'my-app',
specName: 'navigation.spec.ts',
});
// Each pressKey() automatically checks all 5 state-based rules
await auditor.pressKey('Tab');
await auditor.pressKey('Tab');
await auditor.pressKey('Tab');
// checkTrap() automatically checks keyboard-trap
await auditor.checkTrap('#modal-container');
// Assert no issues found across all interactions
expect(auditor.getIssues()).toHaveLength(0);
});
在套件末尾将问题写入 JSON 报告文件:
import { test } from '@a11y-oracle/playwright-plugin';
import { OracleAuditor, type OracleIssue } from '@a11y-oracle/audit-formatter';
import * as fs from 'fs';
const allIssues: OracleIssue[] = [];
test('check page focus indicators', async ({ page, a11y }) => {
await page.goto('/my-page.html');
const auditor = new OracleAuditor(a11y, {
project: 'my-app',
specName: 'nav.spec.ts',
});
await auditor.pressKey('Tab');
await auditor.pressKey('Tab');
allIssues.push(...auditor.getIssues());
});
test.afterAll(() => {
if (allIssues.length > 0) {
fs.writeFileSync('oracle-results.json', JSON.stringify(allIssues, null, 2));
}
});
关于每项规则的详细补救指导意见,见 补救指南。 。 。 .
自定义选项
每个测试组覆盖语音引擎选项 test.use()数字 :
test.describe('without landmark suffix', () => {
test.use({ a11yOptions: { includeLandmarks: false } });
test('nav role without landmark', async ({ page, a11y }) => {
await page.goto('/my-page.html');
const tree = await a11y.getFullTreeSpeech();
const nav = tree.find(r => r.role === 'navigation');
expect(nav?.speech).toBe('Main, navigation');
});
});
可用的选项 :
| 选项 | 类型 | 默认 | 说明 |
|---|---|---|---|
includeLandmarks | boolean | true | 在标志性角色中添加"地印" |
includeDescription | boolean | false | 包含 aria-describedby 输出中的文本 |
focusSettleMs | number | 50 | 在按下焦点/ CSS 键后延迟 |
手动使用
如果需要对生命周期进行更多的控制(例如,附在某一页的中试),请使用 A11yOracle 直接分类 :
import { A11yOracle } from '@a11y-oracle/playwright-plugin';
import { test, expect } from '@playwright/test';
test('manual setup', async ({ page }) => {
await page.goto('/my-page.html');
const a11y = new A11yOracle(page, { includeDescription: true });
await a11y.init();
const speech = await a11y.press('Tab');
expect(speech).toContain('button');
await a11y.dispose();
});
API 参考
A11yOracle
管理 CDP 会话,并为当前页面提供访问测试.
constructor(page: Page, options?: A11yOrchestratorOptions)
创建新实例 .
page- 剧作家Page随附.options.includeLandmarks——将"地标"附录为地标性角色. 默认true。 。 。 .options.includeDescription- 包括说明案文。 默认false。 。 。 .options.focusSettleMs——按键后延迟焦点/CSS结算. 默认50。 。 。 .
init(): Promise<void>
打开 CDP 会话并启用无障碍域. 一定要在任何其他方法之前叫出来 测试固定器自动调用.
仅限发言的 API
press(key: string): Promise<string>
按键盘键( 通过 Playwright 键) page.keyboard.press()),并返回新聚焦元素的演讲. 如果没有元素焦点,则返回空字符串.
const speech = await a11y.press('Tab');
// "Products, button, collapsed"
getSpeech(): Promise<string>
获取当前焦点元素的语音字符串而不按键 .
getSpeechResult(): Promise<SpeechResult | null>
获取焦点元素的全部结构化结果.
getFullTreeSpeech(): Promise<SpeechResult[]>
在无障碍树上获取所有非被忽略的节点的语音.
统一国家
pressKey(key: string, modifiers?: ModifierKeys): Promise<A11yState>
通过本地 CDP 发送密钥 Input.dispatchKeyEvent 并返回统一无障碍状态。 与 press(),这使用硬件级别密钥发送并返回完整的状态.
const state = await a11y.pressKey('Tab');
// state.speech → "Products, button, collapsed"
// state.focusedElement → { tag: 'BUTTON', id: '...', ... }
// state.focusIndicator → { isVisible: true, meetsWCAG_AA: true, ... }
getA11yState(): Promise<A11yState>
获取当前统一状态而不按键 .
await page.focus('#my-button');
const state = await a11y.getA11yState();
traverseTabOrder(): Promise<TabOrderReport>
在 DOM 标签顺序中提取所有可制表元素 .
traverseSubTree(selector: string, maxTabs?: number): Promise<TraversalResult>
检测容器是否夹住键盘焦点(WCAG 2.1.2).
生命周期
dispose(): Promise<void>
解开社发委会会会会和免费资源. 测试固定器自动调用.
测试固定
这个 test 导出扩展 Playwright 的 test 有两个固定装置:
| 固定线 | 类型 | 说明 |
|---|---|---|
a11y | A11yOracle | 初始化实例, 每次测试后自动处理 |
a11yOptions | A11yOrchestratorOptions | 通过 test.use() |
出口
// Test fixture (recommended)
export { test, expect } from '@a11y-oracle/playwright-plugin';
// Manual usage
export { A11yOracle } from '@a11y-oracle/playwright-plugin';
// Fixture types
export type { A11yOracleFixtures } from '@a11y-oracle/playwright-plugin';
// Re-exported types from core-engine
export type {
A11yState,
A11yFocusedElement,
A11yFocusIndicator,
A11yOrchestratorOptions,
SpeechResult,
SpeechEngineOptions,
ModifierKeys,
TabOrderReport,
TabOrderEntry,
TraversalResult,
FocusIndicator,
} from '@a11y-oracle/playwright-plugin';
剧作家配置
插件需要铬. 一个典型 playwright.config.ts数字 :
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
baseURL: 'http://localhost:4200',
},
projects: [
{
name: 'chromium',
use: { browserName: 'chromium' },
},
],
webServer: {
command: 'npm run serve',
url: 'http://localhost:4200',
reuseExistingServer: !process.env.CI,
},
});
如何运作
- 固定会话通过
page.context().newCDPSession(page) - 它创造了两个
SpeechEngine还有一个A11yOrchestrator该届会议的报告 press(key)——使用 Playwright 的键盘 API,等待50ms,然后读取 AXTree 的语音pressKey(key)使用本地CDPInput.dispatchKeyEvent用于硬件级别发送, 等等focusSettleMs,然后同时收集语音+焦点要素+焦点指标- 重点指标分析
Runtime.evaluate读取计算 CSS 样式并计算对比率 - 在处置时, CPP 会话是分开的
语音格式如下: [Computed Name], [Role], [State/Properties]
关于角色和状态图的完整清单,见 @a11y- oracle/ 核心引擎 README。 。 。 .