@a11y-oracle/cypress-plugin 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +26 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -0
- package/dist/lib/commands.d.ts +25 -1
- package/dist/lib/commands.d.ts.map +1 -1
- package/dist/lib/commands.js +81 -7
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -412,6 +412,30 @@ Node-side function (not a Cypress command). Call inside `setupNodeEvents()` to r
|
|
|
412
412
|
import { setupOracleReporting } from '@a11y-oracle/cypress-plugin';
|
|
413
413
|
```
|
|
414
414
|
|
|
415
|
+
### CDP Adapter
|
|
416
|
+
|
|
417
|
+
#### `createCypressCDPAdapter()`
|
|
418
|
+
|
|
419
|
+
Create a ready-to-use `CDPSessionLike` adapter for custom integrations. This is useful when you need to call `@a11y-oracle/axe-bridge`'s `resolveAllIncomplete()` directly instead of using the built-in Cypress commands.
|
|
420
|
+
|
|
421
|
+
The function encapsulates all the CDP plumbing:
|
|
422
|
+
- Enables `DOM.enable` and `Page.enable` CDP domains
|
|
423
|
+
- Discovers the AUT frame ID from `Page.getFrameTree`
|
|
424
|
+
- Creates an isolated world execution context in the AUT frame
|
|
425
|
+
- Detects iframe position and CSS transform scale
|
|
426
|
+
- Returns a `CDPSessionLike` adapter with scale-aware `Page.captureScreenshot` coordinate translation
|
|
427
|
+
|
|
428
|
+
```typescript
|
|
429
|
+
import { createCypressCDPAdapter } from '@a11y-oracle/cypress-plugin';
|
|
430
|
+
import { resolveAllIncomplete } from '@a11y-oracle/axe-bridge';
|
|
431
|
+
|
|
432
|
+
// In your custom Cypress command:
|
|
433
|
+
cy.wrap(null).then(async () => {
|
|
434
|
+
const cdp = await createCypressCDPAdapter();
|
|
435
|
+
const resolved = await resolveAllIncomplete(cdp, axeResults, options);
|
|
436
|
+
});
|
|
437
|
+
```
|
|
438
|
+
|
|
415
439
|
### Lifecycle
|
|
416
440
|
|
|
417
441
|
#### `cy.disposeA11yOracle()`
|
|
@@ -425,6 +449,7 @@ Types are re-exported from `@a11y-oracle/core-engine` and `@a11y-oracle/audit-fo
|
|
|
425
449
|
```typescript
|
|
426
450
|
import type {
|
|
427
451
|
// Core engine types
|
|
452
|
+
CDPSessionLike,
|
|
428
453
|
SpeechResult,
|
|
429
454
|
A11yState,
|
|
430
455
|
A11yFocusedElement,
|
|
@@ -459,7 +484,7 @@ Cypress runs the app under test (AUT) inside an iframe within its runner page. T
|
|
|
459
484
|
|
|
460
485
|
3. **Isolated execution context** — For `Runtime.evaluate` calls (focus indicator analysis, tab order, trap detection), the plugin creates an isolated world in the AUT frame via `Page.createIsolatedWorld`. This isolated world shares the same DOM (including `document.activeElement`, computed styles, etc.) but has its own JavaScript scope, ensuring evaluations target the AUT content.
|
|
461
486
|
|
|
462
|
-
4. **Screenshot coordinate translation** — `getBoundingClientRect()` inside the AUT iframe returns iframe-relative coordinates, but `Page.captureScreenshot` clips from the top-level browser viewport. The plugin queries the AUT iframe's position in the viewport and offsets all screenshot clip coordinates accordingly. This ensures pixel-level analysis (color contrast, focus indicator diffing) captures the correct region.
|
|
487
|
+
4. **Screenshot coordinate translation** — `getBoundingClientRect()` inside the AUT iframe returns iframe-relative coordinates, but `Page.captureScreenshot` clips from the top-level browser viewport. The plugin queries the AUT iframe's position in the viewport and offsets all screenshot clip coordinates accordingly. It also detects the CSS transform scale that Cypress applies to the AUT iframe wrapper (typically ~0.66x in headless Electron), and applies it to coordinates, dimensions, and the clip's `scale` property. This ensures pixel-level analysis (color contrast, focus indicator diffing) captures the correct region even when the AUT is rendered at a scaled size.
|
|
463
488
|
|
|
464
489
|
5. **Focus management** — Before each keyboard event, the plugin uses `DOM.focus()` on the AUT iframe element so that `Input.dispatchKeyEvent` reaches the correct frame.
|
|
465
490
|
|
package/dist/index.d.ts
CHANGED
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
* - Cypress >= 12.0.0
|
|
40
40
|
*/
|
|
41
41
|
import './lib/commands.js';
|
|
42
|
-
export type { SpeechResult, SpeechEngineOptions, A11yState, A11yFocusedElement, A11yFocusIndicator, A11yOrchestratorOptions, ModifierKeys, TabOrderReport, TabOrderEntry, TraversalResult, FocusIndicator, } from '@a11y-oracle/core-engine';
|
|
42
|
+
export type { CDPSessionLike, SpeechResult, SpeechEngineOptions, A11yState, A11yFocusedElement, A11yFocusIndicator, A11yOrchestratorOptions, ModifierKeys, TabOrderReport, TabOrderEntry, TraversalResult, FocusIndicator, } from '@a11y-oracle/core-engine';
|
|
43
43
|
export type { OracleIssue, OracleNode, OracleCheck, OracleImpact, OracleResultType, AuditContext, } from '@a11y-oracle/audit-formatter';
|
|
44
44
|
export { setupOracleReporting } from './lib/reporting.js';
|
|
45
|
+
export { createCypressCDPAdapter } from './lib/commands.js';
|
|
45
46
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAGH,OAAO,mBAAmB,CAAC;AAG3B,YAAY,EACV,YAAY,EACZ,mBAAmB,EACnB,SAAS,EACT,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACvB,YAAY,EACZ,cAAc,EACd,aAAa,EACb,eAAe,EACf,cAAc,GACf,MAAM,0BAA0B,CAAC;AAGlC,YAAY,EACV,WAAW,EACX,UAAU,EACV,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,YAAY,GACb,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC"}
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAuCG;AAGH,OAAO,mBAAmB,CAAC;AAG3B,YAAY,EACV,cAAc,EACd,YAAY,EACZ,mBAAmB,EACnB,SAAS,EACT,kBAAkB,EAClB,kBAAkB,EAClB,uBAAuB,EACvB,YAAY,EACZ,cAAc,EACd,aAAa,EACb,eAAe,EACf,cAAc,GACf,MAAM,0BAA0B,CAAC;AAGlC,YAAY,EACV,WAAW,EACX,UAAU,EACV,WAAW,EACX,YAAY,EACZ,gBAAgB,EAChB,YAAY,GACb,MAAM,8BAA8B,CAAC;AAGtC,OAAO,EAAE,oBAAoB,EAAE,MAAM,oBAAoB,CAAC;AAG1D,OAAO,EAAE,uBAAuB,EAAE,MAAM,mBAAmB,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -42,3 +42,5 @@
|
|
|
42
42
|
import './lib/commands.js';
|
|
43
43
|
// Node-side reporting setup
|
|
44
44
|
export { setupOracleReporting } from './lib/reporting.js';
|
|
45
|
+
// Reusable CDP adapter for custom integrations (e.g. axe-bridge resolveAllIncomplete)
|
|
46
|
+
export { createCypressCDPAdapter } from './lib/commands.js';
|
package/dist/lib/commands.d.ts
CHANGED
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
* cy.disposeA11yOracle();
|
|
30
30
|
* ```
|
|
31
31
|
*/
|
|
32
|
-
import type { SpeechResult, A11yState, A11yOrchestratorOptions, TabOrderReport, TraversalResult, ModifierKeys } from '@a11y-oracle/core-engine';
|
|
32
|
+
import type { CDPSessionLike, SpeechResult, A11yState, A11yOrchestratorOptions, TabOrderReport, TraversalResult, ModifierKeys } from '@a11y-oracle/core-engine';
|
|
33
33
|
import type { AuditContext } from '@a11y-oracle/audit-formatter';
|
|
34
34
|
declare global {
|
|
35
35
|
namespace Cypress {
|
|
@@ -98,4 +98,28 @@ declare global {
|
|
|
98
98
|
}
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* Create a ready-to-use CDP adapter for the Cypress AUT iframe.
|
|
103
|
+
*
|
|
104
|
+
* Encapsulates all the CDP plumbing needed to use
|
|
105
|
+
* `@a11y-oracle/axe-bridge`'s `resolveAllIncomplete()` in a custom
|
|
106
|
+
* Cypress command:
|
|
107
|
+
*
|
|
108
|
+
* 1. Enables required CDP domains (`DOM.enable`, `Page.enable`)
|
|
109
|
+
* 2. Discovers the AUT frame ID from `Page.getFrameTree`
|
|
110
|
+
* 3. Creates an isolated world execution context in the AUT frame
|
|
111
|
+
* 4. Detects iframe position and CSS transform scale
|
|
112
|
+
* 5. Returns a `CDPSessionLike` adapter with scale-aware coordinate
|
|
113
|
+
* translation for `Page.captureScreenshot`
|
|
114
|
+
*
|
|
115
|
+
* @example
|
|
116
|
+
* ```typescript
|
|
117
|
+
* import { createCypressCDPAdapter } from '@a11y-oracle/cypress-plugin';
|
|
118
|
+
* import { resolveAllIncomplete } from '@a11y-oracle/axe-bridge';
|
|
119
|
+
*
|
|
120
|
+
* const cdp = await createCypressCDPAdapter();
|
|
121
|
+
* const resolved = await resolveAllIncomplete(cdp, axeResults, options);
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export declare function createCypressCDPAdapter(): Promise<CDPSessionLike>;
|
|
101
125
|
//# sourceMappingURL=commands.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/lib/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"commands.d.ts","sourceRoot":"","sources":["../../src/lib/commands.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AAGH,OAAO,KAAK,EACV,cAAc,EACd,YAAY,EACZ,SAAS,EACT,uBAAuB,EACvB,cAAc,EACd,eAAe,EACf,YAAY,EACb,MAAM,0BAA0B,CAAC;AAMlC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,8BAA8B,CAAC;AAIjE,OAAO,CAAC,MAAM,CAAC;IACb,UAAU,OAAO,CAAC;QAChB,UAAU,SAAS;YACjB;;;eAGG;YACH,cAAc,CAAC,OAAO,CAAC,EAAE,uBAAuB,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;YAEnE;;;;;eAKG;YACH,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,CAAC;YAE1C,+DAA+D;YAC/D,aAAa,IAAI,SAAS,CAAC,MAAM,CAAC,CAAC;YAEnC,2EAA2E;YAC3E,mBAAmB,IAAI,SAAS,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;YAEtD,0EAA0E;YAC1E,qBAAqB,IAAI,SAAS,CAAC,YAAY,EAAE,CAAC,CAAC;YAEnD;;;;;eAKG;YACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,YAAY,GAAG,SAAS,CAAC,SAAS,CAAC,CAAC;YAE1E,0EAA0E;YAC1E,SAAS,IAAI,SAAS,CAAC,SAAS,CAAC,CAAC;YAElC,sDAAsD;YACtD,oBAAoB,IAAI,SAAS,CAAC,cAAc,CAAC,CAAC;YAElD;;;;;eAKG;YACH,mBAAmB,CACjB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,GACf,SAAS,CAAC,eAAe,CAAC,CAAC;YAE9B;;;;;;;;;eASG;YACH,uBAAuB,CAAC,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAAG,SAAS,CAAC,IAAI,CAAC,CAAC;YAE1E;;;;;;;eAOG;YACH,sBAAsB,CACpB,QAAQ,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,MAAM,EAChB,OAAO,CAAC,EAAE,OAAO,CAAC,YAAY,CAAC,GAC9B,SAAS,CAAC,IAAI,CAAC,CAAC;YAEnB;;;eAGG;YACH,iBAAiB,IAAI,SAAS,CAAC,IAAI,CAAC,CAAC;SACtC;KACF;CACF;AAsQD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,cAAc,CAAC,CAwCvE"}
|
package/dist/lib/commands.js
CHANGED
|
@@ -74,15 +74,20 @@ function createFrameAwareCDPAdapter() {
|
|
|
74
74
|
// Runtime.evaluate runs inside the AUT iframe (via contextId), so
|
|
75
75
|
// getBoundingClientRect() returns iframe-relative coords. But
|
|
76
76
|
// Page.captureScreenshot clips relative to the top-level viewport.
|
|
77
|
+
// Cypress scales the AUT iframe via a CSS transform on a wrapper
|
|
78
|
+
// element, so we must also apply the display scale factor.
|
|
77
79
|
if (method === 'Page.captureScreenshot' &&
|
|
78
80
|
p['clip'] &&
|
|
79
81
|
autIframeBounds &&
|
|
80
|
-
(autIframeBounds.x !== 0 || autIframeBounds.y !== 0)) {
|
|
82
|
+
(autIframeBounds.x !== 0 || autIframeBounds.y !== 0 || autIframeBounds.scale !== 1)) {
|
|
81
83
|
const clip = p['clip'];
|
|
84
|
+
const s = autIframeBounds.scale;
|
|
82
85
|
p['clip'] = {
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
+
x: autIframeBounds.x + clip.x * s,
|
|
87
|
+
y: autIframeBounds.y + clip.y * s,
|
|
88
|
+
width: clip.width * s,
|
|
89
|
+
height: clip.height * s,
|
|
90
|
+
scale: (clip.scale || 1) / s,
|
|
86
91
|
};
|
|
87
92
|
}
|
|
88
93
|
return sendCDP(method, p);
|
|
@@ -139,12 +144,18 @@ async function findAUTContextId(frameId) {
|
|
|
139
144
|
}
|
|
140
145
|
}
|
|
141
146
|
/**
|
|
142
|
-
* Get the AUT iframe's position in the top-level viewport.
|
|
147
|
+
* Get the AUT iframe's position and display scale in the top-level viewport.
|
|
143
148
|
*
|
|
144
149
|
* Runs `Runtime.evaluate` in the top-level context (without `contextId`)
|
|
145
150
|
* to find the AUT iframe and return its bounding rect origin. Adds
|
|
146
151
|
* `clientLeft`/`clientTop` to account for any iframe border.
|
|
147
152
|
*
|
|
153
|
+
* Also computes the display scale by comparing the iframe's rendered width
|
|
154
|
+
* (`getBoundingClientRect().width`) to its CSS content width (`clientWidth`).
|
|
155
|
+
* Cypress scales the AUT iframe via a CSS transform on an ancestor wrapper
|
|
156
|
+
* element (not the iframe itself), so the iframe's own computed transform
|
|
157
|
+
* is `none`. The empirical width comparison correctly detects the scale.
|
|
158
|
+
*
|
|
148
159
|
* Used to translate iframe-relative coordinates from
|
|
149
160
|
* `getBoundingClientRect()` to viewport-absolute coordinates for
|
|
150
161
|
* `Page.captureScreenshot` clips.
|
|
@@ -157,10 +168,16 @@ async function getAUTIframeBounds() {
|
|
|
157
168
|
const src = f.getAttribute('src') || f.src || '';
|
|
158
169
|
if (src && !src.includes('/__/') && !src.includes('__cypress') && src !== 'about:blank') {
|
|
159
170
|
const rect = f.getBoundingClientRect();
|
|
160
|
-
|
|
171
|
+
const contentWidth = f.clientWidth + 2 * f.clientLeft;
|
|
172
|
+
const scale = contentWidth > 0 ? rect.width / contentWidth : 1;
|
|
173
|
+
return {
|
|
174
|
+
x: rect.x + f.clientLeft * scale,
|
|
175
|
+
y: rect.y + f.clientTop * scale,
|
|
176
|
+
scale: scale,
|
|
177
|
+
};
|
|
161
178
|
}
|
|
162
179
|
}
|
|
163
|
-
return { x: 0, y: 0 };
|
|
180
|
+
return { x: 0, y: 0, scale: 1 };
|
|
164
181
|
})()`,
|
|
165
182
|
returnByValue: true,
|
|
166
183
|
}));
|
|
@@ -244,6 +261,63 @@ async function dispatchKey(key) {
|
|
|
244
261
|
nativeVirtualKeyCode: keyDef.keyCode,
|
|
245
262
|
});
|
|
246
263
|
}
|
|
264
|
+
// ── Public API ─────────────────────────────────────────────────────
|
|
265
|
+
/**
|
|
266
|
+
* Create a ready-to-use CDP adapter for the Cypress AUT iframe.
|
|
267
|
+
*
|
|
268
|
+
* Encapsulates all the CDP plumbing needed to use
|
|
269
|
+
* `@a11y-oracle/axe-bridge`'s `resolveAllIncomplete()` in a custom
|
|
270
|
+
* Cypress command:
|
|
271
|
+
*
|
|
272
|
+
* 1. Enables required CDP domains (`DOM.enable`, `Page.enable`)
|
|
273
|
+
* 2. Discovers the AUT frame ID from `Page.getFrameTree`
|
|
274
|
+
* 3. Creates an isolated world execution context in the AUT frame
|
|
275
|
+
* 4. Detects iframe position and CSS transform scale
|
|
276
|
+
* 5. Returns a `CDPSessionLike` adapter with scale-aware coordinate
|
|
277
|
+
* translation for `Page.captureScreenshot`
|
|
278
|
+
*
|
|
279
|
+
* @example
|
|
280
|
+
* ```typescript
|
|
281
|
+
* import { createCypressCDPAdapter } from '@a11y-oracle/cypress-plugin';
|
|
282
|
+
* import { resolveAllIncomplete } from '@a11y-oracle/axe-bridge';
|
|
283
|
+
*
|
|
284
|
+
* const cdp = await createCypressCDPAdapter();
|
|
285
|
+
* const resolved = await resolveAllIncomplete(cdp, axeResults, options);
|
|
286
|
+
* ```
|
|
287
|
+
*/
|
|
288
|
+
export async function createCypressCDPAdapter() {
|
|
289
|
+
await sendCDP('DOM.enable');
|
|
290
|
+
await sendCDP('Page.enable');
|
|
291
|
+
const frameId = await findAUTFrameId();
|
|
292
|
+
const contextId = frameId ? await findAUTContextId(frameId) : null;
|
|
293
|
+
const iframeBounds = await getAUTIframeBounds();
|
|
294
|
+
return {
|
|
295
|
+
send: (method, params) => {
|
|
296
|
+
const p = { ...params };
|
|
297
|
+
if (method === 'Accessibility.getFullAXTree' && frameId) {
|
|
298
|
+
p['frameId'] = frameId;
|
|
299
|
+
}
|
|
300
|
+
if (method === 'Runtime.evaluate' && contextId !== null) {
|
|
301
|
+
p['contextId'] = contextId;
|
|
302
|
+
}
|
|
303
|
+
if (method === 'Page.captureScreenshot' &&
|
|
304
|
+
p['clip'] &&
|
|
305
|
+
iframeBounds &&
|
|
306
|
+
(iframeBounds.x !== 0 || iframeBounds.y !== 0 || iframeBounds.scale !== 1)) {
|
|
307
|
+
const clip = p['clip'];
|
|
308
|
+
const s = iframeBounds.scale;
|
|
309
|
+
p['clip'] = {
|
|
310
|
+
x: iframeBounds.x + clip.x * s,
|
|
311
|
+
y: iframeBounds.y + clip.y * s,
|
|
312
|
+
width: clip.width * s,
|
|
313
|
+
height: clip.height * s,
|
|
314
|
+
scale: (clip.scale || 1) / s,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
return sendCDP(method, p);
|
|
318
|
+
},
|
|
319
|
+
};
|
|
320
|
+
}
|
|
247
321
|
// ── Commands ───────────────────────────────────────────────────────
|
|
248
322
|
Cypress.Commands.add('initA11yOracle', (options) => {
|
|
249
323
|
cy.wrap(null, { log: false }).then(async () => {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a11y-oracle/cypress-plugin",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.3.0",
|
|
4
4
|
"description": "Cypress custom commands for accessibility speech assertions with iframe-aware CDP routing",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "a11y-oracle",
|
|
@@ -45,9 +45,9 @@
|
|
|
45
45
|
"cypress": ">=12.0.0"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@a11y-oracle/core-engine": "1.
|
|
49
|
-
"@a11y-oracle/keyboard-engine": "1.
|
|
50
|
-
"@a11y-oracle/audit-formatter": "1.
|
|
48
|
+
"@a11y-oracle/core-engine": "1.3.0",
|
|
49
|
+
"@a11y-oracle/keyboard-engine": "1.3.0",
|
|
50
|
+
"@a11y-oracle/audit-formatter": "1.3.0",
|
|
51
51
|
"tslib": "^2.3.0"
|
|
52
52
|
}
|
|
53
53
|
}
|