craftdriver 1.7.0 → 1.9.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.
Files changed (234) hide show
  1. package/CHANGELOG.md +25 -12
  2. package/README.md +16 -6
  3. package/dist/cli/agentSession.d.ts +62 -0
  4. package/dist/cli/agentSession.d.ts.map +1 -0
  5. package/dist/cli/agentSession.js +100 -0
  6. package/dist/cli/agentSession.js.map +1 -0
  7. package/dist/cli/artifactPaths.d.ts +42 -0
  8. package/dist/cli/artifactPaths.d.ts.map +1 -0
  9. package/dist/cli/artifactPaths.js +144 -0
  10. package/dist/cli/artifactPaths.js.map +1 -0
  11. package/dist/cli/bounds.d.ts +88 -0
  12. package/dist/cli/bounds.d.ts.map +1 -0
  13. package/dist/cli/bounds.js +128 -0
  14. package/dist/cli/bounds.js.map +1 -0
  15. package/dist/cli/client.d.ts +3 -0
  16. package/dist/cli/client.d.ts.map +1 -1
  17. package/dist/cli/client.js +24 -8
  18. package/dist/cli/client.js.map +1 -1
  19. package/dist/cli/daemon.d.ts +3 -0
  20. package/dist/cli/daemon.d.ts.map +1 -1
  21. package/dist/cli/daemon.js +152 -39
  22. package/dist/cli/daemon.js.map +1 -1
  23. package/dist/cli/defaults.d.ts +23 -3
  24. package/dist/cli/defaults.d.ts.map +1 -1
  25. package/dist/cli/defaults.js +50 -5
  26. package/dist/cli/defaults.js.map +1 -1
  27. package/dist/cli/dispatcher.d.ts +54 -11
  28. package/dist/cli/dispatcher.d.ts.map +1 -1
  29. package/dist/cli/dispatcher.js +851 -54
  30. package/dist/cli/dispatcher.js.map +1 -1
  31. package/dist/cli/index.d.ts.map +1 -1
  32. package/dist/cli/index.js +305 -56
  33. package/dist/cli/index.js.map +1 -1
  34. package/dist/cli/init.d.ts +14 -14
  35. package/dist/cli/init.d.ts.map +1 -1
  36. package/dist/cli/init.js +328 -180
  37. package/dist/cli/init.js.map +1 -1
  38. package/dist/cli/journal.d.ts +153 -0
  39. package/dist/cli/journal.d.ts.map +1 -0
  40. package/dist/cli/journal.js +252 -0
  41. package/dist/cli/journal.js.map +1 -0
  42. package/dist/cli/lineReader.d.ts +33 -0
  43. package/dist/cli/lineReader.d.ts.map +1 -0
  44. package/dist/cli/lineReader.js +66 -0
  45. package/dist/cli/lineReader.js.map +1 -0
  46. package/dist/cli/locatorCandidates.d.ts +44 -0
  47. package/dist/cli/locatorCandidates.d.ts.map +1 -0
  48. package/dist/cli/locatorCandidates.js +190 -0
  49. package/dist/cli/locatorCandidates.js.map +1 -0
  50. package/dist/cli/mcp/artifacts.d.ts +39 -1
  51. package/dist/cli/mcp/artifacts.d.ts.map +1 -1
  52. package/dist/cli/mcp/artifacts.js +104 -9
  53. package/dist/cli/mcp/artifacts.js.map +1 -1
  54. package/dist/cli/mcp/bounds.d.ts +65 -0
  55. package/dist/cli/mcp/bounds.d.ts.map +1 -0
  56. package/dist/cli/mcp/bounds.js +124 -0
  57. package/dist/cli/mcp/bounds.js.map +1 -0
  58. package/dist/cli/mcp/params.d.ts +38 -0
  59. package/dist/cli/mcp/params.d.ts.map +1 -0
  60. package/dist/cli/mcp/params.js +174 -0
  61. package/dist/cli/mcp/params.js.map +1 -0
  62. package/dist/cli/mcp/server.d.ts +94 -16
  63. package/dist/cli/mcp/server.d.ts.map +1 -1
  64. package/dist/cli/mcp/server.js +304 -150
  65. package/dist/cli/mcp/server.js.map +1 -1
  66. package/dist/cli/mcp/tools.d.ts +62 -17
  67. package/dist/cli/mcp/tools.d.ts.map +1 -1
  68. package/dist/cli/mcp/tools.js +373 -158
  69. package/dist/cli/mcp/tools.js.map +1 -1
  70. package/dist/cli/pageSemantics.d.ts +6 -0
  71. package/dist/cli/pageSemantics.d.ts.map +1 -0
  72. package/dist/cli/pageSemantics.js +6 -0
  73. package/dist/cli/pageSemantics.js.map +1 -0
  74. package/dist/cli/parseArgs.d.ts +6 -18
  75. package/dist/cli/parseArgs.d.ts.map +1 -1
  76. package/dist/cli/parseArgs.js +689 -45
  77. package/dist/cli/parseArgs.js.map +1 -1
  78. package/dist/cli/protocol.d.ts +8 -0
  79. package/dist/cli/protocol.d.ts.map +1 -1
  80. package/dist/cli/selector.d.ts +3 -3
  81. package/dist/cli/selector.d.ts.map +1 -1
  82. package/dist/cli/selector.js +6 -9
  83. package/dist/cli/selector.js.map +1 -1
  84. package/dist/cli/sessionRegistry.d.ts +42 -0
  85. package/dist/cli/sessionRegistry.d.ts.map +1 -0
  86. package/dist/cli/sessionRegistry.js +99 -0
  87. package/dist/cli/sessionRegistry.js.map +1 -0
  88. package/dist/cli/snapshot.d.ts +100 -19
  89. package/dist/cli/snapshot.d.ts.map +1 -1
  90. package/dist/cli/snapshot.js +295 -108
  91. package/dist/cli/snapshot.js.map +1 -1
  92. package/dist/cli/stateStore.d.ts +50 -0
  93. package/dist/cli/stateStore.d.ts.map +1 -0
  94. package/dist/cli/stateStore.js +122 -0
  95. package/dist/cli/stateStore.js.map +1 -0
  96. package/dist/index.d.ts +3 -2
  97. package/dist/index.d.ts.map +1 -1
  98. package/dist/index.js +1 -0
  99. package/dist/index.js.map +1 -1
  100. package/dist/lib/a11y.d.ts +6 -2
  101. package/dist/lib/a11y.d.ts.map +1 -1
  102. package/dist/lib/a11y.js +3 -1
  103. package/dist/lib/a11y.js.map +1 -1
  104. package/dist/lib/bidi/evaluate.d.ts +11 -0
  105. package/dist/lib/bidi/evaluate.d.ts.map +1 -0
  106. package/dist/lib/bidi/evaluate.js +25 -0
  107. package/dist/lib/bidi/evaluate.js.map +1 -0
  108. package/dist/lib/bidi/index.d.ts +1 -1
  109. package/dist/lib/bidi/index.d.ts.map +1 -1
  110. package/dist/lib/bidi/index.js +2 -2
  111. package/dist/lib/bidi/index.js.map +1 -1
  112. package/dist/lib/bidi/logs.d.ts +3 -0
  113. package/dist/lib/bidi/logs.d.ts.map +1 -1
  114. package/dist/lib/bidi/logs.js +7 -0
  115. package/dist/lib/bidi/logs.js.map +1 -1
  116. package/dist/lib/bidi/network.d.ts +14 -1
  117. package/dist/lib/bidi/network.d.ts.map +1 -1
  118. package/dist/lib/bidi/network.js +45 -7
  119. package/dist/lib/bidi/network.js.map +1 -1
  120. package/dist/lib/bidi/storage.d.ts +30 -6
  121. package/dist/lib/bidi/storage.d.ts.map +1 -1
  122. package/dist/lib/bidi/storage.js +190 -45
  123. package/dist/lib/bidi/storage.js.map +1 -1
  124. package/dist/lib/bidi/types.d.ts +9 -2
  125. package/dist/lib/bidi/types.d.ts.map +1 -1
  126. package/dist/lib/browser.d.ts +7 -4
  127. package/dist/lib/browser.d.ts.map +1 -1
  128. package/dist/lib/browser.js +186 -65
  129. package/dist/lib/browser.js.map +1 -1
  130. package/dist/lib/browserContext.d.ts +58 -24
  131. package/dist/lib/browserContext.d.ts.map +1 -1
  132. package/dist/lib/browserContext.js +300 -99
  133. package/dist/lib/browserContext.js.map +1 -1
  134. package/dist/lib/by.d.ts +68 -30
  135. package/dist/lib/by.d.ts.map +1 -1
  136. package/dist/lib/by.js +54 -23
  137. package/dist/lib/by.js.map +1 -1
  138. package/dist/lib/clearFastPath.d.ts.map +1 -1
  139. package/dist/lib/clearFastPath.js +2 -0
  140. package/dist/lib/clearFastPath.js.map +1 -1
  141. package/dist/lib/clickFastPath.d.ts.map +1 -1
  142. package/dist/lib/clickFastPath.js +2 -0
  143. package/dist/lib/clickFastPath.js.map +1 -1
  144. package/dist/lib/driver.d.ts +26 -0
  145. package/dist/lib/driver.d.ts.map +1 -1
  146. package/dist/lib/driver.js +123 -4
  147. package/dist/lib/driver.js.map +1 -1
  148. package/dist/lib/elementHandle.d.ts +13 -10
  149. package/dist/lib/elementHandle.d.ts.map +1 -1
  150. package/dist/lib/elementHandle.js +119 -50
  151. package/dist/lib/elementHandle.js.map +1 -1
  152. package/dist/lib/errors.d.ts +12 -0
  153. package/dist/lib/errors.d.ts.map +1 -1
  154. package/dist/lib/errors.js +12 -0
  155. package/dist/lib/errors.js.map +1 -1
  156. package/dist/lib/expect.d.ts +10 -0
  157. package/dist/lib/expect.d.ts.map +1 -1
  158. package/dist/lib/expect.js +76 -49
  159. package/dist/lib/expect.js.map +1 -1
  160. package/dist/lib/fillFastPath.d.ts.map +1 -1
  161. package/dist/lib/fillFastPath.js +2 -0
  162. package/dist/lib/fillFastPath.js.map +1 -1
  163. package/dist/lib/frame.d.ts.map +1 -1
  164. package/dist/lib/frame.js +29 -17
  165. package/dist/lib/frame.js.map +1 -1
  166. package/dist/lib/initScript.d.ts +9 -0
  167. package/dist/lib/initScript.d.ts.map +1 -0
  168. package/dist/lib/initScript.js +14 -0
  169. package/dist/lib/initScript.js.map +1 -0
  170. package/dist/lib/locator.d.ts +21 -54
  171. package/dist/lib/locator.d.ts.map +1 -1
  172. package/dist/lib/locator.js +165 -208
  173. package/dist/lib/locator.js.map +1 -1
  174. package/dist/lib/page.d.ts +22 -0
  175. package/dist/lib/page.d.ts.map +1 -1
  176. package/dist/lib/page.js +71 -13
  177. package/dist/lib/page.js.map +1 -1
  178. package/dist/lib/pageSemantics.d.ts +42 -0
  179. package/dist/lib/pageSemantics.d.ts.map +1 -0
  180. package/dist/lib/pageSemantics.js +216 -0
  181. package/dist/lib/pageSemantics.js.map +1 -0
  182. package/dist/lib/query.d.ts +68 -0
  183. package/dist/lib/query.d.ts.map +1 -0
  184. package/dist/lib/query.js +590 -0
  185. package/dist/lib/query.js.map +1 -0
  186. package/dist/lib/secureFile.d.ts +8 -0
  187. package/dist/lib/secureFile.d.ts.map +1 -0
  188. package/dist/lib/secureFile.js +67 -0
  189. package/dist/lib/secureFile.js.map +1 -0
  190. package/dist/lib/sessionStateValidation.d.ts +35 -0
  191. package/dist/lib/sessionStateValidation.d.ts.map +1 -0
  192. package/dist/lib/sessionStateValidation.js +223 -0
  193. package/dist/lib/sessionStateValidation.js.map +1 -0
  194. package/dist/lib/shadowRoot.d.ts +21 -0
  195. package/dist/lib/shadowRoot.d.ts.map +1 -0
  196. package/dist/lib/shadowRoot.js +29 -0
  197. package/dist/lib/shadowRoot.js.map +1 -0
  198. package/dist/lib/shadowRootLocator.d.ts +31 -0
  199. package/dist/lib/shadowRootLocator.d.ts.map +1 -0
  200. package/dist/lib/shadowRootLocator.js +55 -0
  201. package/dist/lib/shadowRootLocator.js.map +1 -0
  202. package/dist/lib/timing.d.ts +2 -2
  203. package/dist/lib/timing.js +2 -2
  204. package/dist/lib/tracing.d.ts +2 -1
  205. package/dist/lib/tracing.d.ts.map +1 -1
  206. package/dist/lib/tracing.js +12 -1
  207. package/dist/lib/tracing.js.map +1 -1
  208. package/docs/accessibility.md +9 -1
  209. package/docs/agents.md +56 -61
  210. package/docs/api-reference.md +5 -1
  211. package/docs/browser-api.md +27 -7
  212. package/docs/browser-context.md +21 -15
  213. package/docs/cli.md +353 -30
  214. package/docs/error-codes.md +7 -4
  215. package/docs/getting-started.md +6 -1
  216. package/docs/index.md +1 -1
  217. package/docs/mcp.md +136 -65
  218. package/docs/public/examples/agent-actions.html +48 -0
  219. package/docs/public/examples/agent-debug.html +86 -0
  220. package/docs/public/examples/agent-late-mutation.html +18 -0
  221. package/docs/public/examples/agent-ref-shift.html +33 -0
  222. package/docs/public/examples/iframe-child.html +12 -1
  223. package/docs/public/examples/shadow-dom.html +137 -0
  224. package/docs/recipes/login-once-reuse-session.md +33 -5
  225. package/docs/selectors.md +61 -0
  226. package/docs/session-management.md +80 -12
  227. package/docs/why-craftdriver.md +1 -1
  228. package/package.json +5 -3
  229. package/skills/craftdriver/SKILL.md +59 -75
  230. package/skills/craftdriver/agents/openai.yaml +4 -0
  231. package/skills/craftdriver/cheatsheet.md +18 -4
  232. package/skills/craftdriver/cli.md +194 -68
  233. package/skills/craftdriver/patterns.md +17 -8
  234. package/skills/craftdriver/workflow.md +149 -0
@@ -12,7 +12,7 @@ import { resolveLaunchTarget } from './launchTarget.js';
12
12
  import { parseRemoteEndpoint, buildRemoteCapabilities, redactUrlForLog } from './remote.js';
13
13
  import { By } from './by.js';
14
14
  import { until } from './wait.js';
15
- import { DEFAULT_ELEMENT_TIMEOUT_MS, DEFAULT_NAVIGATION_TIMEOUT_MS, STATE_POLL_INTERVAL_MS, NETWORK_IDLE_SETTLE_MS, PORT_RELEASE_DELAY_MS, BIDI_CONNECT_MAX_ATTEMPTS, BIDI_CONNECT_BACKOFF_STEP_MS, EVAL_REALM_RETRY_ATTEMPTS, EVAL_REALM_RETRY_DELAY_MS, } from './timing.js';
15
+ import { DEFAULT_ELEMENT_TIMEOUT_MS, DEFAULT_NAVIGATION_TIMEOUT_MS, STATE_POLL_INTERVAL_MS, NETWORK_IDLE_SETTLE_MS, PORT_RELEASE_DELAY_MS, BIDI_CONNECT_MAX_ATTEMPTS, BIDI_CONNECT_BACKOFF_STEP_MS, } from './timing.js';
16
16
  import { ElementHandle } from './elementHandle.js';
17
17
  import { Locator } from './locator.js';
18
18
  import { expectSelector } from './expect.js';
@@ -33,10 +33,13 @@ import { Tracer } from './tracing.js';
33
33
  import { A11y } from './a11y.js';
34
34
  import { Clock } from './clock.js';
35
35
  import { CraftdriverError, ErrorCode } from './errors.js';
36
+ import { parseSessionState, hasNonEmptySessionStorage, isSessionStateEmpty, storageStateDetail, } from './sessionStateValidation.js';
36
37
  import { clickWithFastPath } from './clickFastPath.js';
37
38
  import { fillWithFastPath } from './fillFastPath.js';
38
39
  import { clearWithFastPath } from './clearFastPath.js';
39
40
  import { runExpectScreenshot, shouldUpdateVisualBaselines } from './visual/index.js';
41
+ import { publicPageInitScript } from './initScript.js';
42
+ import { withRealmRetry } from './bidi/evaluate.js';
40
43
  /** Common mobile device presets */
41
44
  export const devices = {
42
45
  'iPhone 14': {
@@ -269,6 +272,7 @@ export class Browser {
269
272
  _topLevelContextTracking;
270
273
  _topLevelContextTrackingOffs = [];
271
274
  _topLevelContextCacheVersion = 0;
275
+ _destroyedTopLevelContextIds = new Set();
272
276
  _logs;
273
277
  _tracer;
274
278
  _storage;
@@ -481,9 +485,19 @@ export class Browser {
481
485
  if (wsUrl && bidiRequested) {
482
486
  await browser.initBiDi(wsUrl);
483
487
  }
484
- // Load session state if provided
485
- if (options.storageState) {
486
- await browser.loadState(options.storageState);
488
+ if (options.storageState !== undefined) {
489
+ const protocol = browser.bidiSession?.isConnected() ? 'bidi' : 'classic';
490
+ const errorContext = { operation: 'Browser.launch', browserName: name, protocol };
491
+ const state = await parseSessionState(options.storageState, errorContext);
492
+ if (browser.bidiSession?.isConnected()) {
493
+ await browser.defaultContext._loadStorageState(state, 'Browser.launch');
494
+ }
495
+ else if (!isSessionStateEmpty(state)) {
496
+ throw new CraftdriverError(ErrorCode.UNSUPPORTED, 'non-empty storageState cannot be restored at WebDriver Classic launch', {
497
+ detail: storageStateDetail(errorContext, 'capability', { partialApplied: false }),
498
+ hint: 'Enable BiDi, or launch first, navigate to the sole origin, then call browser.loadState().',
499
+ });
500
+ }
487
501
  }
488
502
  }
489
503
  catch (err) {
@@ -517,8 +531,23 @@ export class Browser {
517
531
  if (wsUrl && target.bidiRequested) {
518
532
  await browser.initBiDi(wsUrl);
519
533
  }
520
- if (options.storageState) {
521
- await browser.loadState(options.storageState);
534
+ if (options.storageState !== undefined) {
535
+ const protocol = browser.bidiSession?.isConnected() ? 'bidi' : 'classic';
536
+ const errorContext = {
537
+ operation: 'Browser.launch',
538
+ browserName: target.browserName,
539
+ protocol,
540
+ };
541
+ const state = await parseSessionState(options.storageState, errorContext);
542
+ if (browser.bidiSession?.isConnected()) {
543
+ await browser.defaultContext._loadStorageState(state, 'Browser.launch');
544
+ }
545
+ else if (!isSessionStateEmpty(state)) {
546
+ throw new CraftdriverError(ErrorCode.UNSUPPORTED, 'non-empty storageState cannot be restored at WebDriver Classic launch', {
547
+ detail: storageStateDetail(errorContext, 'capability', { partialApplied: false }),
548
+ hint: 'Enable BiDi, or launch first, navigate to the sole origin, then call browser.loadState().',
549
+ });
550
+ }
522
551
  }
523
552
  }
524
553
  catch (err) {
@@ -550,6 +579,8 @@ export class Browser {
550
579
  this._topLevelContextTracking = this._startTopLevelContextTracking(contexts);
551
580
  },
552
581
  });
582
+ session.network.setInternalContextPredicate((context) => this._isInternalContextId(context));
583
+ session.logs.setInternalContextPredicate((context) => this._isInternalContextId(context));
553
584
  await this._ensureTopLevelContextTracking().catch(() => { });
554
585
  return; // success
555
586
  }
@@ -618,7 +649,13 @@ export class Browser {
618
649
  */
619
650
  get storage() {
620
651
  if (!this._storage) {
621
- this._storage = new SessionStateManager(this.driver, this.bidiSession?.isConnected() ? this.bidiSession.getConnection() : null);
652
+ this._storage = new SessionStateManager(this.driver, this.bidiSession?.isConnected() ? this.bidiSession.getConnection() : null, undefined, {
653
+ browserName: this._browserName,
654
+ protocol: this.bidiSession?.isConnected() ? 'bidi' : 'classic',
655
+ runSerializedRestore: this.bidiSession?.isConnected()
656
+ ? (task) => this.defaultContext._runSerializedRestore(task)
657
+ : undefined,
658
+ });
622
659
  }
623
660
  return this._storage;
624
661
  }
@@ -681,8 +718,23 @@ export class Browser {
681
718
  /**
682
719
  * Load session state from file
683
720
  */
684
- async loadState(path) {
685
- return this.storage.loadState(path);
721
+ async loadState(source) {
722
+ const protocol = this.bidiSession?.isConnected() ? 'bidi' : 'classic';
723
+ const state = await parseSessionState(source, {
724
+ operation: 'browser.loadState',
725
+ browserName: this._browserName,
726
+ protocol,
727
+ });
728
+ // BiDi with no sessionStorage → the validated hydrator (multi-origin, no
729
+ // active page required). Otherwise (Classic, or sessionStorage present) take
730
+ // the active-page path, which restores a single origin matching the active
731
+ // page and can carry sessionStorage.
732
+ if (this.bidiSession?.isConnected() && !hasNonEmptySessionStorage(state)) {
733
+ await this.defaultContext._loadStorageState(state, 'browser.loadState');
734
+ }
735
+ else {
736
+ await this.storage.setState(state, 'browser.loadState');
737
+ }
686
738
  }
687
739
  async _runTracedAction(name, args, selector, run) {
688
740
  const actionIndex = this._tracer?.recordAction(name, args, selector);
@@ -1135,6 +1187,7 @@ export class Browser {
1135
1187
  this._topLevelContextTrackingOffs = [];
1136
1188
  this._topLevelContextTracking = undefined;
1137
1189
  this._topLevelContextUserContexts.clear();
1190
+ this._destroyedTopLevelContextIds.clear();
1138
1191
  this._topLevelContextCacheVersion++;
1139
1192
  // DELETE the WebDriver session — this tells the driver service to close the browser.
1140
1193
  await this.driver.quit().catch(() => { });
@@ -1179,7 +1232,7 @@ export class Browser {
1179
1232
  'Tracing relies on BiDi events; Classic WebDriver does not expose them.', { detail: { browserName: this._browserName, feature: 'startTrace' } });
1180
1233
  }
1181
1234
  if (!this._tracer) {
1182
- this._tracer = new Tracer(this, this.bidiSession.getConnection(), this._engine);
1235
+ this._tracer = new Tracer(this, this.bidiSession.getConnection(), this._engine, (context) => this._isInternalContextId(context));
1183
1236
  }
1184
1237
  await this._tracer.start(opts);
1185
1238
  }
@@ -1212,8 +1265,9 @@ export class Browser {
1212
1265
  */
1213
1266
  async evaluate(fn, ...args) {
1214
1267
  const fnSrc = typeof fn === 'function' ? fn.toString() : fn;
1215
- if (this.bidiSession?.isConnected()) {
1216
- const conn = this.bidiSession.getConnection();
1268
+ const bidiSession = this.bidiSession;
1269
+ if (bidiSession?.isConnected()) {
1270
+ const conn = bidiSession.getConnection();
1217
1271
  // Classic-first navigations return at readyState === 'complete', which is
1218
1272
  // not a barrier the BiDi side respects: an immediately following
1219
1273
  // { context } call can race the browser swapping the old realm for the
@@ -1223,28 +1277,16 @@ export class Browser {
1223
1277
  // result.type === 'exception' path below and are never retried here.
1224
1278
  const functionDeclaration = typeof fn === 'function' ? fnSrc : `function() { ${fnSrc} }`;
1225
1279
  const callArgs = typeof fn === 'function' ? args.map(serializeLocalValue) : [];
1226
- let result;
1227
- for (let attempt = 1;; attempt++) {
1228
- const context = this.bidiSession.getContext();
1280
+ const result = await withRealmRetry(() => {
1281
+ const context = bidiSession.getContext();
1229
1282
  const target = context ? { context } : {};
1230
- try {
1231
- result = await conn.send('script.callFunction', {
1232
- functionDeclaration,
1233
- target,
1234
- arguments: callArgs,
1235
- awaitPromise: true,
1236
- });
1237
- break;
1238
- }
1239
- catch (err) {
1240
- if (attempt < EVAL_REALM_RETRY_ATTEMPTS &&
1241
- String(err?.message).includes('execution contexts cleared')) {
1242
- await new Promise((r) => setTimeout(r, EVAL_REALM_RETRY_DELAY_MS));
1243
- continue;
1244
- }
1245
- throw err;
1246
- }
1247
- }
1283
+ return conn.send('script.callFunction', {
1284
+ functionDeclaration,
1285
+ target,
1286
+ arguments: callArgs,
1287
+ awaitPromise: true,
1288
+ });
1289
+ });
1248
1290
  if (result.type === 'exception') {
1249
1291
  throw new CraftdriverError(ErrorCode.EVAL_THREW, `evaluate() threw an exception in the page: ${result.exceptionDetails?.text ?? 'unknown error'}`, { detail: { exception: result.exceptionDetails?.text ?? null } });
1250
1292
  }
@@ -1278,7 +1320,7 @@ export class Browser {
1278
1320
  const conn = this.bidiSession.getConnection();
1279
1321
  const fnSrc = typeof fnOrSrc === 'function' ? fnOrSrc.toString() : `() => { ${fnOrSrc} }`;
1280
1322
  const result = await conn.send('script.addPreloadScript', {
1281
- functionDeclaration: fnSrc,
1323
+ functionDeclaration: publicPageInitScript(fnSrc),
1282
1324
  });
1283
1325
  const scriptId = result.script;
1284
1326
  this._browserInitScriptIds.add(scriptId);
@@ -1611,7 +1653,14 @@ export class Browser {
1611
1653
  const tree = await conn.send('browsingContext.getTree', {
1612
1654
  maxDepth: 0,
1613
1655
  });
1614
- return (tree.contexts ?? []).map((ctx) => new Page(this.driver, ctx.context, this.getDefaultTimeout, conn, this._wrapContext(ctx.userContext ?? 'default')));
1656
+ const pages = [];
1657
+ for (const ctx of tree.contexts ?? []) {
1658
+ const owner = this._wrapContext(ctx.userContext ?? 'default');
1659
+ if (await owner._isInternalPageContext(ctx.context))
1660
+ continue;
1661
+ pages.push(new Page(this.driver, ctx.context, this.getDefaultTimeout, conn, owner));
1662
+ }
1663
+ return pages;
1615
1664
  }
1616
1665
  // Classic fallback: use window handles
1617
1666
  const handles = await this.driver.getWindowHandles();
@@ -1657,19 +1706,38 @@ export class Browser {
1657
1706
  // `BiDiSession.connect()` (which must have run for `isConnected()` to be
1658
1707
  // true), so no per-call `subscribe()` round trip is needed here.
1659
1708
  return new Promise((resolve, reject) => {
1709
+ let settled = false;
1660
1710
  const timer = setTimeout(() => {
1711
+ settled = true;
1661
1712
  off();
1662
1713
  reject(new Error(`waitForPage() timed out after ${timeout}ms`));
1663
1714
  }, timeout);
1664
1715
  const off = conn.on('browsingContext.contextCreated', (params) => {
1665
1716
  // Only top-level contexts have no parent
1666
1717
  if (!params.parent) {
1667
- clearTimeout(timer);
1668
- off();
1669
- resolve(new Page(this.driver, params.context, this.getDefaultTimeout, conn, this._wrapContext(params.userContext ?? 'default')));
1718
+ const id = params.context;
1719
+ const owner = this._wrapContext(params.userContext ?? 'default');
1720
+ void owner._isInternalPageContext(id).then((internal) => {
1721
+ if (internal || settled)
1722
+ return;
1723
+ settled = true;
1724
+ clearTimeout(timer);
1725
+ off();
1726
+ resolve(new Page(this.driver, id, this.getDefaultTimeout, conn, owner));
1727
+ }).catch((err) => {
1728
+ if (settled)
1729
+ return;
1730
+ settled = true;
1731
+ clearTimeout(timer);
1732
+ off();
1733
+ reject(err);
1734
+ });
1670
1735
  }
1671
1736
  });
1672
1737
  action().catch((err) => {
1738
+ if (settled)
1739
+ return;
1740
+ settled = true;
1673
1741
  clearTimeout(timer);
1674
1742
  off();
1675
1743
  reject(err);
@@ -1758,7 +1826,7 @@ export class Browser {
1758
1826
  *
1759
1827
  * @example
1760
1828
  * // Skip the login UI in every test by reusing a saved session.
1761
- * const ctx = await browser.newContext({ storageState: 'auth/alice.json' });
1829
+ * const ctx = await browser.newContext({ storageState: '.auth/alice.json' });
1762
1830
  * const page = await ctx.newPage({ url: 'https://app.example.com/dashboard' });
1763
1831
  *
1764
1832
  * @example
@@ -1797,6 +1865,15 @@ export class Browser {
1797
1865
  });
1798
1866
  return ctx;
1799
1867
  }
1868
+ _isInternalContextId(context) {
1869
+ if (typeof context !== 'string')
1870
+ return false;
1871
+ for (const ctx of this._contextsById.values()) {
1872
+ if (ctx._isInternalPageId(context))
1873
+ return true;
1874
+ }
1875
+ return false;
1876
+ }
1800
1877
  async newContext(opts) {
1801
1878
  this.requireBiDi('newContext()', 'newContext() requires BiDi (enableBiDi: true). ' +
1802
1879
  'WebDriver Classic has no concept of user contexts.');
@@ -1806,17 +1883,25 @@ export class Browser {
1806
1883
  baseURL: opts?.baseURL,
1807
1884
  extraHTTPHeaders: opts?.extraHTTPHeaders,
1808
1885
  });
1809
- if (opts?.storageState !== undefined) {
1810
- await ctx.loadStorageState(opts.storageState);
1811
- }
1812
- // Apply identity & device emulation options. Each setter is `userContexts`-scoped,
1813
- // so future pages in this context inherit automatically.
1814
- if (opts?.locale !== undefined)
1815
- await ctx.setLocale(opts.locale);
1816
- if (opts?.timezoneId !== undefined)
1817
- await ctx.setTimezone(opts.timezoneId);
1818
- if (opts?.geolocation !== undefined)
1819
- await ctx.setGeolocation(opts.geolocation);
1886
+ try {
1887
+ if (opts?.storageState !== undefined) {
1888
+ await ctx._loadStorageState(opts.storageState, 'browser.newContext');
1889
+ }
1890
+ // Apply identity & device emulation options. Each setter is `userContexts`-scoped,
1891
+ // so future pages in this context inherit automatically.
1892
+ if (opts?.locale !== undefined)
1893
+ await ctx.setLocale(opts.locale);
1894
+ if (opts?.timezoneId !== undefined)
1895
+ await ctx.setTimezone(opts.timezoneId);
1896
+ if (opts?.geolocation !== undefined)
1897
+ await ctx.setGeolocation(opts.geolocation);
1898
+ }
1899
+ catch (err) {
1900
+ // Restore/emulation failed — tear the just-created context down so a
1901
+ // rejected newContext() never leaves an orphaned user context behind.
1902
+ await ctx.close().catch(() => { });
1903
+ throw err;
1904
+ }
1820
1905
  return ctx;
1821
1906
  }
1822
1907
  /**
@@ -1887,13 +1972,21 @@ export class Browser {
1887
1972
  const id = params.context;
1888
1973
  if (typeof id !== 'string')
1889
1974
  return;
1890
- this._topLevelContextUserContexts.set(id, typeof params.userContext === 'string' ? params.userContext : '');
1891
- this._topLevelContextCacheVersion++;
1975
+ const userContext = typeof params.userContext === 'string' ? params.userContext : 'default';
1976
+ this._destroyedTopLevelContextIds.delete(id);
1977
+ const owner = this._wrapContext(userContext);
1978
+ void owner._isInternalPageContext(id).then((internal) => {
1979
+ if (internal || this._destroyedTopLevelContextIds.has(id))
1980
+ return;
1981
+ this._topLevelContextUserContexts.set(id, userContext);
1982
+ this._topLevelContextCacheVersion++;
1983
+ }).catch(() => { });
1892
1984
  };
1893
1985
  const onDestroyed = (params) => {
1894
1986
  if (params.parent)
1895
1987
  return;
1896
1988
  if (typeof params.context === 'string') {
1989
+ this._destroyedTopLevelContextIds.add(params.context);
1897
1990
  this._topLevelContextUserContexts.delete(params.context);
1898
1991
  this._topLevelContextCacheVersion++;
1899
1992
  }
@@ -1922,6 +2015,9 @@ export class Browser {
1922
2015
  }
1923
2016
  for (const ctx of tree.contexts ?? []) {
1924
2017
  if (!ctx.parent) {
2018
+ const owner = this._wrapContext(ctx.userContext ?? 'default');
2019
+ if (await owner._isInternalPageContext(ctx.context))
2020
+ continue;
1925
2021
  this._topLevelContextUserContexts.set(ctx.context, ctx.userContext ?? 'default');
1926
2022
  }
1927
2023
  }
@@ -1929,7 +2025,7 @@ export class Browser {
1929
2025
  }
1930
2026
  _firstDefaultTopLevelContext() {
1931
2027
  for (const [id, userContext] of this._topLevelContextUserContexts) {
1932
- if (userContext === 'default')
2028
+ if (userContext === 'default' && !this._isInternalContextId(id))
1933
2029
  return id;
1934
2030
  }
1935
2031
  return undefined;
@@ -1958,13 +2054,17 @@ export class Browser {
1958
2054
  const conn = this.bidiSession.getConnection();
1959
2055
  const handle = await this.driver.getCurrentWindowHandle().catch(() => '');
1960
2056
  await this._ensureTopLevelContextTracking();
1961
- if (handle && this._topLevelContextUserContexts.get(handle) === 'default') {
2057
+ if (handle &&
2058
+ this._topLevelContextUserContexts.get(handle) === 'default' &&
2059
+ !this._isInternalContextId(handle)) {
1962
2060
  return new Page(this.driver, handle, this.getDefaultTimeout, conn, this.defaultContext);
1963
2061
  }
1964
2062
  // Events should keep the cache warm, but a cheap top-level sync covers
1965
2063
  // missed startup events and direct protocol/browser changes.
1966
2064
  await this._refreshTopLevelContextCache(conn);
1967
- if (handle && this._topLevelContextUserContexts.get(handle) === 'default') {
2065
+ if (handle &&
2066
+ this._topLevelContextUserContexts.get(handle) === 'default' &&
2067
+ !this._isInternalContextId(handle)) {
1968
2068
  return new Page(this.driver, handle, this.getDefaultTimeout, conn, this.defaultContext);
1969
2069
  }
1970
2070
  const fallback = this._firstDefaultTopLevelContext();
@@ -2171,7 +2271,14 @@ export class Browser {
2171
2271
  }
2172
2272
  find(selector) {
2173
2273
  const by = typeof selector === 'string' ? By.css(selector) : selector;
2174
- return new ElementHandle(this.driver, by, this.getDefaultTimeout);
2274
+ return new ElementHandle(this.driver, by, this.getDefaultTimeout).withBiDi(() => {
2275
+ if (!this.bidiSession?.isConnected())
2276
+ return undefined;
2277
+ const contextId = this.bidiSession.getContext();
2278
+ return contextId
2279
+ ? { connection: this.bidiSession.getConnection(), contextId }
2280
+ : undefined;
2281
+ });
2175
2282
  }
2176
2283
  /**
2177
2284
  * Return a lazy, chainable `Locator` for the given selector.
@@ -2179,7 +2286,14 @@ export class Browser {
2179
2286
  */
2180
2287
  locator(selector) {
2181
2288
  const by = typeof selector === 'string' ? By.css(selector) : selector;
2182
- return new Locator(this.driver, by, this.getDefaultTimeout);
2289
+ return new Locator(this.driver, by, this.getDefaultTimeout).withBiDi(() => {
2290
+ if (!this.bidiSession?.isConnected())
2291
+ return undefined;
2292
+ const contextId = this.bidiSession.getContext();
2293
+ return contextId
2294
+ ? { connection: this.bidiSession.getConnection(), contextId }
2295
+ : undefined;
2296
+ });
2183
2297
  }
2184
2298
  /**
2185
2299
  * Return snapshot `ElementHandle`s for all elements matching `selector` at call time.
@@ -2188,28 +2302,35 @@ export class Browser {
2188
2302
  async findAll(selector) {
2189
2303
  const by = typeof selector === 'string' ? By.css(selector) : selector;
2190
2304
  const webElements = await this.driver.findElements(by);
2191
- return webElements.map((we) => ElementHandle.fromWebElement(this.driver, we, this.getDefaultTimeout));
2305
+ return webElements.map((we) => ElementHandle.fromWebElement(this.driver, we, this.getDefaultTimeout).withBiDi(() => {
2306
+ if (!this.bidiSession?.isConnected())
2307
+ return undefined;
2308
+ const contextId = this.bidiSession.getContext();
2309
+ return contextId
2310
+ ? { connection: this.bidiSession.getConnection(), contextId }
2311
+ : undefined;
2312
+ }));
2192
2313
  }
2193
2314
  getByRole(role, opts) {
2194
- return new ElementHandle(this.driver, By.role(role, opts), this.getDefaultTimeout);
2315
+ return this.find(By.role(role, opts));
2195
2316
  }
2196
2317
  getByText(text, opts) {
2197
- return new ElementHandle(this.driver, By.text(text, opts), this.getDefaultTimeout);
2318
+ return this.find(By.text(text, opts));
2198
2319
  }
2199
2320
  getByLabel(text, opts) {
2200
- return new ElementHandle(this.driver, By.labelText(text, opts), this.getDefaultTimeout);
2321
+ return this.find(By.labelText(text, opts));
2201
2322
  }
2202
2323
  getByPlaceholder(text, opts) {
2203
- return new ElementHandle(this.driver, By.placeholder(text, opts), this.getDefaultTimeout);
2324
+ return this.find(By.placeholder(text, opts));
2204
2325
  }
2205
2326
  getByTestId(id) {
2206
- return new ElementHandle(this.driver, By.testId(id), this.getDefaultTimeout);
2327
+ return this.find(By.testId(id));
2207
2328
  }
2208
2329
  getByAltText(text, opts) {
2209
- return new ElementHandle(this.driver, By.altText(text, opts), this.getDefaultTimeout);
2330
+ return this.find(By.altText(text, opts));
2210
2331
  }
2211
2332
  getByTitle(text, opts) {
2212
- return new ElementHandle(this.driver, By.title(text, opts), this.getDefaultTimeout);
2333
+ return this.find(By.title(text, opts));
2213
2334
  }
2214
2335
  // Keyboard controller and enum for nicer usage: browser.keyboard.press(Key.Enter)
2215
2336
  keyboard;