@a11y-oracle/cypress-plugin 1.3.0 → 1.3.1

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.
@@ -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;AAsQD;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAsB,uBAAuB,IAAI,OAAO,CAAC,cAAc,CAAC,CAwCvE"}
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"}
@@ -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
- return Cypress.automation('remote:debugger:protocol', {
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.0",
3
+ "version": "1.3.1",
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.0",
49
- "@a11y-oracle/keyboard-engine": "1.3.0",
50
- "@a11y-oracle/audit-formatter": "1.3.0",
48
+ "@a11y-oracle/core-engine": "1.3.1",
49
+ "@a11y-oracle/keyboard-engine": "1.3.1",
50
+ "@a11y-oracle/audit-formatter": "1.3.1",
51
51
  "tslib": "^2.3.0"
52
52
  }
53
53
  }