@a11y- oracle/ cypress- plugin (韩语)
A11y-Oracle的 Cypress 集成 . 提供自定义命令,通过Chrome DevTools协议读取浏览器的可访问性树,发送本地键盘事件,并分析视觉焦点指标.
describe('Navigation', () => {
beforeEach(() => {
cy.visit('/dropdown-nav.html');
cy.initA11yOracle();
});
afterEach(() => {
cy.disposeA11yOracle();
});
it('Tab to button announces name and role', () => {
cy.a11yPress('Tab')
.should('contain', 'Home')
.and('contain', 'menu item');
});
});
安装
npm install -D @a11y-oracle/cypress-plugin @a11y-oracle/core-engine cypress
仅铬/铬. 该插件使用CDP,它只在Chrome-family浏览器中提供.
QQ稳定通知——剧作家推荐. Cypress插件具有功能性,但已经知道带有大型测试套件的稳定性限制. 我们建议使用
@a11y-oracle/playwright-plugin对于最可靠的经验。 见 已知的限制 详情见下文.
设置
1. 进口命令
将插件添加到您的 Cypress 支持文件 :
// cypress/support/e2e.ts (or cypress/support/e2e.js)
import '@a11y-oracle/cypress-plugin';
就是这样。 正在自动导入包登记所有自定义命令 .
2. 配置浏览器
保证您的 Cypress 配置在 Chrome 中运行测试 :
// cypress.config.ts
import { defineConfig } from 'cypress';
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:4200',
specPattern: 'cypress/e2e/**/*.cy.ts',
supportFile: 'cypress/support/e2e.ts',
},
});
与 Chrome 运行 :
npx cypress run --browser chrome
使用量
发言请求
describe('My Form', () => {
beforeEach(() => {
cy.visit('/form.html');
cy.initA11yOracle();
});
afterEach(() => {
cy.disposeA11yOracle();
});
it('Tab navigates to submit button', () => {
cy.a11yPress('Tab');
cy.a11yPress('Tab');
cy.a11yPress('Tab')
.should('equal', 'Submit, button');
});
it('checkbox announces checked state', () => {
cy.a11yPress('Tab');
cy.a11yPress('Space')
.should('contain', 'checkbox')
.should('contain', 'checked');
});
});
统一国家(言论+焦点+指标)
it('Tab returns unified accessibility state', () => {
cy.a11yPressKey('Tab').then((state) => {
// Speech
expect(state.speech).to.contain('Submit');
expect(state.speechResult?.role).to.equal('button');
// Focused element
expect(state.focusedElement?.tag).to.equal('BUTTON');
expect(state.focusedElement?.id).to.equal('submit-btn');
// Focus indicator (WCAG 2.4.12 AA)
expect(state.focusIndicator.isVisible).to.be.true;
expect(state.focusIndicator.meetsWCAG_AA).to.be.true;
});
});
it('Shift+Tab navigates backward', () => {
cy.a11yPressKey('Tab');
cy.a11yPressKey('Tab');
cy.a11yPressKey('Tab', { shift: true }).then((state) => {
expect(state.focusedElement).to.not.be.null;
});
});
标签顺序和键盘陷阱检测
it('page has correct tab order', () => {
cy.a11yTraverseTabOrder().then((report) => {
expect(report.totalCount).to.be.greaterThan(0);
expect(report.entries[0].tag).to.equal('A');
});
});
it('modal does not trap keyboard focus', () => {
cy.a11yTraverseSubTree('#modal-container', 20).then((result) => {
expect(result.isTrapped).to.be.false;
expect(result.escapeElement).to.not.be.null;
});
});
问题报告
检查焦点指标和键盘陷阱,自动报告问题。 问题通过 cy.task('logOracleIssues') 并且可以在运行结束时写入JSON报告.
describe('Accessibility audit', () => {
beforeEach(() => {
cy.visit('/my-page.html');
cy.initA11yOracle();
});
afterEach(() => {
cy.disposeA11yOracle();
});
it('focus indicators pass oracle rules', () => {
cy.a11yPressKey('Tab');
cy.a11yCheckFocusAndReport(); // checks + reports issues
cy.a11yPressKey('Tab');
cy.a11yCheckFocusAndReport(); // check each focused element
});
it('modal is not a keyboard trap', () => {
cy.get('#open-modal').click();
cy.a11yCheckTrapAndReport('#modal-dialog', 10);
});
});
WCAG 级别和规则配置
通过 WCAG 兼容级别过滤出问题或通过 Cypress env 禁用特定规则:
// cypress.config.ts
export default defineConfig({
e2e: {
env: {
wcagLevel: 'wcag21aa', // WCAG 2.1 Level AA (default: 'wcag22aa')
disabledRules: ['oracle/positive-tabindex'], // Suppress specific rules
},
},
});
支助 wcagLevel 值( 匹配轴- 核心标记格式) :
'wcag2a'编号 :'wcag2aa'- WCAG 2.0'wcag21a'编号 :'wcag21aa'- 《世界保护法院规约》2.1'wcag22a'编号 :'wcag22aa'- WCAG 2.2(违约)
或覆盖每个命令 :
cy.a11yCheckFocusAndReport({ wcagLevel: 'wcag22a' });
cy.a11yCheckFocusAndReport({ disabledRules: ['oracle/focus-low-contrast'] });
设定 Cypress.env('failOnErrors') 改为 true 在发现问题时立即失败的测试:
// cypress.config.ts
export default defineConfig({
e2e: {
env: { failOnErrors: true },
},
});
关于每项规则的详细补救指导意见,见 补救指南。 。 。 .
节点系统报告设置
在所有层面累积问题并编写 JSON 报告文件, 调用 setupOracleReporting() 在您的 Cypress 配置中 :
// cypress.config.ts
import { defineConfig } from 'cypress';
import { setupOracleReporting } from '@a11y-oracle/cypress-plugin';
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
setupOracleReporting(on, config);
},
env: { projectName: 'my-app' },
},
});
登记 logOracleIssues 任务和写入 oracle-results-{projectName}.json 运行完成后.
与轴心侵犯行为相结合: 如果您想要在与您的轴- 核心违反相同的阵列中出现甲骨文问题( 仅上传到灯塔), 请手动添加任务处理器 :
setupNodeEvents(on, config) {
const allViolations: any[] = [];
on('task', {
logAxeViolations(violations) { allViolations.push(...violations); return null; },
logOracleIssues(issues) { allViolations.push(...issues); return null; },
});
on('after:run', () => {
if (allViolations.length > 0) {
fs.writeFileSync('a11y-results.json', JSON.stringify(allViolations, null, 2));
}
});
},
使用地标和结构
使用 getA11yFullTreeSpeech() 检查没有焦点的元素:
it('navigation landmark exists', () => {
cy.getA11yFullTreeSpeech().then((tree) => {
const nav = tree.find(r => r.speech.includes('navigation landmark'));
expect(nav).to.exist;
expect(nav.speech).to.contain('Main');
});
});
结构化演讲结果
使用 getA11ySpeechResult() 访问语音输出的各个部分:
it('returns structured data', () => {
cy.a11yPress('Tab');
cy.getA11ySpeechResult().then((result) => {
expect(result).to.not.be.null;
expect(result.name).to.equal('Home');
expect(result.role).to.contain('menu item');
expect(result.states).to.be.an('array');
expect(result.rawNode).to.exist;
});
});
配置选项
将选项传递到 initA11yOracle() 自定义行为 :
// Include descriptions from aria-describedby
cy.initA11yOracle({ includeDescription: true });
// Disable "landmark" suffix on landmark roles
cy.initA11yOracle({ includeLandmarks: false });
// Adjust focus settle delay for slow CSS transitions
cy.initA11yOracle({ focusSettleMs: 100 });
| 选项 | 类型 | 默认 | 说明 |
|---|---|---|---|
includeLandmarks | boolean | true | 在标志性角色中添加"地印" |
includeDescription | boolean | false | 包含 aria-describedby 输出中的文本 |
focusSettleMs | number | 50 | 在按下焦点/ CSS 键后延迟 |
API 参考
语音命令
cy.initA11yOracle(options?)
初始化插件 。 必须在之后调用 cy.visit() 在A11y-Oracle命令之前 典型的调用 beforeEach()。 。 。 .
启用 CDP 域,发现 AUT iframe,创建语音引擎和管弦乐器.
cy.a11yPress(key)
通过 CDP 按下键盘键,并返回新聚焦元素的语音. Yi取一弦.
cy.a11yPress('Tab').should('contain', 'button');
cy.a11yPress('Enter').should('contain', 'expanded');
cy.a11yPress('Escape').should('contain', 'collapsed');
支持的密钥 : Tab, (中文(简体) ), Enter, (中文(简体) ), Space, (中文(简体) ), Escape, (中文(简体) ), ArrowUp, (中文(简体) ), ArrowDown, (中文(简体) ), ArrowLeft, (中文(简体) ), ArrowRight, (中文(简体) ), Home, (中文(简体) ), End, (中文(简体) ), Backspace, (中文(简体) ), Delete。 。 。 .
cy.getA11ySpeech()
获取当前焦点元素的语音字符串而不按键 。 Yi取一弦.
cy.getA11ySpeechResult()
获取焦点元素的全部结构化结果. 也门 SpeechResult | null。 。 。 .
cy.getA11yFullTreeSpeech()
在无障碍树上获取所有非被忽略的节点的语音. 也门 SpeechResult[]。 。 。 .
国家统一指挥部
cy.a11yPressKey(key, modifiers?)
通过本地 CDP 发送按下一个密钥,并返回统一无障碍状态. 也门 A11yState。 。 。 .
cy.a11yPressKey('Tab').then((state) => {
expect(state.speech).to.contain('button');
expect(state.focusIndicator.meetsWCAG_AA).to.be.true;
});
// With modifier keys
cy.a11yPressKey('Tab', { shift: true }).then((state) => {
expect(state.focusedElement).to.not.be.null;
});
cy.a11yState()
在不按键的情况下获得当前统一无障碍状态. 也门 A11yState。 。 。 .
cy.get('#my-button').focus();
cy.a11yState().then((state) => {
expect(state.speech).to.contain('Submit');
});
cy.a11yTraverseTabOrder()
在 DOM 标签顺序中提取所有可制表元素 。 也门 TabOrderReport。 。 。 .
cy.a11yTraverseTabOrder().then((report) => {
expect(report.totalCount).to.be.greaterThan(0);
});
cy.a11yTraverseSubTree(selector, maxTabs?)
检测容器是否夹住键盘焦点(WCAG 2.1.2)。 也门 TraversalResult。 。 。 .
cy.a11yTraverseSubTree('#modal', 20).then((result) => {
expect(result.isTrapped).to.be.false;
});
报告命令
cy.a11yCheckFocusAndReport(context?)
检查当前重点要素,并通过 cy.task('logOracleIssues')。运行所有基于州的规则: oracle/focus-not-visible, (中文(简体) ), oracle/focus-low-contrast, (中文(简体) ), oracle/focus-missing-name, (中文(简体) ), oracle/focus-generic-role,以及 oracle/positive-tabindex。 。 。 .
context- 可选Partial<AuditContext>默认为{ project: Cypress.env('projectName'), specName: Cypress.spec.name, wcagLevel: Cypress.env('wcagLevel'), disabledRules: Cypress.env('disabledRules') }。 。 。 .
cy.a11yCheckTrapAndReport(selector, maxTabs?, context?)
检查一个容器中的键盘陷阱并报告任何问题 cy.task('logOracleIssues')。 。 。 .
selector用于测试容器的CSS选择器.maxTabs——最大标签在宣布陷阱前按下. 默认50。 。 。 .context- 可选Partial<AuditContext>。 。 。 .
setupOracleReporting(on, config)
节点侧函数(不是 Cypress 命令). 进来吧 setupNodeEvents() 登记 logOracleIssues 任务和撰写 JSON 报告 after:run。见 节点系统报告设置。 。 。 .
import { setupOracleReporting } from '@a11y-oracle/cypress-plugin';
CDP 适应器
createCypressCDPAdapter()
创建现成的使用 CDPSessionLike 用于自定义集成的适配器. 当您需要打电话时,这个是有用的 @a11y-oracle/axe-bridge是因为 resolveAllIncomplete() 直接而不是使用内置 Cypress 命令.
该函数囊括所有 CDP 管道:
- 启用
DOM.enable财务报告和审定财务报表Page.enableCDP 域数 - 发现 AUT 框架 ID 从
Page.getFrameTree - 在 AUT 框架中创建孤立的世界执行上下文
- 检测到 Iframe 位置和 CSS 变换规模
- 回返a
CDPSessionLike具有规模意识的适配器Page.captureScreenshot坐标翻译
import { createCypressCDPAdapter } from '@a11y-oracle/cypress-plugin';
import { resolveAllIncomplete } from '@a11y-oracle/axe-bridge';
// In your custom Cypress command:
cy.wrap(null).then(async () => {
const cdp = await createCypressCDPAdapter();
const resolved = await resolveAllIncomplete(cdp, axeResults, options);
});
生命周期
cy.disposeA11yOracle()
处理插件并释放 CDP 资源. 典型的调用 afterEach()。 。 。 .
类型
类型从 @a11y-oracle/core-engine 财务报告和审定财务报表 @a11y-oracle/audit-formatter数字 :
import type {
// Core engine types
CDPSessionLike,
SpeechResult,
A11yState,
A11yFocusedElement,
A11yFocusIndicator,
A11yOrchestratorOptions,
ModifierKeys,
TabOrderReport,
TabOrderEntry,
TraversalResult,
FocusIndicator,
// Audit formatter types
OracleIssue,
OracleNode,
OracleCheck,
OracleImpact,
OracleResultType,
AuditContext,
} from '@a11y-oracle/cypress-plugin';
如何运作
Cypress 插件使用浏览器侧式 CDP 方法通过 Cypress.automation('remote:debugger:protocol') - 与下列情况相同的模式: cypress-real-events 活动。需要节点边任务或外部库.
Cypress Iframe 架构
Cypress在测试(AUT)下运行该应用在其跑取页内的iframe内. 这造成了挑战: CDP 命令默认瞄准跑者页面,而不是AUT. 插件以透明方式处理 :
-
框架发现 - 打开
initA11yOracle(),插件调用Page.getFrameTree()以列举所有框架。 它通过过滤出 Cypress 内部帧来识别 AUT 框架(/__/, (中文(简体) ),__cypress, (中文(简体) ),about:blank) (中文(简体) ). -
框架范围访问 —— CDP 适配器注入 AUT
frameId输入Accessibility.getFullAXTree()调用,确保无障碍树被范围化为应用,而不是跑取UI. -
孤立的执行背景 为:
Runtime.evaluate调用(焦点指标分析、标签顺序、陷阱检测),插件通过Page.createIsolatedWorld这个孤立的世界拥有同样的DOM(包括:document.activeElement,计算样式等)但有自己的JavaScript范围,确保评价针对AUT内容. -
屏幕截图坐标翻译 —— 说
getBoundingClientRect()在 AUT iframe 内返回 iframe 相对坐标, 但是Page.captureScreenshot来自顶级浏览器取景端的剪辑. 插件查询了AUT iframe在取景口的位置,并相应抵消了所有的截图剪辑坐标. 它还能检测到CSS的变换尺度,Cypress适用于AUT Iframe包装机(一般为~0.66x在无头电子中),并将其应用于坐标,维度,和剪辑scale属性。 这保证了像素级分析(颜色对比,焦点指标diffing),即使在AUT以缩放的尺寸提供时也能捕捉到正确的区域. -
重点管理 — 每次键盘事件之前,插件使用
DOM.focus()在AUT iframe 元素上这样Input.dispatchKeyEvent到达正确的框架.
CDP 流量
cy.a11yPressKey('Tab')
│
├─ DOM.focus() on AUT iframe
├─ Input.dispatchKeyEvent (keyDown + keyUp)
├─ 50ms delay for focus/ARIA state updates
├─ Accessibility.getFullAXTree({ frameId: autFrameId })
├─ Runtime.evaluate({ contextId: autContextId }) — focused element
├─ Runtime.evaluate({ contextId: autContextId }) — focus indicator CSS
├─ Find focused node in AXTree → speech string
└─ Assemble A11yState { speech, focusedElement, focusIndicator }
类型脚本
该插件将增强 Cypress.Chainable 接口。 TypeScript 在导入支持文件中的插件时会自动取取命令类型.
如果您在测试文件中需要类型而不导入支持文件 :
/// <reference types="@a11y-oracle/cypress-plugin" />
已知的限制
CDP 长期试验服中的资源积累
Cypress在测试(AUT)下运行应用程序,运行在运行器页内的iframe. 要与 AUT 的无障碍树交互,插件必须创建 单独处决世界 在AUT框架中通过 Page.createIsolatedWorld 每页 initA11yOracle() 电话 与提供本土一等CDP会话的Playwright不同,Cypress的iframe架构意味着这些被孤立的世界会积累出Chrome没有完全恢复的浏览器侧面资源,即使在之后 disposeA11yOracle() 清净其自所引.
这在实践中意味着什么:
- 包含多个 spec文件的测试套件或每个文件的许多测试,在运行过程中可能会遇到越来越多的内存压力.
- 在v1.3.0和早些时候,这导致大约16起死亡事件之后的确定性绞刑
init编号 :dispose周期,因为Accessibility.getFullAXTree当节点横跨所有累积的上下文时(#14号) (中文(简体) ). - v1.3.1通过妥善地摧毁被处置的被孤立的世界来缓解绞刑,但潜在的建筑制约——Cypress代理所有CDP通过跑道呼叫并管理与Playwright不同的帧环境——依然存在.
建议: 如果您正在启动一个新项目, 或者您可以灵活选择您的 E2E 框架, 请使用 @a11y-oracle/playwright-plugin. Playwright提供直接的CDP会话访问而无需iframe间接,使其内在的稳定性和性能更能为A11y-Oracle的CDP繁忙工作流程所提供.
如果你需要留在Cypress上,插件是完全功能性的——只需意识到这些对非常大的套房的限制.
解决问题
"无法找到AUT Iframe"
此错误意味着 initA11yOracle() 之前被调用过 cy.visit()。在Cypress装入一页之前,AUT iframe是不存在的.
// Wrong
cy.initA11yOracle();
cy.visit('/page.html');
// Correct
cy.visit('/page.html');
cy.initA11yOracle();
语音输出包含 Cypress UI 元素
如果断言符合Cypress跑道元素(如"停止,按钮"或"可选,按钮"),帧扫描可能失败了. 确保:
- 你在克罗姆跑步
--browser chrome(中文(简体) ) cy.visit()完成时间cy.initA11yOracle()- AUT URL 不是一个
about:blank
空语音字符串
a11yPress() 当元素在按下键后没有焦点时,返回空字符串。 如果:
- 键盘将焦点移出页面外( 例如 Tab 超过最后一个元素)
- 重点要素
role="presentation"或者说role="none" - 重点要素是
RootWebArea(文件正文)
重点指标显示 contrastRatio: null
这发生在焦点指标颜色无法可靠地解析时. 常见原因:
- 复杂的 CSS 颜色函数( R)
color-mix(), (中文(简体) ),hsl(),命名颜色) - 多层梯度作为重点指标
currentColor作为大纲颜色
关于角色和状态图的完整清单,见 @a11y- oracle/ 核心引擎 README。 。 。 .