@a11y-oracle/cypress-plugin 1.3.0 → 1.3.2
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 +18 -0
- package/dist/lib/commands.d.ts.map +1 -1
- package/dist/lib/commands.js +65 -1
- package/package.json +4 -4
package/README.md
CHANGED
|
@@ -29,6 +29,8 @@ npm install -D @a11y-oracle/cypress-plugin @a11y-oracle/core-engine cypress
|
|
|
29
29
|
|
|
30
30
|
> **Chrome/Chromium only.** The plugin uses CDP, which is only available in Chrome-family browsers.
|
|
31
31
|
|
|
32
|
+
> **⚠️ Stability Notice — Playwright recommended.** The Cypress plugin is functional but has known stability constraints with large test suites. We recommend using [`@a11y-oracle/playwright-plugin`](../playwright-plugin/README.md) for the most reliable experience. See [Known Limitations](#known-limitations) below for details.
|
|
33
|
+
|
|
32
34
|
## Setup
|
|
33
35
|
|
|
34
36
|
### 1. Import Commands
|
|
@@ -513,6 +515,22 @@ If you need types in your test files without importing the support file:
|
|
|
513
515
|
/// <reference types="@a11y-oracle/cypress-plugin" />
|
|
514
516
|
```
|
|
515
517
|
|
|
518
|
+
## Known Limitations
|
|
519
|
+
|
|
520
|
+
### CDP Resource Accumulation in Long Test Suites
|
|
521
|
+
|
|
522
|
+
Cypress runs the application under test (AUT) inside an iframe within its runner page. To interact with the AUT's accessibility tree, the plugin must create an **isolated execution world** in the AUT frame via `Page.createIsolatedWorld` on every `initA11yOracle()` call. Unlike Playwright — which provides native, first-class CDP sessions — Cypress's iframe architecture means these isolated worlds accumulate browser-side resources that Chrome does not fully reclaim, even after `disposeA11yOracle()` cleans up its own references.
|
|
523
|
+
|
|
524
|
+
**What this means in practice:**
|
|
525
|
+
|
|
526
|
+
- Test suites with many spec files or many tests per file may experience increasing memory pressure over the course of a run.
|
|
527
|
+
- In v1.3.0 and earlier, this caused a deterministic hang after approximately 16 `init`/`dispose` cycles, because `Accessibility.getFullAXTree` would stall when traversing nodes across all accumulated contexts ([#14](https://github.com/a11y-oracle/a11y-oracle/issues/14)).
|
|
528
|
+
- v1.3.1 mitigated the hang by properly destroying isolated worlds on dispose, but the underlying architectural constraint — that Cypress proxies all CDP calls through its runner and manages frame contexts differently than Playwright — remains.
|
|
529
|
+
|
|
530
|
+
**Recommendation:** If you are starting a new project or have the flexibility to choose your E2E framework, use [`@a11y-oracle/playwright-plugin`](../playwright-plugin/README.md). Playwright provides direct CDP session access without iframe indirection, making it inherently more stable and performant for A11y-Oracle's CDP-heavy workflow.
|
|
531
|
+
|
|
532
|
+
If you need to stay on Cypress, the plugin is fully functional — just be aware of these constraints for very large suites.
|
|
533
|
+
|
|
516
534
|
## Troubleshooting
|
|
517
535
|
|
|
518
536
|
### "Could not find the AUT iframe"
|
|
@@ -1 +1 @@
|
|
|
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;
|
|
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;AA8UD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,cAAc,CAAC,CAwCvE"}
|
package/dist/lib/commands.js
CHANGED
|
@@ -38,14 +38,48 @@ let orchestrator = null;
|
|
|
38
38
|
let autFrameId = null;
|
|
39
39
|
let autContextId = null;
|
|
40
40
|
let autIframeBounds = null;
|
|
41
|
+
/**
|
|
42
|
+
* Cached isolated world state for reuse across init/dispose cycles.
|
|
43
|
+
*
|
|
44
|
+
* `Page.createIsolatedWorld` accumulates execution contexts that Chrome
|
|
45
|
+
* never garbage-collects during same-origin navigations. After ~16
|
|
46
|
+
* iterations the browser hangs on subsequent CDP calls. By caching the
|
|
47
|
+
* context ID and verifying it with a cheap `Runtime.evaluate` probe we
|
|
48
|
+
* avoid creating a new world when the previous one is still alive.
|
|
49
|
+
*/
|
|
50
|
+
let _cachedWorldFrameId = null;
|
|
51
|
+
let _cachedWorldContextId = null;
|
|
52
|
+
/** Timeout (ms) applied to every CDP call to surface hangs as errors. */
|
|
53
|
+
const CDP_TIMEOUT_MS = 30_000;
|
|
54
|
+
/**
|
|
55
|
+
* Wrap a promise with a timeout to prevent indefinite hangs.
|
|
56
|
+
*/
|
|
57
|
+
function withTimeout(promise, ms, label) {
|
|
58
|
+
return new Promise((resolve, reject) => {
|
|
59
|
+
const timer = setTimeout(() => {
|
|
60
|
+
reject(new Error(`A11y-Oracle: CDP call "${label}" timed out after ${ms}ms`));
|
|
61
|
+
}, ms);
|
|
62
|
+
promise.then((value) => {
|
|
63
|
+
clearTimeout(timer);
|
|
64
|
+
resolve(value);
|
|
65
|
+
}, (error) => {
|
|
66
|
+
clearTimeout(timer);
|
|
67
|
+
reject(error);
|
|
68
|
+
});
|
|
69
|
+
});
|
|
70
|
+
}
|
|
41
71
|
/**
|
|
42
72
|
* Send a raw CDP command through Cypress's automation channel.
|
|
73
|
+
*
|
|
74
|
+
* Every call is guarded by a timeout so that accumulated isolated worlds
|
|
75
|
+
* or other browser-side issues surface as errors instead of silent hangs.
|
|
43
76
|
*/
|
|
44
77
|
function sendCDP(command, params = {}) {
|
|
45
|
-
|
|
78
|
+
const raw = Cypress.automation('remote:debugger:protocol', {
|
|
46
79
|
command,
|
|
47
80
|
params,
|
|
48
81
|
});
|
|
82
|
+
return withTimeout(raw, CDP_TIMEOUT_MS, command);
|
|
49
83
|
}
|
|
50
84
|
/**
|
|
51
85
|
* Create a {@link CDPSessionLike} adapter that routes CDP calls through
|
|
@@ -131,6 +165,29 @@ async function findAUTFrameId() {
|
|
|
131
165
|
* calls execute in the AUT, not the Cypress runner.
|
|
132
166
|
*/
|
|
133
167
|
async function findAUTContextId(frameId) {
|
|
168
|
+
// Try to reuse the context that was handed off by the previous
|
|
169
|
+
// disposeA11yOracle() call. A lightweight Runtime.evaluate probe
|
|
170
|
+
// confirms the context is still alive (Chrome destroys isolated worlds
|
|
171
|
+
// on cross-origin navigation but may preserve them for same-origin).
|
|
172
|
+
// Always consume (clear) the cache regardless of outcome.
|
|
173
|
+
const cachedCtx = _cachedWorldContextId;
|
|
174
|
+
const cachedFrame = _cachedWorldFrameId;
|
|
175
|
+
_cachedWorldContextId = null;
|
|
176
|
+
_cachedWorldFrameId = null;
|
|
177
|
+
if (cachedCtx !== null && cachedFrame === frameId) {
|
|
178
|
+
try {
|
|
179
|
+
await sendCDP('Runtime.evaluate', {
|
|
180
|
+
expression: '1',
|
|
181
|
+
contextId: cachedCtx,
|
|
182
|
+
returnByValue: true,
|
|
183
|
+
});
|
|
184
|
+
// Context is still valid — reuse it.
|
|
185
|
+
return cachedCtx;
|
|
186
|
+
}
|
|
187
|
+
catch {
|
|
188
|
+
// Context was destroyed (frame navigated), fall through to create.
|
|
189
|
+
}
|
|
190
|
+
}
|
|
134
191
|
try {
|
|
135
192
|
const result = await sendCDP('Page.createIsolatedWorld', {
|
|
136
193
|
frameId,
|
|
@@ -476,6 +533,13 @@ Cypress.Commands.add('disposeA11yOracle', () => {
|
|
|
476
533
|
// Engine was already disabled via orchestrator (same CDP session)
|
|
477
534
|
engine = null;
|
|
478
535
|
}
|
|
536
|
+
// Hand off the current isolated world to the cache so the next
|
|
537
|
+
// initA11yOracle() can reuse it instead of leaking a new one.
|
|
538
|
+
// CDP provides no API to destroy an isolated world; reuse is
|
|
539
|
+
// the only way to prevent accumulation. The cache is consumed
|
|
540
|
+
// (cleared) at the start of the next findAUTContextId() call.
|
|
541
|
+
_cachedWorldContextId = autContextId;
|
|
542
|
+
_cachedWorldFrameId = autFrameId;
|
|
479
543
|
autFrameId = null;
|
|
480
544
|
autContextId = null;
|
|
481
545
|
autIframeBounds = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@a11y-oracle/cypress-plugin",
|
|
3
|
-
"version": "1.3.
|
|
3
|
+
"version": "1.3.2",
|
|
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.3.
|
|
49
|
-
"@a11y-oracle/keyboard-engine": "1.3.
|
|
50
|
-
"@a11y-oracle/audit-formatter": "1.3.
|
|
48
|
+
"@a11y-oracle/core-engine": "1.3.2",
|
|
49
|
+
"@a11y-oracle/keyboard-engine": "1.3.2",
|
|
50
|
+
"@a11y-oracle/audit-formatter": "1.3.2",
|
|
51
51
|
"tslib": "^2.3.0"
|
|
52
52
|
}
|
|
53
53
|
}
|