@xbrowser/cli 0.16.0 → 1.0.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 (30) hide show
  1. package/dist/{browser-R7B255ML.js → browser-53KUFEEM.js} +5 -1
  2. package/dist/{browser-I2HJZ7IP.js → browser-DSVV4GHS.js} +2 -2
  3. package/dist/{browser-GWBH6OJK.js → browser-GURRY444.js} +3 -1
  4. package/dist/cdp-driver-MNPR3HZH.js +2537 -0
  5. package/dist/cdp-driver-SSXUGXP6.js +47 -0
  6. package/dist/{chunk-2ONMTDLK.js → chunk-2BQZIT3S.js} +2535 -50
  7. package/dist/{chunk-KDYXFLAC.js → chunk-2MFXKN32.js} +2 -2
  8. package/dist/chunk-42RPMJ76.js +2530 -0
  9. package/dist/{chunk-F3ZWFCJJ.js → chunk-E4O5ZU3H.js} +2535 -50
  10. package/dist/{chunk-ATFTAKMN.js → chunk-IDVD44ED.js} +20 -0
  11. package/dist/chunk-T4J4C2NZ.js +250 -0
  12. package/dist/{chunk-RS6YYWTK.js → chunk-YKOHDEFV.js} +73 -38
  13. package/dist/cli.js +1054 -140
  14. package/dist/{convert-4DUWZIKH.js → convert-EGFYNICZ.js} +2 -0
  15. package/dist/{daemon-client-GX2UYIW4.js → daemon-client-3VM7VU7O.js} +22 -0
  16. package/dist/{daemon-client-3IJD6X4B.js → daemon-client-YAVQ343A.js} +7 -1
  17. package/dist/daemon-main.js +984 -114
  18. package/dist/{extract-EGRXZSSK.js → extract-L2IW3IUB.js} +2 -0
  19. package/dist/{filter-OLAE26HN.js → filter-HC4RA7JY.js} +2 -0
  20. package/dist/index.d.ts +581 -41
  21. package/dist/index.js +1100 -182
  22. package/dist/launcher-KA7J32K5.js +19 -0
  23. package/dist/{network-store-YAF5OIBH.js → network-store-66A2RATI.js} +1 -0
  24. package/dist/{session-recorder-XET3DNML.js → session-recorder-MA75PKTQ.js} +1 -1
  25. package/package.json +2 -3
  26. package/dist/daemon-client-XWSSQBEA.js +0 -58
  27. package/dist/network-store-2S5HATEV.js +0 -194
  28. package/dist/parse-action-dsl-DRSPBALP.js +0 -72
  29. package/dist/screenshot-MB6R7RSS.js +0 -26
  30. package/dist/session-recorder-ILSSV2UC.js +0 -6
@@ -0,0 +1,2530 @@
1
+ import {
2
+ connectToCDP,
3
+ launchChrome
4
+ } from "./chunk-T4J4C2NZ.js";
5
+ import {
6
+ __require
7
+ } from "./chunk-3RG5ZIWI.js";
8
+
9
+ // src/cdp-driver/browser.ts
10
+ import { EventEmitter as EventEmitter3 } from "events";
11
+
12
+ // src/cdp-driver/context.ts
13
+ import { EventEmitter as EventEmitter2 } from "events";
14
+
15
+ // src/cdp-driver/page.ts
16
+ import { EventEmitter } from "events";
17
+
18
+ // src/cdp-driver/mouse.ts
19
+ var XBMouseImpl = class {
20
+ conn;
21
+ sessionId;
22
+ _x = 0;
23
+ _y = 0;
24
+ _button = "none";
25
+ constructor(conn, sessionId) {
26
+ this.conn = conn;
27
+ this.sessionId = sessionId;
28
+ }
29
+ /** Current cursor X position */
30
+ get x() {
31
+ return this._x;
32
+ }
33
+ /** Current cursor Y position */
34
+ get y() {
35
+ return this._y;
36
+ }
37
+ async click(x, y, opts = {}) {
38
+ const button = opts.button ?? "left";
39
+ const clickCount = opts.clickCount ?? 1;
40
+ const delay = opts.delay ?? 0;
41
+ await this.move(x, y);
42
+ await this.down({ button });
43
+ if (delay > 0) {
44
+ await sleep(delay);
45
+ }
46
+ await this.up({ button });
47
+ for (let i = 1; i < clickCount; i++) {
48
+ if (delay > 0) await sleep(delay);
49
+ await this.down({ button });
50
+ if (delay > 0) await sleep(delay);
51
+ await this.up({ button });
52
+ }
53
+ }
54
+ async dblclick(x, y, opts = {}) {
55
+ await this.click(x, y, { clickCount: 2, button: opts.button });
56
+ }
57
+ async down(opts = {}) {
58
+ const button = opts.button ?? "left";
59
+ this._button = button;
60
+ await this.send("Input.dispatchMouseEvent", {
61
+ type: "mousePressed",
62
+ x: this._x,
63
+ y: this._y,
64
+ button,
65
+ clickCount: 1
66
+ });
67
+ }
68
+ async up(opts = {}) {
69
+ const button = opts.button ?? "left";
70
+ this._button = "none";
71
+ await this.send("Input.dispatchMouseEvent", {
72
+ type: "mouseReleased",
73
+ x: this._x,
74
+ y: this._y,
75
+ button,
76
+ clickCount: 1
77
+ });
78
+ }
79
+ async move(x, y, opts = {}) {
80
+ const steps = Math.max(1, opts.steps ?? 1);
81
+ const fromX = this._x;
82
+ const fromY = this._y;
83
+ const dx = x - fromX;
84
+ const dy = y - fromY;
85
+ for (let i = 1; i <= steps; i++) {
86
+ const t = i / steps;
87
+ this._x = fromX + dx * t;
88
+ this._y = fromY + dy * t;
89
+ await this.send("Input.dispatchMouseEvent", {
90
+ type: "mouseMoved",
91
+ x: this._x,
92
+ y: this._y,
93
+ button: this._button
94
+ });
95
+ }
96
+ this._x = x;
97
+ this._y = y;
98
+ }
99
+ async wheel(deltaX, deltaY) {
100
+ await this.send("Input.dispatchMouseEvent", {
101
+ type: "mouseWheel",
102
+ x: this._x,
103
+ y: this._y,
104
+ deltaX,
105
+ deltaY
106
+ });
107
+ }
108
+ async send(method, params) {
109
+ await this.conn.send(method, params, this.sessionId);
110
+ }
111
+ };
112
+ function sleep(ms) {
113
+ return new Promise((resolve) => setTimeout(resolve, ms));
114
+ }
115
+
116
+ // src/cdp-driver/keyboard.ts
117
+ var XBKeyboardImpl = class {
118
+ conn;
119
+ sessionId;
120
+ constructor(conn, sessionId) {
121
+ this.conn = conn;
122
+ this.sessionId = sessionId;
123
+ }
124
+ async press(key, opts = {}) {
125
+ const delay = opts.delay ?? 0;
126
+ const mapping = resolveKeyMapping(key);
127
+ const downParams = {
128
+ type: "rawKeyDown",
129
+ key: mapping.key,
130
+ code: mapping.code
131
+ };
132
+ if (mapping.text) {
133
+ downParams.text = mapping.text;
134
+ downParams.unmodifiedText = mapping.text;
135
+ }
136
+ if (mapping.keyCode) {
137
+ downParams.windowsVirtualKeyCode = mapping.keyCode;
138
+ }
139
+ await this.dispatchKeyEvent(downParams);
140
+ if (mapping.text) {
141
+ await this.dispatchKeyEvent({
142
+ type: "char",
143
+ text: mapping.text
144
+ });
145
+ }
146
+ if (delay > 0) await sleep2(delay);
147
+ const upParams = {
148
+ type: "keyUp",
149
+ key: mapping.key,
150
+ code: mapping.code
151
+ };
152
+ if (mapping.keyCode) {
153
+ upParams.windowsVirtualKeyCode = mapping.keyCode;
154
+ }
155
+ await this.dispatchKeyEvent(upParams);
156
+ }
157
+ async down(key) {
158
+ const mapping = resolveKeyMapping(key);
159
+ const params = {
160
+ type: "rawKeyDown",
161
+ key: mapping.key,
162
+ code: mapping.code
163
+ };
164
+ if (mapping.text) {
165
+ params.text = mapping.text;
166
+ params.unmodifiedText = mapping.text;
167
+ }
168
+ if (mapping.keyCode) {
169
+ params.windowsVirtualKeyCode = mapping.keyCode;
170
+ }
171
+ await this.dispatchKeyEvent(params);
172
+ }
173
+ async up(key) {
174
+ const mapping = resolveKeyMapping(key);
175
+ const params = {
176
+ type: "keyUp",
177
+ key: mapping.key,
178
+ code: mapping.code
179
+ };
180
+ if (mapping.keyCode) {
181
+ params.windowsVirtualKeyCode = mapping.keyCode;
182
+ }
183
+ await this.dispatchKeyEvent(params);
184
+ }
185
+ async type(text, opts = {}) {
186
+ const delay = opts.delay ?? 0;
187
+ for (const char of text) {
188
+ if (delay > 0) await sleep2(delay);
189
+ const mapping = resolveKeyMapping(char);
190
+ const downParams = {
191
+ type: "rawKeyDown",
192
+ key: mapping.key,
193
+ code: mapping.code
194
+ };
195
+ if (mapping.text) {
196
+ downParams.text = mapping.text;
197
+ downParams.unmodifiedText = mapping.text;
198
+ }
199
+ if (mapping.keyCode) {
200
+ downParams.windowsVirtualKeyCode = mapping.keyCode;
201
+ }
202
+ await this.dispatchKeyEvent(downParams);
203
+ if (mapping.text) {
204
+ await this.dispatchKeyEvent({
205
+ type: "char",
206
+ text: mapping.text
207
+ });
208
+ }
209
+ await this.dispatchKeyEvent({
210
+ type: "keyUp",
211
+ key: mapping.key,
212
+ code: mapping.code,
213
+ ...mapping.keyCode ? { windowsVirtualKeyCode: mapping.keyCode } : {}
214
+ });
215
+ }
216
+ }
217
+ async insertText(text) {
218
+ await this.conn.send(
219
+ "Input.insertText",
220
+ { text },
221
+ this.sessionId
222
+ );
223
+ }
224
+ async dispatchKeyEvent(params) {
225
+ await this.conn.send("Input.dispatchKeyEvent", params, this.sessionId);
226
+ }
227
+ };
228
+ function resolveKeyMapping(key) {
229
+ if (KEY_MAP[key]) return KEY_MAP[key];
230
+ if (key.length === 1) {
231
+ const lower = key.toLowerCase();
232
+ if (lower >= "a" && lower <= "z") {
233
+ const code = `Key${lower.toUpperCase()}`;
234
+ const keyCode = lower.charCodeAt(0) - 32;
235
+ return { key, code, text: key, keyCode };
236
+ }
237
+ if (key >= "0" && key <= "9") {
238
+ const code = `Digit${key}`;
239
+ const keyCode = key.charCodeAt(0);
240
+ return { key, code, text: key, keyCode };
241
+ }
242
+ return { key, code: key, text: key };
243
+ }
244
+ return { key, code: key };
245
+ }
246
+ var KEY_MAP = {
247
+ Enter: { key: "Enter", code: "Enter", text: "\r" },
248
+ Tab: { key: "Tab", code: "Tab", text: " " },
249
+ Escape: { key: "Escape", code: "Escape" },
250
+ Backspace: { key: "Backspace", code: "Backspace" },
251
+ Delete: { key: "Delete", code: "Delete" },
252
+ Space: { key: " ", code: "Space", text: " " },
253
+ ArrowUp: { key: "ArrowUp", code: "ArrowUp" },
254
+ ArrowDown: { key: "ArrowDown", code: "ArrowDown" },
255
+ ArrowLeft: { key: "ArrowLeft", code: "ArrowLeft" },
256
+ ArrowRight: { key: "ArrowRight", code: "ArrowRight" },
257
+ Home: { key: "Home", code: "Home" },
258
+ End: { key: "End", code: "End" },
259
+ PageUp: { key: "PageUp", code: "PageUp" },
260
+ PageDown: { key: "PageDown", code: "PageDown" },
261
+ Control: { key: "Control", code: "ControlLeft" },
262
+ Shift: { key: "Shift", code: "ShiftLeft" },
263
+ Alt: { key: "Alt", code: "AltLeft" },
264
+ Meta: { key: "Meta", code: "MetaLeft" },
265
+ F1: { key: "F1", code: "F1" },
266
+ F2: { key: "F2", code: "F2" },
267
+ F3: { key: "F3", code: "F3" },
268
+ F4: { key: "F4", code: "F4" },
269
+ F5: { key: "F5", code: "F5" },
270
+ F6: { key: "F6", code: "F6" },
271
+ F7: { key: "F7", code: "F7" },
272
+ F8: { key: "F8", code: "F8" },
273
+ F9: { key: "F9", code: "F9" },
274
+ F10: { key: "F10", code: "F10" },
275
+ F11: { key: "F11", code: "F11" },
276
+ F12: { key: "F12", code: "F12" }
277
+ };
278
+ function sleep2(ms) {
279
+ return new Promise((resolve) => setTimeout(resolve, ms));
280
+ }
281
+
282
+ // src/cdp-driver/actionability.ts
283
+ async function waitForActionable(page, selector, opts = {}) {
284
+ const timeout = opts.timeout ?? 3e4;
285
+ if (opts.force) {
286
+ const nodeId = await page.querySelector(selector);
287
+ if (!nodeId) throw new Error(`Element not found: ${selector}`);
288
+ const rect = await page.getBoxModel(nodeId);
289
+ if (!rect) throw new Error(`Element has no box: ${selector}`);
290
+ return { nodeId, rect };
291
+ }
292
+ const deadline = Date.now() + timeout;
293
+ while (Date.now() < deadline) {
294
+ const result = await checkActionable(page, selector);
295
+ if (result.ok && result.rect) {
296
+ const nodeId = await page.querySelector(selector);
297
+ if (nodeId) return { nodeId, rect: result.rect };
298
+ }
299
+ await page.waitForTimeout(50);
300
+ }
301
+ throw new Error(`Actionability timeout: element '${selector}' not ready after ${timeout}ms`);
302
+ }
303
+ async function checkActionable(page, selector) {
304
+ const result = await page.evaluate(`
305
+ (function() {
306
+ const el = document.querySelector(${JSON.stringify(selector)});
307
+ if (!el) return { ok: false, reason: 'not_found' };
308
+
309
+ // Check attached to DOM
310
+ if (!el.isConnected) return { ok: false, reason: 'detached' };
311
+
312
+ // Check visibility
313
+ const style = window.getComputedStyle(el);
314
+ if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') {
315
+ return { ok: false, reason: 'invisible' };
316
+ }
317
+
318
+ // Check non-zero size
319
+ const rect = el.getBoundingClientRect();
320
+ if (rect.width === 0 || rect.height === 0) {
321
+ return { ok: false, reason: 'zero_size' };
322
+ }
323
+
324
+ // Check enabled (for form elements)
325
+ if (el.disabled) return { ok: false, reason: 'disabled' };
326
+ if (el.tagName === 'OPTION' && el.closest('select')?.disabled) {
327
+ return { ok: false, reason: 'parent_disabled' };
328
+ }
329
+
330
+ // Check not covered by another element at center
331
+ const cx = rect.x + rect.width / 2;
332
+ const cy = rect.y + rect.height / 2;
333
+ const topEl = document.elementFromPoint(cx, cy);
334
+ if (topEl && topEl !== el && !el.contains(topEl) && !topEl.contains(el)) {
335
+ return { ok: false, reason: 'covered', rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } };
336
+ }
337
+
338
+ return {
339
+ ok: true,
340
+ rect: { x: rect.x, y: rect.y, width: rect.width, height: rect.height },
341
+ };
342
+ })()
343
+ `);
344
+ return result;
345
+ }
346
+ async function scrollIntoView(page, selector) {
347
+ await page.evaluate(`
348
+ (function() {
349
+ const el = document.querySelector(${JSON.stringify(selector)});
350
+ if (el) el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' });
351
+ })()
352
+ `);
353
+ }
354
+
355
+ // src/cdp-driver/locator.ts
356
+ var XBLocatorImpl = class _XBLocatorImpl {
357
+ page;
358
+ selector;
359
+ constructor(page, selector) {
360
+ this.page = page;
361
+ this.selector = selector;
362
+ }
363
+ // ── Actions ─────────────────────────────────────────────────
364
+ async click(opts = {}) {
365
+ const { rect } = await waitForActionable(this.page, this.selector, opts);
366
+ await scrollIntoView(this.page, this.selector);
367
+ const updatedRect = await this.page.evaluate(`
368
+ (function() {
369
+ const el = document.querySelector(${JSON.stringify(this.selector)});
370
+ if (!el) return null;
371
+ const rect = el.getBoundingClientRect();
372
+ return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
373
+ })()
374
+ `);
375
+ const finalRect = updatedRect ?? rect;
376
+ const cx = finalRect.x + finalRect.width / 2;
377
+ const cy = finalRect.y + finalRect.height / 2;
378
+ await this.page.mouse.click(cx, cy, {
379
+ button: opts.button ?? "left",
380
+ clickCount: opts.clickCount ?? 1,
381
+ delay: opts.delay
382
+ });
383
+ }
384
+ async fill(value, opts = {}) {
385
+ await waitForActionable(this.page, this.selector, opts);
386
+ await scrollIntoView(this.page, this.selector);
387
+ await this.page.evaluate(`
388
+ (function() {
389
+ const el = document.querySelector(${JSON.stringify(this.selector)});
390
+ if (!el) throw new Error('Element not found: ${this.selector.replace(/'/g, "\\'")}');
391
+ el.focus();
392
+ el.value = '';
393
+ el.dispatchEvent(new Event('input', { bubbles: true }));
394
+ })()
395
+ `);
396
+ await this.page.keyboard.insertText(value);
397
+ await this.page.evaluate(`
398
+ (function() {
399
+ const el = document.querySelector(${JSON.stringify(this.selector)});
400
+ if (el) {
401
+ el.dispatchEvent(new Event('input', { bubbles: true }));
402
+ el.dispatchEvent(new Event('change', { bubbles: true }));
403
+ }
404
+ })()
405
+ `);
406
+ }
407
+ async press(key, opts = {}) {
408
+ await waitForActionable(this.page, this.selector, { timeout: opts.timeout });
409
+ await scrollIntoView(this.page, this.selector);
410
+ await this.page.evaluate(`
411
+ (function() {
412
+ const el = document.querySelector(${JSON.stringify(this.selector)});
413
+ if (el) el.focus();
414
+ })()
415
+ `);
416
+ await this.page.keyboard.press(key);
417
+ }
418
+ async pressSequentially(text, opts = {}) {
419
+ await waitForActionable(this.page, this.selector, { timeout: opts.timeout });
420
+ await scrollIntoView(this.page, this.selector);
421
+ await this.page.evaluate(`
422
+ (function() {
423
+ const el = document.querySelector(${JSON.stringify(this.selector)});
424
+ if (el) el.focus();
425
+ })()
426
+ `);
427
+ await this.page.keyboard.type(text, { delay: opts.delay });
428
+ }
429
+ async hover(opts = {}) {
430
+ if (!opts.force) {
431
+ const { rect } = await waitForActionable(this.page, this.selector, { timeout: opts.timeout });
432
+ await scrollIntoView(this.page, this.selector);
433
+ const cx = rect.x + rect.width / 2;
434
+ const cy = rect.y + rect.height / 2;
435
+ await this.page.mouse.move(cx, cy);
436
+ } else {
437
+ await scrollIntoView(this.page, this.selector);
438
+ const rect = await this.page.evaluate(`
439
+ (function() {
440
+ const el = document.querySelector(${JSON.stringify(this.selector)});
441
+ if (!el) return { x: 0, y: 0, width: 0, height: 0 };
442
+ const r = el.getBoundingClientRect();
443
+ return { x: r.x, y: r.y, width: r.width, height: r.height };
444
+ })()
445
+ `);
446
+ const cx = rect.x + rect.width / 2;
447
+ const cy = rect.y + rect.height / 2;
448
+ await this.page.mouse.move(cx, cy);
449
+ }
450
+ }
451
+ async type(text, opts = {}) {
452
+ await this.pressSequentially(text, opts);
453
+ }
454
+ async check(opts = {}) {
455
+ await waitForActionable(this.page, this.selector, { timeout: opts.timeout });
456
+ const isChecked = await this.page.evaluate(`
457
+ (function() {
458
+ const el = document.querySelector(${JSON.stringify(this.selector)});
459
+ return el?.checked === true;
460
+ })()
461
+ `);
462
+ if (!isChecked) {
463
+ await this.click({ timeout: opts.timeout });
464
+ }
465
+ }
466
+ async uncheck(opts = {}) {
467
+ await waitForActionable(this.page, this.selector, { timeout: opts.timeout });
468
+ const isChecked = await this.page.evaluate(`
469
+ (function() {
470
+ const el = document.querySelector(${JSON.stringify(this.selector)});
471
+ return el?.checked === true;
472
+ })()
473
+ `);
474
+ if (isChecked) {
475
+ await this.click({ timeout: opts.timeout });
476
+ }
477
+ }
478
+ async selectOption(value) {
479
+ await waitForActionable(this.page, this.selector);
480
+ const values = Array.isArray(value) ? value : [value];
481
+ const selected = await this.page.evaluate(`
482
+ (function() {
483
+ const el = document.querySelector(${JSON.stringify(this.selector)});
484
+ if (!el || el.tagName !== 'SELECT') throw new Error('Not a select element');
485
+
486
+ const values = ${JSON.stringify(values)};
487
+ const selectedValues = [];
488
+
489
+ for (const opt of el.options) {
490
+ for (const v of values) {
491
+ if (typeof v === 'object') {
492
+ if (v.label && opt.label === v.label) { opt.selected = true; selectedValues.push(opt.value); }
493
+ else if (v.value && opt.value === v.value) { opt.selected = true; selectedValues.push(opt.value); }
494
+ else if (v.index !== undefined && opt.index === v.index) { opt.selected = true; selectedValues.push(opt.value); }
495
+ } else if (opt.value === v || opt.label === v) {
496
+ opt.selected = true;
497
+ selectedValues.push(opt.value);
498
+ }
499
+ }
500
+ }
501
+
502
+ el.dispatchEvent(new Event('input', { bubbles: true }));
503
+ el.dispatchEvent(new Event('change', { bubbles: true }));
504
+ return selectedValues;
505
+ })()
506
+ `);
507
+ return selected;
508
+ }
509
+ async screenshot(opts = {}) {
510
+ await waitForActionable(this.page, this.selector);
511
+ const nodeId = await this.page.querySelector(this.selector);
512
+ if (!nodeId) throw new Error(`Element not found: ${this.selector}`);
513
+ const box = await this.page.getBoxModel(nodeId);
514
+ if (!box) throw new Error("Element has no box");
515
+ return this.page.screenshot({
516
+ ...opts,
517
+ clip: { x: box.x, y: box.y, width: box.width, height: box.height }
518
+ });
519
+ }
520
+ // ── State checks ────────────────────────────────────────────
521
+ async waitFor(opts = {}) {
522
+ await this.page.waitForSelector(this.selector, opts);
523
+ }
524
+ async count() {
525
+ return this.page.evaluate(`
526
+ document.querySelectorAll(${JSON.stringify(this.selector)}).length
527
+ `);
528
+ }
529
+ async isVisible() {
530
+ try {
531
+ const result = await this.page.evaluate(`
532
+ (function() {
533
+ const el = document.querySelector(${JSON.stringify(this.selector)});
534
+ if (!el) return false;
535
+ if (!el.isConnected) return false;
536
+ const style = window.getComputedStyle(el);
537
+ if (style.display === 'none' || style.visibility === 'hidden') return false;
538
+ const rect = el.getBoundingClientRect();
539
+ return rect.width > 0 && rect.height > 0;
540
+ })()
541
+ `);
542
+ return Boolean(result);
543
+ } catch {
544
+ return false;
545
+ }
546
+ }
547
+ async isHidden() {
548
+ return !await this.isVisible();
549
+ }
550
+ async isEnabled() {
551
+ return this.page.evaluate(`
552
+ (function() {
553
+ const el = document.querySelector(${JSON.stringify(this.selector)});
554
+ if (!el) return false;
555
+ return !el.disabled;
556
+ })()
557
+ `);
558
+ }
559
+ async isDisabled() {
560
+ return !await this.isEnabled();
561
+ }
562
+ async boundingBox() {
563
+ const nodeId = await this.page.querySelector(this.selector);
564
+ if (!nodeId) return null;
565
+ return this.page.getBoxModel(nodeId);
566
+ }
567
+ // ── Text/HTML ───────────────────────────────────────────────
568
+ async textContent() {
569
+ return this.page.evaluate(`
570
+ (function() {
571
+ const el = document.querySelector(${JSON.stringify(this.selector)});
572
+ return el?.textContent ?? null;
573
+ })()
574
+ `);
575
+ }
576
+ async innerText() {
577
+ return this.page.evaluate(`
578
+ (function() {
579
+ const el = document.querySelector(${JSON.stringify(this.selector)});
580
+ if (!el) throw new Error('Element not found');
581
+ return el.innerText;
582
+ })()
583
+ `);
584
+ }
585
+ async innerHTML() {
586
+ return this.page.evaluate(`
587
+ (function() {
588
+ const el = document.querySelector(${JSON.stringify(this.selector)});
589
+ if (!el) throw new Error('Element not found');
590
+ return el.innerHTML;
591
+ })()
592
+ `);
593
+ }
594
+ async getAttribute(name) {
595
+ return this.page.evaluate(`
596
+ (function() {
597
+ const el = document.querySelector(${JSON.stringify(this.selector)});
598
+ return el?.getAttribute(${JSON.stringify(name)}) ?? null;
599
+ })()
600
+ `);
601
+ }
602
+ // ── Evaluate ────────────────────────────────────────────────
603
+ async evaluate(fn, ...args) {
604
+ const fnBody = typeof fn === "function" ? fn.toString() : fn;
605
+ return this.page.evaluate(
606
+ `(function(sel, fnStr, ...evalArgs) {
607
+ const el = document.querySelector(sel);
608
+ if (!el) throw new Error('No element found for selector: ' + sel);
609
+ const fn = new Function('return ' + fnStr)();
610
+ return fn(el, ...evalArgs);
611
+ })(${JSON.stringify(this.selector)}, ${JSON.stringify(fnBody)}${args.length > 0 ? ", " + args.map((a) => JSON.stringify(a)).join(", ") : ""})`
612
+ );
613
+ }
614
+ async ariaSnapshot() {
615
+ const result = await this.page._cdpSend(
616
+ "Accessibility.getFullAXTree"
617
+ );
618
+ return result.nodes.map((n) => `${n.role?.value}: ${n.name?.value ?? ""}`).join("\n");
619
+ }
620
+ // ── Filtering ───────────────────────────────────────────────
621
+ first() {
622
+ return new FilteredLocator(this.page, this.selector, 0);
623
+ }
624
+ last() {
625
+ return new FilteredLocator(this.page, this.selector, -1);
626
+ }
627
+ nth(index) {
628
+ return new FilteredLocator(this.page, this.selector, index);
629
+ }
630
+ filter(opts) {
631
+ if (opts.visible) {
632
+ return new VisibleFilteredLocator(this.page, this.selector);
633
+ }
634
+ return new _XBLocatorImpl(this.page, this.selector);
635
+ }
636
+ async all() {
637
+ const n = await this.page.evaluate(`
638
+ document.querySelectorAll(${JSON.stringify(this.selector)}).length
639
+ `);
640
+ const locators = [];
641
+ for (let i = 0; i < n; i++) {
642
+ locators.push(new FilteredLocator(this.page, this.selector, i));
643
+ }
644
+ return locators;
645
+ }
646
+ async focus() {
647
+ await this.page.evaluate(`
648
+ (function() {
649
+ const el = document.querySelector(${JSON.stringify(this.selector)});
650
+ if (el) el.focus();
651
+ })()
652
+ `);
653
+ }
654
+ };
655
+ var FilteredLocator = class extends XBLocatorImpl {
656
+ constructor(page, selector, index) {
657
+ const indexedSelector = index === -1 ? `${selector}:last-of-type` : `${selector}:nth-of-type(${index + 1})`;
658
+ super(page, indexedSelector);
659
+ }
660
+ };
661
+ var VisibleFilteredLocator = class extends XBLocatorImpl {
662
+ async _withVisibleTag(fn) {
663
+ const tag = `data-xb-vt-${Date.now()}-${Math.floor(Math.random() * 1e6)}`;
664
+ const found = await this.page.evaluate(`
665
+ (function() {
666
+ const els = document.querySelectorAll(${JSON.stringify(this.selector)});
667
+ for (const el of els) {
668
+ if (!el.isConnected) continue;
669
+ const style = window.getComputedStyle(el);
670
+ if (style.display === 'none' || style.visibility === 'hidden' || style.opacity === '0') continue;
671
+ const rect = el.getBoundingClientRect();
672
+ if (rect.width === 0 || rect.height === 0) continue;
673
+ el.setAttribute(${JSON.stringify(tag)}, '');
674
+ return true;
675
+ }
676
+ return false;
677
+ })()
678
+ `);
679
+ if (!found) throw new Error(`No visible element found for: ${this.selector}`);
680
+ try {
681
+ return await fn(`[${tag}]`);
682
+ } finally {
683
+ await this.page.evaluate(`
684
+ document.querySelectorAll(${JSON.stringify(`[${tag}]`)}).forEach(el => el.removeAttribute(${JSON.stringify(tag)}))
685
+ `);
686
+ }
687
+ }
688
+ async click(opts) {
689
+ return this._withVisibleTag((tagSel) => new XBLocatorImpl(this.page, tagSel).click(opts));
690
+ }
691
+ async fill(value, opts) {
692
+ return this._withVisibleTag((tagSel) => new XBLocatorImpl(this.page, tagSel).fill(value, opts));
693
+ }
694
+ async press(key, opts) {
695
+ return this._withVisibleTag((tagSel) => new XBLocatorImpl(this.page, tagSel).press(key, opts));
696
+ }
697
+ async hover(opts) {
698
+ return this._withVisibleTag((tagSel) => new XBLocatorImpl(this.page, tagSel).hover(opts));
699
+ }
700
+ async count() {
701
+ return this.page.evaluate(`
702
+ (function() {
703
+ let count = 0;
704
+ const els = document.querySelectorAll(${JSON.stringify(this.selector)});
705
+ for (const el of els) {
706
+ if (!el.isConnected) continue;
707
+ const style = window.getComputedStyle(el);
708
+ if (style.display === 'none' || style.visibility === 'hidden') continue;
709
+ const rect = el.getBoundingClientRect();
710
+ if (rect.width === 0 || rect.height === 0) continue;
711
+ count++;
712
+ }
713
+ return count;
714
+ })()
715
+ `);
716
+ }
717
+ async isVisible() {
718
+ try {
719
+ const result = await this.page.evaluate(`
720
+ (function() {
721
+ const els = document.querySelectorAll(${JSON.stringify(this.selector)});
722
+ for (const el of els) {
723
+ if (!el.isConnected) continue;
724
+ const style = window.getComputedStyle(el);
725
+ if (style.display === 'none' || style.visibility === 'hidden') continue;
726
+ const rect = el.getBoundingClientRect();
727
+ if (rect.width > 0 && rect.height > 0) return true;
728
+ }
729
+ return false;
730
+ })()
731
+ `);
732
+ return result;
733
+ } catch {
734
+ return false;
735
+ }
736
+ }
737
+ async textContent() {
738
+ return this._withVisibleTag((tagSel) => new XBLocatorImpl(this.page, tagSel).textContent());
739
+ }
740
+ async innerText() {
741
+ return this._withVisibleTag((tagSel) => new XBLocatorImpl(this.page, tagSel).innerText());
742
+ }
743
+ async waitFor(opts) {
744
+ const deadline = Date.now() + (opts?.timeout ?? 3e4);
745
+ while (Date.now() < deadline) {
746
+ if (await this.isVisible()) return;
747
+ await this.page.waitForTimeout(50);
748
+ }
749
+ throw new Error(`Timeout waiting for visible element: ${this.selector}`);
750
+ }
751
+ };
752
+
753
+ // src/cdp-driver/element-handle.ts
754
+ var XBElementHandleImpl = class {
755
+ page;
756
+ nodeId;
757
+ disposed = false;
758
+ constructor(page, nodeId) {
759
+ this.page = page;
760
+ this.nodeId = nodeId;
761
+ }
762
+ get _nodeId() {
763
+ return this.nodeId;
764
+ }
765
+ async click(opts = {}) {
766
+ if (this.disposed) throw new Error("Element handle disposed");
767
+ const box = await this.boundingBox();
768
+ if (!box) throw new Error("Element has no box");
769
+ await this.scrollIntoViewIfNeeded();
770
+ const cx = box.x + box.width / 2;
771
+ const cy = box.y + box.height / 2;
772
+ await this.page.mouse.click(cx, cy, {
773
+ button: opts.button ?? "left",
774
+ clickCount: opts.clickCount ?? 1,
775
+ delay: opts.delay
776
+ });
777
+ }
778
+ async fill(value, _opts = {}) {
779
+ if (this.disposed) throw new Error("Element handle disposed");
780
+ const objectId = await this.page.resolveNode(this.nodeId);
781
+ await this.page.callFunctionOn(
782
+ objectId,
783
+ `function(value) {
784
+ this.focus();
785
+ this.value = '';
786
+ this.dispatchEvent(new Event('input', { bubbles: true }));
787
+ }`,
788
+ [value]
789
+ );
790
+ await this.page.keyboard.insertText(value);
791
+ await this.page.callFunctionOn(
792
+ objectId,
793
+ `function() {
794
+ this.dispatchEvent(new Event('input', { bubbles: true }));
795
+ this.dispatchEvent(new Event('change', { bubbles: true }));
796
+ }`
797
+ );
798
+ }
799
+ async hover() {
800
+ const box = await this.boundingBox();
801
+ if (!box) throw new Error("Element has no box");
802
+ await this.scrollIntoViewIfNeeded();
803
+ await this.page.mouse.move(box.x + box.width / 2, box.y + box.height / 2);
804
+ }
805
+ async press(key) {
806
+ const objectId = await this.page.resolveNode(this.nodeId);
807
+ await this.page.callFunctionOn(objectId, "function() { this.focus(); }");
808
+ await this.page.keyboard.press(key);
809
+ }
810
+ async screenshot(opts = {}) {
811
+ const box = await this.boundingBox();
812
+ if (!box) throw new Error("Element has no box");
813
+ return this.page.screenshot({
814
+ ...opts,
815
+ clip: box
816
+ });
817
+ }
818
+ async boundingBox() {
819
+ return this.page.getBoxModel(this.nodeId);
820
+ }
821
+ async isVisible() {
822
+ try {
823
+ const objectId = await this.page.resolveNode(this.nodeId);
824
+ const result = await this.page.callFunctionOn(
825
+ objectId,
826
+ `function() {
827
+ if (!this.isConnected) return false;
828
+ const style = window.getComputedStyle(this);
829
+ if (style.display === 'none' || style.visibility === 'hidden') return false;
830
+ const rect = this.getBoundingClientRect();
831
+ return rect.width > 0 && rect.height > 0;
832
+ }`
833
+ );
834
+ return Boolean(result);
835
+ } catch {
836
+ return false;
837
+ }
838
+ }
839
+ async isEnabled() {
840
+ const objectId = await this.page.resolveNode(this.nodeId);
841
+ return this.page.callFunctionOn(objectId, "function() { return !this.disabled; }");
842
+ }
843
+ async textContent() {
844
+ const objectId = await this.page.resolveNode(this.nodeId);
845
+ return this.page.callFunctionOn(objectId, "function() { return this.textContent; }");
846
+ }
847
+ async innerText() {
848
+ const objectId = await this.page.resolveNode(this.nodeId);
849
+ return this.page.callFunctionOn(objectId, "function() { return this.innerText; }");
850
+ }
851
+ async innerHTML() {
852
+ const objectId = await this.page.resolveNode(this.nodeId);
853
+ return this.page.callFunctionOn(objectId, "function() { return this.innerHTML; }");
854
+ }
855
+ async getAttribute(name) {
856
+ const objectId = await this.page.resolveNode(this.nodeId);
857
+ return this.page.callFunctionOn(objectId, `function() { return this.getAttribute(${JSON.stringify(name)}); }`);
858
+ }
859
+ async scrollIntoViewIfNeeded() {
860
+ if (this.disposed) return;
861
+ const objectId = await this.page.resolveNode(this.nodeId);
862
+ await this.page.callFunctionOn(
863
+ objectId,
864
+ 'function() { this.scrollIntoView({ block: "center", inline: "center", behavior: "instant" }); }'
865
+ );
866
+ }
867
+ dispose() {
868
+ this.disposed = true;
869
+ }
870
+ };
871
+
872
+ // src/cdp-driver/page-helpers.ts
873
+ function globToRegex(glob) {
874
+ let pattern = glob.replace(/[\\^${}()|[\]+]/g, "\\$&").replace(/\./g, "\\.").replace(/\*\*/g, "{{DOUBLESTAR}}").replace(/\*/g, "[^/]*").replace(/{{DOUBLESTAR}}/g, ".*").replace(/\?/g, ".");
875
+ if (!pattern.startsWith("http") && !pattern.startsWith("\\.")) {
876
+ pattern = ".*" + pattern;
877
+ }
878
+ return new RegExp("^" + pattern + "$", "i");
879
+ }
880
+ function matchGlob(pattern, url) {
881
+ return globToRegex(pattern).test(url);
882
+ }
883
+ function createResponsePredicate(urlOrPredicate) {
884
+ if (typeof urlOrPredicate === "function") {
885
+ return urlOrPredicate;
886
+ }
887
+ if (urlOrPredicate instanceof RegExp) {
888
+ return (resp) => urlOrPredicate.test(resp.url());
889
+ }
890
+ const regex = globToRegex(urlOrPredicate);
891
+ return (resp) => regex.test(resp.url());
892
+ }
893
+ function createRequestPredicate(urlOrPredicate) {
894
+ if (typeof urlOrPredicate === "function") {
895
+ return urlOrPredicate;
896
+ }
897
+ if (urlOrPredicate instanceof RegExp) {
898
+ return (req) => urlOrPredicate.test(req.url());
899
+ }
900
+ const regex = globToRegex(urlOrPredicate);
901
+ return (req) => regex.test(req.url());
902
+ }
903
+ function createXBResponse(data, conn, sessionId, requestData) {
904
+ const request = requestData ? createXBRequest(null, requestData) : createXBRequest(null, { requestId: data.requestId, url: data.url, method: "GET", headers: {}, postData: null, resourceType: "other" });
905
+ return {
906
+ status: () => data.status,
907
+ statusText: () => "",
908
+ url: () => data.url,
909
+ headers: () => data.headers,
910
+ ok: () => data.status >= 200 && data.status < 300,
911
+ body: async () => {
912
+ if (!conn) throw new Error("Response body not available");
913
+ try {
914
+ const resp = await conn.send(
915
+ "Network.getResponseBody",
916
+ { requestId: data.requestId },
917
+ sessionId
918
+ );
919
+ return Buffer.from(resp.body, resp.base64Encoded ? "base64" : "utf8");
920
+ } catch {
921
+ throw new Error("Response body not available");
922
+ }
923
+ },
924
+ text: async () => {
925
+ if (!conn) throw new Error("Response body not available");
926
+ try {
927
+ const resp = await conn.send(
928
+ "Network.getResponseBody",
929
+ { requestId: data.requestId },
930
+ sessionId
931
+ );
932
+ return resp.base64Encoded ? Buffer.from(resp.body, "base64").toString("utf8") : resp.body;
933
+ } catch {
934
+ throw new Error("Response body not available");
935
+ }
936
+ },
937
+ json: async () => {
938
+ const text = await (async () => {
939
+ if (!conn) throw new Error("Response body not available");
940
+ try {
941
+ const resp = await conn.send(
942
+ "Network.getResponseBody",
943
+ { requestId: data.requestId },
944
+ sessionId
945
+ );
946
+ return resp.base64Encoded ? Buffer.from(resp.body, "base64").toString("utf8") : resp.body;
947
+ } catch {
948
+ throw new Error("Response body not available");
949
+ }
950
+ })();
951
+ return JSON.parse(text);
952
+ },
953
+ request: () => request
954
+ };
955
+ }
956
+ function createXBRequest(page, data) {
957
+ return {
958
+ url: () => data.url,
959
+ method: () => data.method,
960
+ headers: () => data.headers,
961
+ postData: () => data.postData,
962
+ resourceType: () => data.resourceType,
963
+ response: async () => {
964
+ if (!page?._networkResponses) return null;
965
+ const resp = page._networkResponses.get(data.requestId);
966
+ if (!resp) return null;
967
+ return createXBResponse(resp, page._connection, page.sessionId, data);
968
+ }
969
+ };
970
+ }
971
+ function createXBRouteFetch(conn, sessionId, params) {
972
+ const request = createXBRequest(null, {
973
+ requestId: params.requestId,
974
+ url: params.request.url,
975
+ method: params.request.method,
976
+ headers: params.request.headers,
977
+ postData: params.request.postData ?? null,
978
+ resourceType: params.resourceType
979
+ });
980
+ return {
981
+ request: () => request,
982
+ abort: async (errorCode) => {
983
+ await conn.send("Fetch.failRequest", {
984
+ requestId: params.requestId,
985
+ errorReason: errorCode || "Failed"
986
+ }, sessionId);
987
+ },
988
+ continue: async (opts) => {
989
+ await conn.send("Fetch.continueRequest", {
990
+ requestId: params.requestId,
991
+ url: opts?.url,
992
+ method: opts?.method,
993
+ headers: opts?.headers ? Object.entries(opts.headers).map(([k, v]) => ({ name: k, value: v })) : void 0,
994
+ postData: opts?.postData ? Buffer.from(opts.postData).toString("base64") : void 0
995
+ }, sessionId);
996
+ },
997
+ fulfill: async (opts) => {
998
+ const bodyStr = typeof opts.body === "string" ? opts.body : opts.body ? opts.body.toString("utf8") : "";
999
+ const bodyBytes = Buffer.from(bodyStr, "utf8");
1000
+ const headers = { ...opts.headers };
1001
+ if (opts.contentType) headers["content-type"] = opts.contentType;
1002
+ headers["access-control-allow-origin"] = "*";
1003
+ await conn.send("Fetch.fulfillRequest", {
1004
+ requestId: params.requestId,
1005
+ responseCode: opts.status ?? 200,
1006
+ responseHeaders: Object.entries(headers).map(([k, v]) => ({ name: k, value: v })),
1007
+ body: bodyBytes.toString("base64")
1008
+ }, sessionId);
1009
+ }
1010
+ };
1011
+ }
1012
+
1013
+ // src/cdp-driver/page.ts
1014
+ var XBPageImpl = class _XBPageImpl {
1015
+ conn;
1016
+ _emitter = new EventEmitter();
1017
+ _subscriptions = [];
1018
+ sessionId;
1019
+ _targetId;
1020
+ _contextImpl;
1021
+ _browserImpl;
1022
+ _closed = false;
1023
+ _url = "about:blank";
1024
+ _title = "";
1025
+ _viewportSize = null;
1026
+ _loadState = {
1027
+ loadFired: true,
1028
+ domContentFired: true,
1029
+ networkIdle: true
1030
+ };
1031
+ mouse;
1032
+ keyboard;
1033
+ // Network tracking for waitForLoadState('networkidle')
1034
+ inflightRequests = /* @__PURE__ */ new Set();
1035
+ networkIdleResolve = null;
1036
+ networkIdleTimer = null;
1037
+ static NETWORK_IDLE_MS = 500;
1038
+ constructor(conn, sessionId, targetId, context, browser) {
1039
+ this.conn = conn;
1040
+ this.sessionId = sessionId;
1041
+ this._targetId = targetId;
1042
+ this._contextImpl = context;
1043
+ this._browserImpl = browser;
1044
+ this.mouse = new XBMouseImpl(conn, sessionId);
1045
+ this.keyboard = new XBKeyboardImpl(conn, sessionId);
1046
+ }
1047
+ _emit(event, ...args) {
1048
+ this._emitter.emit(event, ...args);
1049
+ }
1050
+ /** Internal initialization — must be called after construction */
1051
+ async _init() {
1052
+ await this.conn.send("Page.enable", void 0, this.sessionId);
1053
+ await this.conn.send("Runtime.enable", void 0, this.sessionId);
1054
+ await this.conn.send("Network.enable", void 0, this.sessionId);
1055
+ this.setupPageEvents();
1056
+ this.setupNetworkEvents();
1057
+ this.setupConsoleEvents();
1058
+ await this.conn.send("Runtime.runIfWaitingForDebugger", void 0, this.sessionId).catch(() => {
1059
+ });
1060
+ try {
1061
+ const info = await this.conn.send(
1062
+ "Target.getTargetInfo",
1063
+ { targetId: this._targetId }
1064
+ );
1065
+ this._url = info.url;
1066
+ this._title = info.title;
1067
+ } catch {
1068
+ }
1069
+ }
1070
+ get _connection() {
1071
+ return this.conn;
1072
+ }
1073
+ // ── Navigation ──────────────────────────────────────────────
1074
+ async goto(url, opts = {}) {
1075
+ if (this._closed) throw new Error("Page is closed");
1076
+ const waitUntil = opts.waitUntil ?? "load";
1077
+ const timeout = opts.timeout ?? 3e4;
1078
+ this._loadState = { loadFired: false, domContentFired: false, networkIdle: false };
1079
+ const result = await this.conn.send(
1080
+ "Page.navigate",
1081
+ { url, referrer: opts.referer },
1082
+ this.sessionId
1083
+ );
1084
+ if (result.errorText) {
1085
+ throw new Error(`Navigation failed: ${result.errorText}`);
1086
+ }
1087
+ await this.waitForLoadState(waitUntil, timeout);
1088
+ this._url = url;
1089
+ const statusCode = 200;
1090
+ const finalUrl = url;
1091
+ const headers = {};
1092
+ return {
1093
+ status: () => statusCode,
1094
+ ok: () => statusCode >= 200 && statusCode < 300,
1095
+ url: () => finalUrl,
1096
+ headers: () => headers
1097
+ };
1098
+ }
1099
+ async goBack(opts = {}) {
1100
+ await this.evaluate("() => history.back()");
1101
+ await this.waitForLoadState(opts.waitUntil ?? "load", opts.timeout);
1102
+ }
1103
+ async goForward(opts = {}) {
1104
+ await this.evaluate("() => history.forward()");
1105
+ await this.waitForLoadState(opts.waitUntil ?? "load", opts.timeout);
1106
+ }
1107
+ async reload(opts = {}) {
1108
+ this._loadState = { loadFired: false, domContentFired: false, networkIdle: false };
1109
+ await this.conn.send("Page.reload", void 0, this.sessionId);
1110
+ await this.waitForLoadState(opts.waitUntil ?? "load", opts.timeout);
1111
+ }
1112
+ async waitForLoadState(state = "load", timeout = 3e4) {
1113
+ if (this._closed) throw new Error("Page is closed");
1114
+ const checkState = () => {
1115
+ switch (state) {
1116
+ case "domcontentloaded":
1117
+ return this._loadState.domContentFired;
1118
+ case "load":
1119
+ return this._loadState.loadFired;
1120
+ case "networkidle":
1121
+ return this._loadState.networkIdle;
1122
+ case "commit":
1123
+ return this._loadState.domContentFired;
1124
+ default:
1125
+ return true;
1126
+ }
1127
+ };
1128
+ if (checkState()) return;
1129
+ return new Promise((resolve, reject) => {
1130
+ const timer = setTimeout(() => {
1131
+ reject(new Error(`waitForLoadState('${state}') timeout after ${timeout}ms`));
1132
+ }, timeout);
1133
+ const check = () => {
1134
+ if (checkState()) {
1135
+ clearTimeout(timer);
1136
+ resolve();
1137
+ } else {
1138
+ setTimeout(check, 50);
1139
+ }
1140
+ };
1141
+ check();
1142
+ });
1143
+ }
1144
+ async waitForTimeout(ms) {
1145
+ await new Promise((resolve) => {
1146
+ const timer = setTimeout(resolve, ms);
1147
+ if (typeof timer.unref === "function") timer.unref();
1148
+ });
1149
+ }
1150
+ async waitForSelector(selector, opts = {}) {
1151
+ const state = opts.state ?? "visible";
1152
+ const timeout = opts.timeout ?? 3e4;
1153
+ const deadline = Date.now() + timeout;
1154
+ while (Date.now() < deadline) {
1155
+ const exists = await this.evaluate(
1156
+ `(function() { const el = document.querySelector(${JSON.stringify(selector)}); return !!el; })()`
1157
+ );
1158
+ if (state === "attached" && exists) return;
1159
+ if (state === "detached" && !exists) return;
1160
+ if (state === "visible" && exists) {
1161
+ const visible = await this.evaluate(
1162
+ `(function() { const el = document.querySelector(${JSON.stringify(selector)}); if (!el) return false; const rect = el.getBoundingClientRect(); const style = window.getComputedStyle(el); return rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none'; })()`
1163
+ );
1164
+ if (visible) return;
1165
+ }
1166
+ if (state === "hidden") {
1167
+ if (!exists) return;
1168
+ const visible = await this.evaluate(
1169
+ `(function() { const el = document.querySelector(${JSON.stringify(selector)}); if (!el) return false; const rect = el.getBoundingClientRect(); const style = window.getComputedStyle(el); return rect.width > 0 && rect.height > 0 && style.visibility !== 'hidden' && style.display !== 'none'; })()`
1170
+ );
1171
+ if (!visible) return;
1172
+ }
1173
+ await this.waitForTimeout(100);
1174
+ }
1175
+ throw new Error(`waitForSelector('${selector}', state='${state}') timeout after ${timeout}ms`);
1176
+ }
1177
+ async waitForFunction(fn, opts = {}, ...args) {
1178
+ const timeout = opts.timeout ?? 3e4;
1179
+ const polling = opts.polling ?? 100;
1180
+ const deadline = Date.now() + timeout;
1181
+ const fnBody = typeof fn === "function" ? fn.toString() : fn;
1182
+ let lastError = null;
1183
+ while (Date.now() < deadline) {
1184
+ try {
1185
+ const result = await this.evaluate(
1186
+ `(function(fnStr, ...evalArgs) { const fn = new Function('return ' + fnStr); return fn(...evalArgs); })(${JSON.stringify(fnBody)}${args.length > 0 ? ", " + args.map((a) => JSON.stringify(a)).join(", ") : ""})`
1187
+ );
1188
+ if (result) return result;
1189
+ } catch (err) {
1190
+ lastError = err;
1191
+ }
1192
+ const pollMs = typeof polling === "number" ? polling : 16;
1193
+ await this.waitForTimeout(pollMs);
1194
+ }
1195
+ const detail = lastError ? `
1196
+ Last error: ${lastError.message}` : "";
1197
+ throw new Error(`waitForFunction timeout after ${timeout}ms${detail}`);
1198
+ }
1199
+ url() {
1200
+ return this._url;
1201
+ }
1202
+ async title() {
1203
+ this._title = await this.evaluate("document.title");
1204
+ return this._title;
1205
+ }
1206
+ async content() {
1207
+ return this.evaluate("document.documentElement.outerHTML");
1208
+ }
1209
+ // ── Evaluation ──────────────────────────────────────────────
1210
+ async evaluate(fn, ...args) {
1211
+ if (this._closed) throw new Error("Page is closed");
1212
+ let expression;
1213
+ if (typeof fn === "string") {
1214
+ expression = fn;
1215
+ } else {
1216
+ const argStr = args.length > 0 ? `...${JSON.stringify(args)}` : "";
1217
+ expression = `(()=>{const __fn=(${fn.toString()});return __fn(${argStr});})()`;
1218
+ }
1219
+ const result = await this.conn.send("Runtime.evaluate", {
1220
+ expression,
1221
+ returnByValue: true,
1222
+ awaitPromise: true
1223
+ }, this.sessionId);
1224
+ if (result.exceptionDetails) {
1225
+ const detail = result.exceptionDetails.exception?.description ?? result.exceptionDetails.exception?.value ?? result.exceptionDetails.text;
1226
+ throw new Error(`${detail}`);
1227
+ }
1228
+ return result.result?.value;
1229
+ }
1230
+ async $eval(selector, fn, ...args) {
1231
+ const fnBody = typeof fn === "function" ? fn.toString() : fn;
1232
+ return this.evaluate(
1233
+ `(function(sel, fnStr, ...evalArgs) {
1234
+ const el = document.querySelector(sel);
1235
+ if (!el) throw new Error('No element found for selector: ' + sel);
1236
+ const fn = new Function('return ' + fnStr)();
1237
+ return fn(el, ...evalArgs);
1238
+ })(${JSON.stringify(selector)}, ${JSON.stringify(fnBody)}${args.length > 0 ? ", " + args.map((a) => JSON.stringify(a)).join(", ") : ""})`
1239
+ );
1240
+ }
1241
+ async $$eval(selector, fn, ...args) {
1242
+ const fnBody = typeof fn === "function" ? fn.toString() : fn;
1243
+ return this.evaluate(
1244
+ `(function(sel, fnStr, ...evalArgs) {
1245
+ const els = Array.from(document.querySelectorAll(sel));
1246
+ const fn = new Function('return ' + fnStr)();
1247
+ return fn(els, ...evalArgs);
1248
+ })(${JSON.stringify(selector)}, ${JSON.stringify(fnBody)}${args.length > 0 ? ", " + args.map((a) => JSON.stringify(a)).join(", ") : ""})`
1249
+ );
1250
+ }
1251
+ // ── Locator ─────────────────────────────────────────────────
1252
+ locator(selector) {
1253
+ return new XBLocatorImpl(this, selector);
1254
+ }
1255
+ getByText(text, opts) {
1256
+ const escaped = text.replace(/'/g, "\\'");
1257
+ if (opts?.exact) {
1258
+ return this.locator(`xpath=//*[normalize-space(text())='${escaped}']`);
1259
+ }
1260
+ return this.locator(`xpath=//*[contains(text(),'${escaped}')]`);
1261
+ }
1262
+ getByRole(role, opts) {
1263
+ let sel = `[role="${role}"]`;
1264
+ if (opts?.name) {
1265
+ sel += opts.exact ? `[aria-label="${opts.name}"]` : `[aria-label*="${opts.name}"]`;
1266
+ }
1267
+ return this.locator(sel);
1268
+ }
1269
+ getByLabel(label, opts) {
1270
+ const escaped = label.replace(/'/g, "\\'");
1271
+ const sel = opts?.exact ? `xpath=//*[@aria-label='${escaped}' or @id=//label[normalize-space(text())='${escaped}']/@for]` : `xpath=//*[contains(@aria-label,'${escaped}') or @id=//label[contains(text(),'${escaped}')]/@for]`;
1272
+ return this.locator(sel);
1273
+ }
1274
+ getByPlaceholder(text, opts) {
1275
+ return this.locator(
1276
+ opts?.exact ? `[placeholder="${text}"]` : `[placeholder*="${text}"]`
1277
+ );
1278
+ }
1279
+ getByTestId(id) {
1280
+ return this.locator(`[data-testid="${id}"]`);
1281
+ }
1282
+ getByAltText(text, opts) {
1283
+ return this.locator(opts?.exact ? `[alt="${text}"]` : `[alt*="${text}"]`);
1284
+ }
1285
+ getByTitle(title, opts) {
1286
+ return this.locator(
1287
+ opts?.exact ? `[title="${title}"]` : `[title*="${title}"]`
1288
+ );
1289
+ }
1290
+ // ── Interaction shortcuts ───────────────────────────────────
1291
+ async click(selector, opts = {}) {
1292
+ await this.locator(selector).click(opts);
1293
+ }
1294
+ async dblclick(selector, opts = {}) {
1295
+ await this.locator(selector).click({ ...opts, clickCount: 2 });
1296
+ }
1297
+ async fill(selector, value, opts = {}) {
1298
+ await this.locator(selector).fill(value, opts);
1299
+ }
1300
+ async press(selector, key, opts) {
1301
+ await this.locator(selector).press(key, opts);
1302
+ }
1303
+ async hover(selector, opts) {
1304
+ await this.locator(selector).hover(opts);
1305
+ }
1306
+ async type(selector, text, opts = {}) {
1307
+ await this.locator(selector).type(text, opts);
1308
+ }
1309
+ async check(selector, opts) {
1310
+ await this.locator(selector).check(opts);
1311
+ }
1312
+ async uncheck(selector, opts) {
1313
+ await this.locator(selector).uncheck(opts);
1314
+ }
1315
+ async selectOption(selector, value) {
1316
+ return this.locator(selector).selectOption(value);
1317
+ }
1318
+ // ── Convenience selectors ───────────────────────────────────
1319
+ async textContent(selector) {
1320
+ return this.evaluate(
1321
+ `(function() { const el = document.querySelector(${JSON.stringify(selector)}); return el?.textContent ?? null; })()`
1322
+ );
1323
+ }
1324
+ async innerText(selector) {
1325
+ return this.evaluate(
1326
+ `(function() { const el = document.querySelector(${JSON.stringify(selector)}); if (!el) throw new Error('Element not found'); return el.innerText; })()`
1327
+ );
1328
+ }
1329
+ async innerHTML(selector) {
1330
+ return this.evaluate(
1331
+ `(function() { const el = document.querySelector(${JSON.stringify(selector)}); if (!el) throw new Error('Element not found'); return el.innerHTML; })()`
1332
+ );
1333
+ }
1334
+ async getAttribute(selector, name) {
1335
+ return this.evaluate(
1336
+ `(function() { const el = document.querySelector(${JSON.stringify(selector)}); return el?.getAttribute(${JSON.stringify(name)}) ?? null; })()`
1337
+ );
1338
+ }
1339
+ // ── Query ───────────────────────────────────────────────────
1340
+ async $(selector) {
1341
+ const nodeId = await this.querySelector(selector);
1342
+ if (!nodeId) return null;
1343
+ return new XBElementHandleImpl(this, nodeId);
1344
+ }
1345
+ async $$(selector) {
1346
+ const nodeIds = await this.querySelectorAll(selector);
1347
+ return nodeIds.map((id) => new XBElementHandleImpl(this, id));
1348
+ }
1349
+ // ── Screen ──────────────────────────────────────────────────
1350
+ async screenshot(opts = {}) {
1351
+ const params = {
1352
+ format: opts.type ?? "png"
1353
+ };
1354
+ if (opts.quality !== void 0 && (opts.type === "jpeg" || !opts.type && opts.quality)) {
1355
+ params.quality = opts.quality;
1356
+ }
1357
+ if (opts.fullPage) {
1358
+ params.captureBeyondViewport = true;
1359
+ }
1360
+ if (opts.clip) {
1361
+ const clip = {
1362
+ x: Math.round(opts.clip.x),
1363
+ y: Math.round(opts.clip.y),
1364
+ width: Math.round(Math.max(1, opts.clip.width)),
1365
+ height: Math.round(Math.max(1, opts.clip.height)),
1366
+ scale: 1
1367
+ };
1368
+ params.clip = clip;
1369
+ }
1370
+ if (opts.omitBackground) {
1371
+ params.omitBackground = true;
1372
+ }
1373
+ const result = await this.conn.send(
1374
+ "Page.captureScreenshot",
1375
+ params,
1376
+ this.sessionId
1377
+ );
1378
+ return Buffer.from(result.data, "base64");
1379
+ }
1380
+ async pdf(opts = {}) {
1381
+ const params = {};
1382
+ if (opts.landscape !== void 0) params.landscape = opts.landscape;
1383
+ if (opts.printBackground !== void 0) params.printBackground = opts.printBackground;
1384
+ if (opts.scale !== void 0) params.scale = opts.scale;
1385
+ if (opts.format) params.paperFormat = opts.format;
1386
+ if (opts.preferCSSPageSize !== void 0) params.preferCSSPageSize = opts.preferCSSPageSize;
1387
+ if (opts.margin) {
1388
+ if (opts.margin.top) params.marginTop = parseFloat(opts.margin.top);
1389
+ if (opts.margin.bottom) params.marginBottom = parseFloat(opts.margin.bottom);
1390
+ if (opts.margin.left) params.marginLeft = parseFloat(opts.margin.left);
1391
+ if (opts.margin.right) params.marginRight = parseFloat(opts.margin.right);
1392
+ }
1393
+ const result = await this.conn.send("Page.printToPDF", params, this.sessionId);
1394
+ return Buffer.from(result.data, "base64");
1395
+ }
1396
+ viewportSize() {
1397
+ return this._viewportSize ?? null;
1398
+ }
1399
+ async setViewportSize(size) {
1400
+ await this.conn.send(
1401
+ "Emulation.setDeviceMetricsOverride",
1402
+ {
1403
+ width: size.width,
1404
+ height: size.height,
1405
+ deviceScaleFactor: 1,
1406
+ mobile: false
1407
+ },
1408
+ this.sessionId
1409
+ );
1410
+ this._viewportSize = size;
1411
+ }
1412
+ // ── Scripts ─────────────────────────────────────────────────
1413
+ async addInitScript(script) {
1414
+ await this.conn.send(
1415
+ "Page.addScriptToEvaluateOnNewDocument",
1416
+ { source: script },
1417
+ this.sessionId
1418
+ );
1419
+ }
1420
+ /** Internal: set user agent */
1421
+ async _setUserAgent(userAgent) {
1422
+ await this.conn.send(
1423
+ "Network.setUserAgentOverride",
1424
+ { userAgent },
1425
+ this.sessionId
1426
+ );
1427
+ }
1428
+ /** Internal: set extra HTTP headers */
1429
+ async _setExtraHTTPHeaders(headers) {
1430
+ await this.setExtraHTTPHeaders(headers);
1431
+ }
1432
+ async bringToFront() {
1433
+ await this.conn.send("Page.bringToFront", void 0, this.sessionId);
1434
+ }
1435
+ async setExtraHTTPHeaders(headers) {
1436
+ await this.conn.send("Network.setExtraHTTPHeaders", { headers }, this.sessionId);
1437
+ }
1438
+ // ── Events ──────────────────────────────────────────────────
1439
+ on(event, handler) {
1440
+ this._emitter.on(event, handler);
1441
+ }
1442
+ off(event, handler) {
1443
+ this._emitter.off(event, handler);
1444
+ }
1445
+ // ── Lifecycle ───────────────────────────────────────────────
1446
+ async close() {
1447
+ if (this._closed) return;
1448
+ this._closed = true;
1449
+ if (this.networkIdleTimer) {
1450
+ clearTimeout(this.networkIdleTimer);
1451
+ this.networkIdleTimer = null;
1452
+ }
1453
+ for (const unsub of this._subscriptions) {
1454
+ unsub();
1455
+ }
1456
+ this._subscriptions = [];
1457
+ await this._browserImpl._closeTarget(this._targetId).catch(() => {
1458
+ });
1459
+ await this._browserImpl._detachFromTarget(this.sessionId).catch(() => {
1460
+ });
1461
+ this._emitter.emit("close");
1462
+ }
1463
+ isClosed() {
1464
+ return this._closed;
1465
+ }
1466
+ context() {
1467
+ return this._contextImpl;
1468
+ }
1469
+ browser() {
1470
+ return this._browserImpl;
1471
+ }
1472
+ mainFrame() {
1473
+ return {
1474
+ url: () => this._url,
1475
+ name: () => "",
1476
+ isDetached: () => this._closed,
1477
+ page: () => this,
1478
+ evaluate: (fn, ...args) => this.evaluate(fn, ...args),
1479
+ $: (sel) => this.$(sel),
1480
+ $$: (sel) => this.$$(sel)
1481
+ };
1482
+ }
1483
+ frames() {
1484
+ return [this.mainFrame()];
1485
+ }
1486
+ // ── CDP helpers exposed for locator/element ─────────────────
1487
+ /** Query a single element, returns CDP nodeId or 0 if not found */
1488
+ async querySelector(selector) {
1489
+ const doc = await this.conn.send(
1490
+ "DOM.getDocument",
1491
+ { depth: 0 },
1492
+ this.sessionId
1493
+ );
1494
+ const result = await this.conn.send(
1495
+ "DOM.querySelector",
1496
+ { nodeId: doc.root.nodeId, selector },
1497
+ this.sessionId
1498
+ );
1499
+ return result.nodeId;
1500
+ }
1501
+ /** Query all matching elements, returns array of CDP nodeIds */
1502
+ async querySelectorAll(selector) {
1503
+ const doc = await this.conn.send(
1504
+ "DOM.getDocument",
1505
+ { depth: 0 },
1506
+ this.sessionId
1507
+ );
1508
+ const result = await this.conn.send(
1509
+ "DOM.querySelectorAll",
1510
+ { nodeId: doc.root.nodeId, selector },
1511
+ this.sessionId
1512
+ );
1513
+ return result.nodeIds ?? [];
1514
+ }
1515
+ /** Resolve a CDP nodeId to a RemoteObject for evaluate */
1516
+ async resolveNode(nodeId) {
1517
+ const result = await this.conn.send(
1518
+ "DOM.resolveNode",
1519
+ { nodeId },
1520
+ this.sessionId
1521
+ );
1522
+ return result.object.objectId;
1523
+ }
1524
+ /** Get the box model for a nodeId */
1525
+ async getBoxModel(nodeId) {
1526
+ try {
1527
+ const result = await this.conn.send("DOM.getBoxModel", { nodeId }, this.sessionId);
1528
+ const c = result.model?.content;
1529
+ if (!c || c.length < 8) return null;
1530
+ const x1 = c[0];
1531
+ const y1 = c[1];
1532
+ const x2 = c[4];
1533
+ const y2 = c[5];
1534
+ return {
1535
+ x: Math.min(x1, x2),
1536
+ y: Math.min(y1, y2),
1537
+ width: Math.abs(x2 - x1),
1538
+ height: Math.abs(y2 - y1)
1539
+ };
1540
+ } catch {
1541
+ return null;
1542
+ }
1543
+ }
1544
+ /** Call a function on a RemoteObject */
1545
+ async callFunctionOn(objectId, functionDeclaration, args = []) {
1546
+ const result = await this.conn.send("Runtime.callFunctionOn", {
1547
+ objectId,
1548
+ functionDeclaration,
1549
+ arguments: args.map((a) => ({ value: a })),
1550
+ returnByValue: true
1551
+ }, this.sessionId);
1552
+ if (result.exceptionDetails) {
1553
+ throw new Error(`CallFunctionOn error: ${result.exceptionDetails.text}`);
1554
+ }
1555
+ return result.result?.value;
1556
+ }
1557
+ /** Send a CDP command scoped to this page's session */
1558
+ async _cdpSend(method, params) {
1559
+ return this.conn.send(method, params, this.sessionId);
1560
+ }
1561
+ /** Subscribe to a CDP event on this page's session. Returns unsubscribe function. */
1562
+ _subscribe(event, handler) {
1563
+ return this.conn.subscribe(event, this.sessionId, handler);
1564
+ }
1565
+ // ── Private: Event Setup ────────────────────────────────────
1566
+ setupPageEvents() {
1567
+ this._subscriptions.push(
1568
+ this.conn.subscribe("Page.frameNavigated", this.sessionId, (params) => {
1569
+ const p = params;
1570
+ if (p.frame) {
1571
+ this._url = p.frame.url;
1572
+ }
1573
+ this._emit("framenavigated", this.mainFrame());
1574
+ })
1575
+ );
1576
+ this._subscriptions.push(
1577
+ this.conn.subscribe("Page.loadEventFired", this.sessionId, () => {
1578
+ this._loadState.loadFired = true;
1579
+ })
1580
+ );
1581
+ this._subscriptions.push(
1582
+ this.conn.subscribe("Page.domContentEventFired", this.sessionId, () => {
1583
+ this._loadState.domContentFired = true;
1584
+ })
1585
+ );
1586
+ this._subscriptions.push(
1587
+ this.conn.subscribe("Page.javascriptDialogOpening", this.sessionId, (params) => {
1588
+ const p = params;
1589
+ const dialog = {
1590
+ type: p.type,
1591
+ message: () => p.message,
1592
+ defaultValue: () => p.defaultValue,
1593
+ accept: async (text) => {
1594
+ await this.conn.send("Page.handleJavaScriptDialog", {
1595
+ accept: true,
1596
+ promptText: text
1597
+ }, this.sessionId);
1598
+ },
1599
+ dismiss: async () => {
1600
+ await this.conn.send("Page.handleJavaScriptDialog", {
1601
+ accept: false
1602
+ }, this.sessionId);
1603
+ }
1604
+ };
1605
+ this._emit("dialog", dialog);
1606
+ })
1607
+ );
1608
+ }
1609
+ setupNetworkEvents() {
1610
+ this._subscriptions.push(
1611
+ this.conn.subscribe("Network.requestWillBeSent", this.sessionId, (params) => {
1612
+ const p = params;
1613
+ this.inflightRequests.add(p.requestId);
1614
+ this._storeNetworkRequest(p.requestId, {
1615
+ url: p.request.url,
1616
+ method: p.request.method,
1617
+ headers: p.request.headers,
1618
+ postData: p.request.postData ?? null,
1619
+ resourceType: p.type
1620
+ });
1621
+ this._emit("request", p);
1622
+ this.checkNetworkIdle();
1623
+ })
1624
+ );
1625
+ this._subscriptions.push(
1626
+ this.conn.subscribe("Network.responseReceived", this.sessionId, (params) => {
1627
+ const p = params;
1628
+ this._storeNetworkResponse(p.requestId, {
1629
+ status: p.response.status,
1630
+ url: p.response.url,
1631
+ headers: p.response.headers
1632
+ });
1633
+ this._emit("response", p);
1634
+ })
1635
+ );
1636
+ this._subscriptions.push(
1637
+ this.conn.subscribe("Network.loadingFinished", this.sessionId, (params) => {
1638
+ const p = params;
1639
+ this.inflightRequests.delete(p.requestId);
1640
+ this._emit("requestfinished", p);
1641
+ this.checkNetworkIdle();
1642
+ })
1643
+ );
1644
+ this._subscriptions.push(
1645
+ this.conn.subscribe("Network.loadingFailed", this.sessionId, (params) => {
1646
+ const p = params;
1647
+ this.inflightRequests.delete(p.requestId);
1648
+ this.checkNetworkIdle();
1649
+ })
1650
+ );
1651
+ }
1652
+ setupConsoleEvents() {
1653
+ this._subscriptions.push(
1654
+ this.conn.subscribe("Runtime.consoleAPICalled", this.sessionId, (params) => {
1655
+ const p = params;
1656
+ const text = p.args.map((a) => {
1657
+ if (a.value !== void 0) return String(a.value);
1658
+ return a.description ?? "";
1659
+ }).join(" ");
1660
+ const location = p.stackTrace?.callFrames?.[0] ? {
1661
+ url: p.stackTrace.callFrames[0].url,
1662
+ lineNumber: p.stackTrace.callFrames[0].lineNumber,
1663
+ columnNumber: p.stackTrace.callFrames[0].columnNumber
1664
+ } : { url: "", lineNumber: 0, columnNumber: 0 };
1665
+ const msg = {
1666
+ type: () => p.type,
1667
+ text: () => text,
1668
+ location: () => location
1669
+ };
1670
+ this._emit("console", msg);
1671
+ })
1672
+ );
1673
+ }
1674
+ checkNetworkIdle() {
1675
+ if (this.inflightRequests.size === 0) {
1676
+ if (this.networkIdleTimer) clearTimeout(this.networkIdleTimer);
1677
+ this.networkIdleTimer = setTimeout(() => {
1678
+ if (this.inflightRequests.size === 0) {
1679
+ this._loadState.networkIdle = true;
1680
+ if (this.networkIdleResolve) {
1681
+ this.networkIdleResolve();
1682
+ this.networkIdleResolve = null;
1683
+ }
1684
+ }
1685
+ }, _XBPageImpl.NETWORK_IDLE_MS);
1686
+ } else {
1687
+ if (this.networkIdleTimer) {
1688
+ clearTimeout(this.networkIdleTimer);
1689
+ this.networkIdleTimer = null;
1690
+ }
1691
+ }
1692
+ }
1693
+ // ── Network Data Store (for waitForResponse/waitForRequest) ──
1694
+ _networkResponses = /* @__PURE__ */ new Map();
1695
+ _networkRequests = /* @__PURE__ */ new Map();
1696
+ _routeHandlers = [];
1697
+ _interceptionEnabled = false;
1698
+ /** Store network data — called by browser.ts installNetworkCapture or internal event handlers */
1699
+ _storeNetworkRequest(requestId, data) {
1700
+ this._networkRequests.set(requestId, { requestId, ...data });
1701
+ }
1702
+ _storeNetworkResponse(requestId, data) {
1703
+ this._networkResponses.set(requestId, { requestId, ...data });
1704
+ }
1705
+ // ── waitForResponse ─────────────────────────────────────────
1706
+ async waitForResponse(urlOrPredicate, opts = {}) {
1707
+ const timeout = opts.timeout ?? 3e4;
1708
+ const predicate = createResponsePredicate(urlOrPredicate);
1709
+ for (const [, data] of this._networkResponses) {
1710
+ const response = createXBResponse(data, this.conn, this.sessionId);
1711
+ if (predicate(response)) return response;
1712
+ }
1713
+ return new Promise((resolve, reject) => {
1714
+ const timer = setTimeout(() => {
1715
+ this._emitter.removeListener("response", handler);
1716
+ reject(new Error(`waitForResponse timed out after ${timeout}ms`));
1717
+ }, timeout);
1718
+ const handler = (params) => {
1719
+ const p = params;
1720
+ const data = {
1721
+ requestId: p.requestId,
1722
+ status: p.response.status,
1723
+ url: p.response.url,
1724
+ headers: p.response.headers
1725
+ };
1726
+ const response = createXBResponse(data, this.conn, this.sessionId);
1727
+ if (predicate(response)) {
1728
+ clearTimeout(timer);
1729
+ this._emitter.removeListener("response", handler);
1730
+ resolve(response);
1731
+ }
1732
+ };
1733
+ this._emitter.on("response", handler);
1734
+ });
1735
+ }
1736
+ // ── waitForRequest ──────────────────────────────────────────
1737
+ async waitForRequest(urlOrPredicate, opts = {}) {
1738
+ const timeout = opts.timeout ?? 3e4;
1739
+ const predicate = createRequestPredicate(urlOrPredicate);
1740
+ for (const [, data] of this._networkRequests) {
1741
+ const request = createXBRequest(this, data);
1742
+ if (predicate(request)) return request;
1743
+ }
1744
+ return new Promise((resolve, reject) => {
1745
+ const timer = setTimeout(() => {
1746
+ this._emitter.removeListener("request", handler);
1747
+ reject(new Error(`waitForRequest timed out after ${timeout}ms`));
1748
+ }, timeout);
1749
+ const handler = (params) => {
1750
+ const p = params;
1751
+ const data = {
1752
+ requestId: p.requestId,
1753
+ url: p.request.url,
1754
+ method: p.request.method,
1755
+ headers: p.request.headers,
1756
+ postData: p.request.postData ?? null,
1757
+ resourceType: p.type
1758
+ };
1759
+ const request = createXBRequest(this, data);
1760
+ if (predicate(request)) {
1761
+ clearTimeout(timer);
1762
+ this._emitter.removeListener("request", handler);
1763
+ resolve(request);
1764
+ }
1765
+ };
1766
+ this._emitter.on("request", handler);
1767
+ });
1768
+ }
1769
+ // ── waitForURL ──────────────────────────────────────────────
1770
+ async waitForURL(url, opts = {}) {
1771
+ const timeout = opts.timeout ?? 3e4;
1772
+ const checkFn = typeof url === "function" ? url : typeof url === "string" ? (current) => matchGlob(url, current) : (current) => url.test(current);
1773
+ if (checkFn(this._url)) return;
1774
+ return new Promise((resolve, reject) => {
1775
+ const timer = setTimeout(() => {
1776
+ this._emitter.removeListener("framenavigated", handler);
1777
+ reject(new Error(`waitForURL timed out after ${timeout}ms`));
1778
+ }, timeout);
1779
+ const handler = () => {
1780
+ if (checkFn(this._url)) {
1781
+ clearTimeout(timer);
1782
+ this._emitter.removeListener("framenavigated", handler);
1783
+ resolve();
1784
+ }
1785
+ };
1786
+ this._emitter.on("framenavigated", handler);
1787
+ });
1788
+ }
1789
+ // ── route / unroute ─────────────────────────────────────────
1790
+ async route(url, handler) {
1791
+ const regex = typeof url === "string" ? globToRegex(url) : url;
1792
+ this._routeHandlers.push({ pattern: String(url), regex, handler });
1793
+ if (!this._interceptionEnabled) {
1794
+ this._interceptionEnabled = true;
1795
+ await this.conn.send("Fetch.enable", {
1796
+ patterns: [{ urlPattern: "*", requestStage: "Request" }]
1797
+ }, this.sessionId);
1798
+ this._subscriptions.push(
1799
+ this.conn.subscribe("Fetch.requestPaused", this.sessionId, (params) => {
1800
+ this._handleRequestPaused(params);
1801
+ })
1802
+ );
1803
+ }
1804
+ }
1805
+ async unroute(url, handler) {
1806
+ const regex = typeof url === "string" ? globToRegex(url) : url;
1807
+ this._routeHandlers = this._routeHandlers.filter(
1808
+ (h) => !(regex.source === h.regex.source && (!handler || h.handler === handler))
1809
+ );
1810
+ if (this._routeHandlers.length === 0 && this._interceptionEnabled) {
1811
+ this._interceptionEnabled = false;
1812
+ await this.conn.send("Fetch.disable", void 0, this.sessionId).catch(() => {
1813
+ });
1814
+ }
1815
+ }
1816
+ async _handleRequestPaused(params) {
1817
+ const requestUrl = params.request.url;
1818
+ for (const { regex, handler } of this._routeHandlers) {
1819
+ if (regex.test(requestUrl)) {
1820
+ const route = createXBRouteFetch(this.conn, this.sessionId, params);
1821
+ try {
1822
+ await handler(route);
1823
+ } catch {
1824
+ await this.conn.send("Fetch.continueRequest", {
1825
+ requestId: params.requestId
1826
+ }, this.sessionId).catch(() => {
1827
+ });
1828
+ }
1829
+ return;
1830
+ }
1831
+ }
1832
+ await this.conn.send("Fetch.continueRequest", {
1833
+ requestId: params.requestId
1834
+ }, this.sessionId).catch(() => {
1835
+ });
1836
+ }
1837
+ // ── setInputFiles ───────────────────────────────────────────
1838
+ async setInputFiles(selector, files) {
1839
+ const fileArr = Array.isArray(files) ? files : [files];
1840
+ const fileList = fileArr.map((f) => ({
1841
+ name: f.name,
1842
+ type: f.mimeType,
1843
+ dataBase64: f.buffer.toString("base64")
1844
+ }));
1845
+ await this.evaluate(`
1846
+ (function() {
1847
+ var selector = ${JSON.stringify(selector)};
1848
+ var input = document.querySelector(selector);
1849
+ if (!input) throw new Error('Element not found: ' + selector);
1850
+
1851
+ var fileList = ${JSON.stringify(fileList)};
1852
+ var dt = new DataTransfer();
1853
+
1854
+ fileList.forEach(function(f) {
1855
+ var binary = atob(f.dataBase64);
1856
+ var bytes = new Uint8Array(binary.length);
1857
+ for (var i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
1858
+ var blob = new Blob([bytes], { type: f.type });
1859
+ var file = new File([blob], f.name, { type: f.type });
1860
+ dt.items.add(file);
1861
+ });
1862
+
1863
+ input.files = dt.files;
1864
+ input.dispatchEvent(new Event('input', { bubbles: true }));
1865
+ input.dispatchEvent(new Event('change', { bubbles: true }));
1866
+ })()
1867
+ `);
1868
+ }
1869
+ // ── dragAndDrop ─────────────────────────────────────────────
1870
+ async dragAndDrop(source, target) {
1871
+ const sourceRect = await this.evaluate(`
1872
+ (function() {
1873
+ const el = document.querySelector(${JSON.stringify(source)});
1874
+ if (!el) throw new Error('Source not found: ${source}');
1875
+ el.scrollIntoView({ behavior: 'instant', block: 'center' });
1876
+ const r = el.getBoundingClientRect();
1877
+ return { x: r.x, y: r.y, width: r.width, height: r.height };
1878
+ })()
1879
+ `);
1880
+ const targetRect = await this.evaluate(`
1881
+ (function() {
1882
+ const el = document.querySelector(${JSON.stringify(target)});
1883
+ if (!el) throw new Error('Target not found: ${target}');
1884
+ el.scrollIntoView({ behavior: 'instant', block: 'center' });
1885
+ const r = el.getBoundingClientRect();
1886
+ return { x: r.x, y: r.y, width: r.width, height: r.height };
1887
+ })()
1888
+ `);
1889
+ const sx = sourceRect.x + sourceRect.width / 2;
1890
+ const sy = sourceRect.y + sourceRect.height / 2;
1891
+ const tx = targetRect.x + targetRect.width / 2;
1892
+ const ty = targetRect.y + targetRect.height / 2;
1893
+ try {
1894
+ await this.conn.send("Input.dispatchDragEvent", {
1895
+ type: "dragStart",
1896
+ x: sx,
1897
+ y: sy,
1898
+ data: { items: [], dragOperations: ["copy", "move", "link"] }
1899
+ }, this.sessionId);
1900
+ await this.conn.send("Input.dispatchDragEvent", {
1901
+ type: "dragOver",
1902
+ x: tx,
1903
+ y: ty,
1904
+ data: { items: [], dragOperations: ["copy", "move", "link"] }
1905
+ }, this.sessionId);
1906
+ await this.conn.send("Input.dispatchDragEvent", {
1907
+ type: "drop",
1908
+ x: tx,
1909
+ y: ty,
1910
+ data: { items: [], dragOperations: ["copy", "move", "link"] }
1911
+ }, this.sessionId);
1912
+ await this.conn.send("Input.dispatchDragEvent", {
1913
+ type: "dragCancel",
1914
+ x: sx,
1915
+ y: sy,
1916
+ data: { items: [], dragOperations: ["copy", "move", "link"] }
1917
+ }, this.sessionId);
1918
+ } catch {
1919
+ await this.mouse.move(sx, sy);
1920
+ await this.mouse.down();
1921
+ await this.mouse.move(tx, ty, { steps: 10 });
1922
+ await this.mouse.up();
1923
+ }
1924
+ }
1925
+ // ── setOfflineMode ──────────────────────────────────────────
1926
+ async setOfflineMode(offline) {
1927
+ await this.conn.send("Network.emulateNetworkConditions", {
1928
+ offline,
1929
+ latency: 0,
1930
+ downloadThroughput: offline ? 0 : -1,
1931
+ uploadThroughput: offline ? 0 : -1
1932
+ }, this.sessionId);
1933
+ }
1934
+ };
1935
+
1936
+ // src/cdp-driver/cdp-session.ts
1937
+ var XBCDPSessionImpl = class {
1938
+ conn;
1939
+ sessionId;
1940
+ constructor(conn, sessionId) {
1941
+ this.conn = conn;
1942
+ this.sessionId = sessionId;
1943
+ }
1944
+ async send(method, params) {
1945
+ return this.conn.send(method, params, this.sessionId);
1946
+ }
1947
+ on(event, handler) {
1948
+ this.conn.on(event, (params, sid) => {
1949
+ if (sid === this.sessionId || !this.sessionId && !sid) {
1950
+ handler(params);
1951
+ }
1952
+ });
1953
+ }
1954
+ off(event, handler) {
1955
+ this.conn.off(event, handler);
1956
+ }
1957
+ async detach() {
1958
+ if (!this.sessionId) return;
1959
+ }
1960
+ };
1961
+
1962
+ // src/cdp-driver/context.ts
1963
+ var XBContextImpl = class {
1964
+ conn;
1965
+ _emitter = new EventEmitter2();
1966
+ _browser;
1967
+ contextId;
1968
+ _pages = [];
1969
+ closed = false;
1970
+ options;
1971
+ targetAttachedHandler = null;
1972
+ _initScripts = [];
1973
+ constructor(conn, contextId, browser, opts) {
1974
+ this.conn = conn;
1975
+ this.contextId = contextId;
1976
+ this._browser = browser;
1977
+ this.options = opts;
1978
+ this.setupAutoAttach();
1979
+ }
1980
+ async newPage() {
1981
+ if (this.closed) throw new Error("Context is closed");
1982
+ const { targetId } = await this._browser._createTarget(this.contextId);
1983
+ const sessionId = await this._browser._attachToTarget(targetId);
1984
+ const page = new XBPageImpl(this.conn, sessionId, targetId, this, this._browser);
1985
+ await page._init();
1986
+ this._pages.push(page);
1987
+ if (this.options.viewport) {
1988
+ await page.setViewportSize(this.options.viewport).catch(() => {
1989
+ });
1990
+ }
1991
+ if (this.options.userAgent) {
1992
+ await page._setUserAgent(this.options.userAgent);
1993
+ }
1994
+ if (this.options.extraHTTPHeaders) {
1995
+ await page._setExtraHTTPHeaders(this.options.extraHTTPHeaders);
1996
+ }
1997
+ for (const script of this._initScripts) {
1998
+ await page.addInitScript(script);
1999
+ }
2000
+ return page;
2001
+ }
2002
+ pages() {
2003
+ return [...this._pages];
2004
+ }
2005
+ browser() {
2006
+ return this._browser;
2007
+ }
2008
+ async close() {
2009
+ if (this.closed) return;
2010
+ this.closed = true;
2011
+ for (const page of this._pages) {
2012
+ await page.close().catch(() => {
2013
+ });
2014
+ }
2015
+ this._pages = [];
2016
+ if (this.targetAttachedHandler) {
2017
+ this.conn.off("Target.attachedToTarget", this.targetAttachedHandler);
2018
+ this.targetAttachedHandler = null;
2019
+ }
2020
+ if (this.contextId && this.contextId !== "default") {
2021
+ await this.conn.send("Target.disposeBrowserContext", {
2022
+ browserContextId: this.contextId
2023
+ }).catch(() => {
2024
+ });
2025
+ }
2026
+ this._browser._removeContext(this.contextId);
2027
+ }
2028
+ async newCDPSession(_page) {
2029
+ return new XBCDPSessionImpl(this.conn);
2030
+ }
2031
+ async addInitScript(script) {
2032
+ this._initScripts.push(script);
2033
+ for (const page of this._pages) {
2034
+ await page.addInitScript(script);
2035
+ }
2036
+ }
2037
+ // ── Cookies ─────────────────────────────────────────────────
2038
+ async cookies(urls) {
2039
+ const urlList = typeof urls === "string" ? [urls] : urls;
2040
+ const result = await this.conn.send("Network.getCookies", urlList ? { urls: urlList } : void 0);
2041
+ return result.cookies;
2042
+ }
2043
+ async addCookies(cookies) {
2044
+ const cdpCookies = cookies.map((c) => ({
2045
+ name: c.name,
2046
+ value: c.value,
2047
+ domain: c.domain,
2048
+ path: c.path || "/",
2049
+ expires: c.expires,
2050
+ httpOnly: c.httpOnly,
2051
+ secure: c.secure,
2052
+ sameSite: c.sameSite
2053
+ }));
2054
+ await this.conn.send("Network.setCookies", { cookies: cdpCookies });
2055
+ }
2056
+ async clearCookies() {
2057
+ await this.conn.send("Network.clearBrowserCookies");
2058
+ }
2059
+ on(event, handler) {
2060
+ this._emitter.on(event, handler);
2061
+ }
2062
+ off(event, handler) {
2063
+ this._emitter.off(event, handler);
2064
+ }
2065
+ // ── Private ─────────────────────────────────────────────────
2066
+ setupAutoAttach() {
2067
+ this.targetAttachedHandler = (paramsRaw) => {
2068
+ const params = paramsRaw;
2069
+ if (this.contextId !== "default" && params.targetInfo.browserContextId !== this.contextId) return;
2070
+ if (params.targetInfo.type !== "page") return;
2071
+ const exists = this._pages.some(
2072
+ (p) => p._targetId === params.targetInfo.targetId
2073
+ );
2074
+ if (exists) return;
2075
+ const page = new XBPageImpl(
2076
+ this.conn,
2077
+ params.sessionId,
2078
+ params.targetInfo.targetId,
2079
+ this,
2080
+ this._browser
2081
+ );
2082
+ this.conn.send("Runtime.runIfWaitingForDebugger", void 0, params.sessionId).catch(() => {
2083
+ });
2084
+ page._init().then(async () => {
2085
+ for (const script of this._initScripts) {
2086
+ await page.addInitScript(script).catch(() => {
2087
+ });
2088
+ }
2089
+ this._pages.push(page);
2090
+ this._emitter.emit("page", page);
2091
+ });
2092
+ };
2093
+ this.conn.on("Target.attachedToTarget", this.targetAttachedHandler);
2094
+ }
2095
+ };
2096
+
2097
+ // src/cdp-driver/browser.ts
2098
+ var XBBrowserImpl = class {
2099
+ conn;
2100
+ _emitter = new EventEmitter3();
2101
+ _contexts = /* @__PURE__ */ new Map();
2102
+ _disconnected = false;
2103
+ childProcess = null;
2104
+ tmpDir;
2105
+ _exitHandler = null;
2106
+ constructor(conn, childProcess, tmpDir) {
2107
+ this.conn = conn;
2108
+ this.childProcess = childProcess ?? null;
2109
+ this.tmpDir = tmpDir;
2110
+ conn.on("disconnect", () => {
2111
+ this._disconnected = true;
2112
+ this._emitter.emit("disconnected");
2113
+ });
2114
+ if (this.childProcess) {
2115
+ this._exitHandler = () => {
2116
+ try {
2117
+ if (this.childProcess?.exitCode === null) {
2118
+ this.childProcess.kill("SIGKILL");
2119
+ }
2120
+ } catch {
2121
+ }
2122
+ if (this.tmpDir) {
2123
+ try {
2124
+ const { rmSync } = __require("fs");
2125
+ rmSync(this.tmpDir, { recursive: true, force: true });
2126
+ } catch {
2127
+ }
2128
+ }
2129
+ };
2130
+ process.on("exit", this._exitHandler);
2131
+ }
2132
+ }
2133
+ get disconnected() {
2134
+ return this._disconnected;
2135
+ }
2136
+ /** The underlying CDP connection (for advanced use) */
2137
+ get connection() {
2138
+ return this.conn;
2139
+ }
2140
+ async close() {
2141
+ if (this._disconnected) return;
2142
+ this._disconnected = true;
2143
+ for (const [, info] of this._contexts) {
2144
+ await info.context.close().catch(() => {
2145
+ });
2146
+ }
2147
+ this._contexts.clear();
2148
+ if (this._exitHandler) {
2149
+ process.removeListener("exit", this._exitHandler);
2150
+ this._exitHandler = null;
2151
+ }
2152
+ if (this.childProcess) {
2153
+ const { killChrome: killChrome2 } = await import("./launcher-KA7J32K5.js");
2154
+ await killChrome2(this.childProcess, this.tmpDir);
2155
+ }
2156
+ await this.conn.close();
2157
+ this._emitter.emit("disconnected");
2158
+ }
2159
+ async newContext(opts = {}) {
2160
+ if (this._disconnected) {
2161
+ throw new Error("Browser is disconnected");
2162
+ }
2163
+ let contextId = "default";
2164
+ try {
2165
+ const result = await this.conn.send(
2166
+ "Target.createBrowserContext",
2167
+ { disposeOnDetach: true },
2168
+ void 0,
2169
+ 1e4
2170
+ // 10s timeout instead of default 30s
2171
+ );
2172
+ contextId = result.browserContextId;
2173
+ } catch {
2174
+ }
2175
+ const context = new XBContextImpl(this.conn, contextId, this, opts);
2176
+ context.on("page", (page) => {
2177
+ this._emitter.emit("page", page);
2178
+ });
2179
+ this._contexts.set(contextId, {
2180
+ contextId,
2181
+ context
2182
+ });
2183
+ return context;
2184
+ }
2185
+ contexts() {
2186
+ return Array.from(this._contexts.values()).map((info) => info.context);
2187
+ }
2188
+ on(event, handler) {
2189
+ this._emitter.on(event, handler);
2190
+ }
2191
+ off(event, handler) {
2192
+ this._emitter.off(event, handler);
2193
+ }
2194
+ /** Called by context.close() to remove from registry */
2195
+ _removeContext(contextId) {
2196
+ this._contexts.delete(contextId);
2197
+ }
2198
+ // ── CDP helpers exposed for context/page ────────────────────
2199
+ /** Attach to a target and get a session ID for flat protocol */
2200
+ async _attachToTarget(targetId) {
2201
+ const result = await this.conn.send(
2202
+ "Target.attachToTarget",
2203
+ { targetId, flatten: true }
2204
+ );
2205
+ return result.sessionId;
2206
+ }
2207
+ /** Detach from a target session */
2208
+ async _detachFromTarget(sessionId) {
2209
+ await this.conn.send("Target.detachFromTarget", { sessionId });
2210
+ }
2211
+ /** Create a new page target within a browser context */
2212
+ async _createTarget(contextId, url = "about:blank") {
2213
+ const params = { url };
2214
+ if (contextId && contextId !== "default") {
2215
+ params.browserContextId = contextId;
2216
+ }
2217
+ return this.conn.send("Target.createTarget", params);
2218
+ }
2219
+ /** Close a target */
2220
+ async _closeTarget(targetId) {
2221
+ await this.conn.send("Target.closeTarget", { targetId });
2222
+ }
2223
+ /** Enable auto-attach for new targets */
2224
+ async _enableAutoAttach() {
2225
+ await this.conn.send("Target.setAutoAttach", {
2226
+ autoAttach: true,
2227
+ waitForDebuggerOnStart: true,
2228
+ flatten: true
2229
+ });
2230
+ }
2231
+ };
2232
+
2233
+ // src/cdp-driver/connection.ts
2234
+ import { EventEmitter as EventEmitter4 } from "events";
2235
+ import { WebSocket } from "ws";
2236
+ var CDPConnection = class extends EventEmitter4 {
2237
+ ws;
2238
+ nextId = 1;
2239
+ pending = /* @__PURE__ */ new Map();
2240
+ closed = false;
2241
+ closeReason = null;
2242
+ /** Default session ID for flat session protocol (Target.attachToTarget) */
2243
+ defaultSessionId;
2244
+ constructor(wsOrUrl, sessionId) {
2245
+ super();
2246
+ this.defaultSessionId = sessionId;
2247
+ if (typeof wsOrUrl === "string") {
2248
+ this.ws = new WebSocket(wsOrUrl);
2249
+ } else {
2250
+ this.ws = wsOrUrl;
2251
+ }
2252
+ this.bindWebSocket();
2253
+ }
2254
+ /** Wait for the connection to be fully open */
2255
+ async ready() {
2256
+ if (this.ws.readyState === WebSocket.OPEN) return;
2257
+ if (this.ws.readyState === WebSocket.CLOSED || this.ws.readyState === WebSocket.CLOSING) {
2258
+ throw new Error(`WebSocket already closed: ${this.closeReason ?? "unknown"}`);
2259
+ }
2260
+ return new Promise((resolve, reject) => {
2261
+ const onOpen = () => {
2262
+ this.ws.off("error", onError);
2263
+ resolve();
2264
+ };
2265
+ const onError = (err) => {
2266
+ this.ws.off("open", onOpen);
2267
+ reject(err);
2268
+ };
2269
+ this.ws.once("open", onOpen);
2270
+ this.ws.once("error", onError);
2271
+ });
2272
+ }
2273
+ /** Is the underlying WebSocket alive? */
2274
+ get isOpen() {
2275
+ return !this.closed && this.ws.readyState === WebSocket.OPEN;
2276
+ }
2277
+ /**
2278
+ * Send a CDP command and await its response.
2279
+ *
2280
+ * @param method — CDP domain.method (e.g. "Page.navigate")
2281
+ * @param params — method parameters
2282
+ * @param sessionId — optional flat session ID for sub-targets
2283
+ * @param timeoutMs — response timeout (default: 30s)
2284
+ * @returns the `result` field from the CDP response
2285
+ */
2286
+ async send(method, params, sessionId, timeoutMs = 3e4) {
2287
+ if (this.closed) {
2288
+ throw new Error(`CDP connection closed: ${this.closeReason ?? "unknown"}`);
2289
+ }
2290
+ if (!this.isOpen) {
2291
+ throw new Error(`CDP connection not open (state: ${this.ws.readyState})`);
2292
+ }
2293
+ const id = this.nextId++;
2294
+ const sid = sessionId ?? this.defaultSessionId;
2295
+ const message = { id, method };
2296
+ if (params !== void 0) message.params = params;
2297
+ if (sid !== void 0) message.sessionId = sid;
2298
+ return new Promise((resolve, reject) => {
2299
+ const timeout = setTimeout(() => {
2300
+ this.pending.delete(id);
2301
+ reject(new Error(`CDP timeout: ${method} (${timeoutMs}ms)`));
2302
+ }, timeoutMs);
2303
+ this.pending.set(id, {
2304
+ resolve: (v) => {
2305
+ clearTimeout(timeout);
2306
+ this.pending.delete(id);
2307
+ resolve(v);
2308
+ },
2309
+ reject: (err) => {
2310
+ clearTimeout(timeout);
2311
+ this.pending.delete(id);
2312
+ reject(err);
2313
+ },
2314
+ method,
2315
+ timeout
2316
+ });
2317
+ const data = JSON.stringify(message);
2318
+ try {
2319
+ this.ws.send(data);
2320
+ } catch (err) {
2321
+ clearTimeout(timeout);
2322
+ this.pending.delete(id);
2323
+ reject(new Error(`CDP send failed: ${method} \u2014 ${err instanceof Error ? err.message : String(err)}`));
2324
+ }
2325
+ });
2326
+ }
2327
+ /**
2328
+ * Subscribe to a CDP event.
2329
+ *
2330
+ * @param event — full event name (e.g. "Page.frameNavigated")
2331
+ * @param handler — called with the event params
2332
+ * @param sessionId — optional session filter
2333
+ */
2334
+ on(event, handler) {
2335
+ return super.on(event, handler);
2336
+ }
2337
+ once(event, handler) {
2338
+ return super.once(event, handler);
2339
+ }
2340
+ /** Remove an event listener */
2341
+ off(event, handler) {
2342
+ super.off(event, handler);
2343
+ return this;
2344
+ }
2345
+ /**
2346
+ * Subscribe to a CDP event for a specific session.
2347
+ * Returns an unsubscribe function.
2348
+ */
2349
+ subscribe(event, sessionId, handler) {
2350
+ const wrapper = (params, sid) => {
2351
+ if (sid === sessionId || !sessionId && !sid) handler(params);
2352
+ };
2353
+ this.on(event, wrapper);
2354
+ return () => this.off(event, wrapper);
2355
+ }
2356
+ /** Close the WebSocket */
2357
+ async close() {
2358
+ if (this.closed) return;
2359
+ this.closed = true;
2360
+ this.closeReason = "closed by caller";
2361
+ for (const [id, pending] of this.pending) {
2362
+ clearTimeout(pending.timeout);
2363
+ pending.reject(new Error(`Connection closed: ${pending.method}`));
2364
+ this.pending.delete(id);
2365
+ }
2366
+ if (this.ws.readyState === WebSocket.OPEN || this.ws.readyState === WebSocket.CONNECTING) {
2367
+ this.ws.close(1e3, "normal closure");
2368
+ }
2369
+ }
2370
+ /** Set the default session ID for flat protocol */
2371
+ setDefaultSessionId(sid) {
2372
+ this.defaultSessionId = sid;
2373
+ }
2374
+ // ── Private ─────────────────────────────────────────────────
2375
+ bindWebSocket() {
2376
+ this.ws.on("message", (raw) => {
2377
+ let msg;
2378
+ try {
2379
+ msg = JSON.parse(raw.toString());
2380
+ } catch {
2381
+ return;
2382
+ }
2383
+ if (msg.id !== void 0) {
2384
+ const pending = this.pending.get(msg.id);
2385
+ if (!pending) return;
2386
+ if (msg.error) {
2387
+ pending.reject(new CDPProtocolError(msg.error.code, msg.error.message, pending.method));
2388
+ } else {
2389
+ pending.resolve(msg.result ?? {});
2390
+ }
2391
+ return;
2392
+ }
2393
+ if (msg.method) {
2394
+ this.emit(msg.method, msg.params ?? {}, msg.sessionId);
2395
+ this.emit("*", msg.method, msg.params ?? {}, msg.sessionId);
2396
+ }
2397
+ });
2398
+ this.ws.on("close", (code, reason) => {
2399
+ if (this.closed) return;
2400
+ this.closed = true;
2401
+ this.closeReason = `WebSocket closed: ${code} ${reason?.toString() ?? ""}`.trim();
2402
+ for (const [id, pending] of this.pending) {
2403
+ clearTimeout(pending.timeout);
2404
+ pending.reject(new Error(`Connection closed: ${pending.method}`));
2405
+ this.pending.delete(id);
2406
+ }
2407
+ this.emit("disconnect");
2408
+ });
2409
+ this.ws.on("error", (err) => {
2410
+ if (this.closed) return;
2411
+ this.emit("ws-error", err);
2412
+ });
2413
+ }
2414
+ };
2415
+ var CDPProtocolError = class extends Error {
2416
+ code;
2417
+ method;
2418
+ data;
2419
+ constructor(code, message, method, data) {
2420
+ super(`CDP error [${code}] in ${method}: ${message}`);
2421
+ this.name = "CDPProtocolError";
2422
+ this.code = code;
2423
+ this.method = method;
2424
+ this.data = data;
2425
+ }
2426
+ };
2427
+
2428
+ // src/cdp-driver/wait.ts
2429
+ async function waitForNetworkIdle(conn, sessionId, opts = {}) {
2430
+ const idleTime = opts.idleTime ?? 500;
2431
+ const timeout = opts.timeout ?? 3e4;
2432
+ const maxInflight = opts.maxInflight ?? 0;
2433
+ return new Promise((resolve, reject) => {
2434
+ let inflight = 0;
2435
+ let idleTimer = null;
2436
+ let timeoutTimer = null;
2437
+ let unsub1 = null;
2438
+ let unsub2 = null;
2439
+ let unsub3 = null;
2440
+ const cleanup = () => {
2441
+ if (unsub1) unsub1();
2442
+ if (unsub2) unsub2();
2443
+ if (unsub3) unsub3();
2444
+ if (idleTimer) clearTimeout(idleTimer);
2445
+ if (timeoutTimer) clearTimeout(timeoutTimer);
2446
+ };
2447
+ const checkIdle = () => {
2448
+ if (inflight <= maxInflight) {
2449
+ if (!idleTimer) {
2450
+ idleTimer = setTimeout(() => {
2451
+ cleanup();
2452
+ resolve();
2453
+ }, idleTime);
2454
+ }
2455
+ } else {
2456
+ if (idleTimer) {
2457
+ clearTimeout(idleTimer);
2458
+ idleTimer = null;
2459
+ }
2460
+ }
2461
+ };
2462
+ const onRequest = () => {
2463
+ inflight++;
2464
+ if (idleTimer) {
2465
+ clearTimeout(idleTimer);
2466
+ idleTimer = null;
2467
+ }
2468
+ };
2469
+ const onFinish = () => {
2470
+ inflight = Math.max(0, inflight - 1);
2471
+ checkIdle();
2472
+ };
2473
+ const onFail = () => {
2474
+ inflight = Math.max(0, inflight - 1);
2475
+ checkIdle();
2476
+ };
2477
+ timeoutTimer = setTimeout(() => {
2478
+ cleanup();
2479
+ reject(new Error(`waitForNetworkIdle timeout after ${timeout}ms`));
2480
+ }, timeout);
2481
+ unsub1 = conn.subscribe("Network.requestWillBeSent", sessionId, onRequest);
2482
+ unsub2 = conn.subscribe("Network.loadingFinished", sessionId, onFinish);
2483
+ unsub3 = conn.subscribe("Network.loadingFailed", sessionId, onFail);
2484
+ checkIdle();
2485
+ });
2486
+ }
2487
+
2488
+ // src/cdp-driver/index.ts
2489
+ async function launch(options = {}) {
2490
+ let wsEndpoint;
2491
+ let childProcess;
2492
+ let tmpDir;
2493
+ if (options.cdpEndpoint) {
2494
+ wsEndpoint = await connectToCDP(options.cdpEndpoint);
2495
+ } else {
2496
+ const result = await launchChrome({
2497
+ executablePath: options.executablePath,
2498
+ headless: options.headless,
2499
+ args: options.args,
2500
+ userDataDir: options.userDataDir,
2501
+ timeout: options.timeout,
2502
+ env: options.env
2503
+ });
2504
+ wsEndpoint = result.wsEndpoint;
2505
+ childProcess = result.process;
2506
+ tmpDir = result.tmpDir;
2507
+ }
2508
+ const conn = new CDPConnection(wsEndpoint);
2509
+ await conn.ready();
2510
+ const browser = new XBBrowserImpl(conn, childProcess, tmpDir);
2511
+ return { browser, wsEndpoint };
2512
+ }
2513
+
2514
+ export {
2515
+ XBMouseImpl,
2516
+ XBKeyboardImpl,
2517
+ waitForActionable,
2518
+ checkActionable,
2519
+ scrollIntoView,
2520
+ XBLocatorImpl,
2521
+ XBElementHandleImpl,
2522
+ XBPageImpl,
2523
+ XBCDPSessionImpl,
2524
+ XBContextImpl,
2525
+ XBBrowserImpl,
2526
+ CDPConnection,
2527
+ CDPProtocolError,
2528
+ waitForNetworkIdle,
2529
+ launch
2530
+ };