@xbrowser/cli 1.1.2 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # xbrowser
2
2
 
3
- > **Browser automation CLI** for web scraping, headless browsing, SEO analysis, and AI agent workflows. 49 commands, 20+ plugins. A command-line alternative to Playwright, Puppeteer, and Selenium — **no code required**.
3
+ > **Browser automation CLI** for web scraping, headless browsing, SEO analysis, and AI agent workflows. 50 commands, 20+ plugins. A command-line alternative to Playwright, Puppeteer, and Selenium — **no code required**.
4
4
 
5
5
  [![CI Status](https://github.com/dyyz1993/xbrowser/workflows/CI/badge.svg)](https://github.com/dyyz1993/xbrowser/actions)
6
6
  [![codecov](https://codecov.io/gh/dyyz1993/xbrowser/branch/master/graph/badge.svg)](https://codecov.io/gh/dyyz1993/xbrowser)
@@ -9,7 +9,7 @@
9
9
 
10
10
  ## 特性
11
11
 
12
- - **49 浏览器命令** — 导航、交互、查询、存储、截图,覆盖常见自动化场景
12
+ - **50 浏览器命令** — 导航、交互、查询、存储、截图,覆盖常见自动化场景
13
13
  - **命令链** — 用 `&&`、`,`、`+`、`->`、`;` 串联多个命令,一行搞定复杂流程
14
14
  - **管道 & Heredoc** — 支持 stdin 管道和 heredoc 批量执行
15
15
  - **录制 / 回放** — 录制浏览器操作为 YAML,随时回放,可转换为 JS/Python/Bash 脚本
@@ -64,6 +64,8 @@ xbrowser juejin publish --file article.md
64
64
  npm install -g @xbrowser/cli
65
65
  ```
66
66
 
67
+ > ⚠️ **正确的包名是 `@xbrowser/cli`**(scoped package)。`xbrowser`(无 scope)和 `@dyyz1993/xbrowser` 都不是最新版本。
68
+
67
69
  ### 最低要求
68
70
 
69
71
  - Node.js >= 18
@@ -852,7 +854,7 @@ npm run validate
852
854
  ```
853
855
  xbrowser/
854
856
  ├── src/
855
- │ ├── commands/ # 49 个浏览器命令定义
857
+ │ ├── commands/ # 50 个浏览器命令定义
856
858
  │ ├── builtins/ # CLI 内置命令(config, plugin, session, create)
857
859
  │ ├── recorder/ # 录制引擎(录制器 + 回放器)
858
860
  │ ├── session/ # 会话管理
@@ -20,8 +20,8 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-NDAMCPIJ.js";
24
- import "./chunk-E5WWMKXB.js";
23
+ } from "./chunk-SLQR57XZ.js";
24
+ import "./chunk-QFROODUU.js";
25
25
  import "./chunk-TNEN6VQ2.js";
26
26
  import "./chunk-GDKLH7ZY.js";
27
27
  import "./chunk-KFQGP6VL.js";
@@ -20,8 +20,8 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-OH7CB2P6.js";
24
- import "./chunk-E5WWMKXB.js";
23
+ } from "./chunk-6V57JME6.js";
24
+ import "./chunk-QFROODUU.js";
25
25
  import "./chunk-TNEN6VQ2.js";
26
26
  import "./chunk-GDKLH7ZY.js";
27
27
  import "./chunk-ABXMBNQ6.js";
@@ -20,7 +20,7 @@ import {
20
20
  saveSessionDiskMeta,
21
21
  setActivePage,
22
22
  touchSession
23
- } from "./chunk-XVZ6NKRJ.js";
23
+ } from "./chunk-MNFOCOL6.js";
24
24
  import "./chunk-TNEN6VQ2.js";
25
25
  import "./chunk-GDKLH7ZY.js";
26
26
  import "./chunk-KFQGP6VL.js";
@@ -283,6 +283,22 @@ function sleep2(ms) {
283
283
  return new Promise((resolve) => setTimeout(resolve, ms));
284
284
  }
285
285
 
286
+ // src/cdp-driver/selector-utils.ts
287
+ function queryJS(selector) {
288
+ if (selector.startsWith("xpath=")) {
289
+ const xpath = JSON.stringify(selector.slice(6));
290
+ return `document.evaluate(${xpath}, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue`;
291
+ }
292
+ return `document.querySelector(${JSON.stringify(selector)})`;
293
+ }
294
+ function queryAllJS(selector) {
295
+ if (selector.startsWith("xpath=")) {
296
+ const xpath = JSON.stringify(selector.slice(6));
297
+ return `(() => { const it = document.evaluate(${xpath}, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); const r=[]; for(let i=0;i<it.snapshotLength;i++) r.push(it.snapshotItem(i)); return r; })()`;
298
+ }
299
+ return `document.querySelectorAll(${JSON.stringify(selector)})`;
300
+ }
301
+
286
302
  // src/cdp-driver/actionability.ts
287
303
  async function waitForActionable(page, selector, opts = {}) {
288
304
  const timeout = opts.timeout ?? 3e4;
@@ -307,7 +323,7 @@ async function waitForActionable(page, selector, opts = {}) {
307
323
  async function checkActionable(page, selector) {
308
324
  const result = await page.evaluate(`
309
325
  (function() {
310
- const el = document.querySelector(${JSON.stringify(selector)});
326
+ const el = ${queryJS(selector)};
311
327
  if (!el) return { ok: false, reason: 'not_found' };
312
328
 
313
329
  // Check attached to DOM
@@ -350,7 +366,7 @@ async function checkActionable(page, selector) {
350
366
  async function scrollIntoView(page, selector) {
351
367
  await page.evaluate(`
352
368
  (function() {
353
- const el = document.querySelector(${JSON.stringify(selector)});
369
+ const el = ${queryJS(selector)};
354
370
  if (el) el.scrollIntoView({ block: 'center', inline: 'center', behavior: 'instant' });
355
371
  })()
356
372
  `);
@@ -364,13 +380,21 @@ var XBLocatorImpl = class _XBLocatorImpl {
364
380
  this.page = page;
365
381
  this.selector = selector;
366
382
  }
383
+ /** Resolve selector to a JS expression that finds a single element (CSS or xpath). */
384
+ _q(sel) {
385
+ return queryJS(sel);
386
+ }
387
+ /** Resolve selector to a JS expression that finds all matching elements (CSS or xpath). */
388
+ _qa(sel) {
389
+ return queryAllJS(sel);
390
+ }
367
391
  // ── Actions ─────────────────────────────────────────────────
368
392
  async click(opts = {}) {
369
393
  const { rect } = await waitForActionable(this.page, this.selector, opts);
370
394
  await scrollIntoView(this.page, this.selector);
371
395
  const updatedRect = await this.page.evaluate(`
372
396
  (function() {
373
- const el = document.querySelector(${JSON.stringify(this.selector)});
397
+ const el = ${this._q(this.selector)};
374
398
  if (!el) return null;
375
399
  const rect = el.getBoundingClientRect();
376
400
  return { x: rect.x, y: rect.y, width: rect.width, height: rect.height };
@@ -390,7 +414,7 @@ var XBLocatorImpl = class _XBLocatorImpl {
390
414
  await scrollIntoView(this.page, this.selector);
391
415
  await this.page.evaluate(`
392
416
  (function() {
393
- const el = document.querySelector(${JSON.stringify(this.selector)});
417
+ const el = ${this._q(this.selector)};
394
418
  if (!el) throw new Error('Element not found: ${this.selector.replace(/'/g, "\\'")}');
395
419
  el.focus();
396
420
  el.value = '';
@@ -400,7 +424,7 @@ var XBLocatorImpl = class _XBLocatorImpl {
400
424
  await this.page.keyboard.insertText(value);
401
425
  await this.page.evaluate(`
402
426
  (function() {
403
- const el = document.querySelector(${JSON.stringify(this.selector)});
427
+ const el = ${this._q(this.selector)};
404
428
  if (el) {
405
429
  el.dispatchEvent(new Event('input', { bubbles: true }));
406
430
  el.dispatchEvent(new Event('change', { bubbles: true }));
@@ -413,7 +437,7 @@ var XBLocatorImpl = class _XBLocatorImpl {
413
437
  await scrollIntoView(this.page, this.selector);
414
438
  await this.page.evaluate(`
415
439
  (function() {
416
- const el = document.querySelector(${JSON.stringify(this.selector)});
440
+ const el = ${this._q(this.selector)};
417
441
  if (el) el.focus();
418
442
  })()
419
443
  `);
@@ -424,7 +448,7 @@ var XBLocatorImpl = class _XBLocatorImpl {
424
448
  await scrollIntoView(this.page, this.selector);
425
449
  await this.page.evaluate(`
426
450
  (function() {
427
- const el = document.querySelector(${JSON.stringify(this.selector)});
451
+ const el = ${this._q(this.selector)};
428
452
  if (el) el.focus();
429
453
  })()
430
454
  `);
@@ -441,7 +465,7 @@ var XBLocatorImpl = class _XBLocatorImpl {
441
465
  await scrollIntoView(this.page, this.selector);
442
466
  const rect = await this.page.evaluate(`
443
467
  (function() {
444
- const el = document.querySelector(${JSON.stringify(this.selector)});
468
+ const el = ${this._q(this.selector)};
445
469
  if (!el) return { x: 0, y: 0, width: 0, height: 0 };
446
470
  const r = el.getBoundingClientRect();
447
471
  return { x: r.x, y: r.y, width: r.width, height: r.height };
@@ -459,7 +483,7 @@ var XBLocatorImpl = class _XBLocatorImpl {
459
483
  await waitForActionable(this.page, this.selector, { timeout: opts.timeout });
460
484
  const isChecked = await this.page.evaluate(`
461
485
  (function() {
462
- const el = document.querySelector(${JSON.stringify(this.selector)});
486
+ const el = ${this._q(this.selector)};
463
487
  return el?.checked === true;
464
488
  })()
465
489
  `);
@@ -471,7 +495,7 @@ var XBLocatorImpl = class _XBLocatorImpl {
471
495
  await waitForActionable(this.page, this.selector, { timeout: opts.timeout });
472
496
  const isChecked = await this.page.evaluate(`
473
497
  (function() {
474
- const el = document.querySelector(${JSON.stringify(this.selector)});
498
+ const el = ${this._q(this.selector)};
475
499
  return el?.checked === true;
476
500
  })()
477
501
  `);
@@ -484,7 +508,7 @@ var XBLocatorImpl = class _XBLocatorImpl {
484
508
  const values = Array.isArray(value) ? value : [value];
485
509
  const selected = await this.page.evaluate(`
486
510
  (function() {
487
- const el = document.querySelector(${JSON.stringify(this.selector)});
511
+ const el = ${this._q(this.selector)};
488
512
  if (!el || el.tagName !== 'SELECT') throw new Error('Not a select element');
489
513
 
490
514
  const values = ${JSON.stringify(values)};
@@ -512,10 +536,15 @@ var XBLocatorImpl = class _XBLocatorImpl {
512
536
  }
513
537
  async screenshot(opts = {}) {
514
538
  await waitForActionable(this.page, this.selector);
515
- const nodeId = await this.page.querySelector(this.selector);
516
- if (!nodeId) throw new Error(`Element not found: ${this.selector}`);
517
- const box = await this.page.getBoxModel(nodeId);
518
- if (!box) throw new Error("Element has no box");
539
+ const box = await this.page.evaluate(`
540
+ (function() {
541
+ const el = ${this._q(this.selector)};
542
+ if (!el) return null;
543
+ const r = el.getBoundingClientRect();
544
+ return { x: r.x, y: r.y, width: r.width, height: r.height };
545
+ })()
546
+ `);
547
+ if (!box) throw new Error(`Element not found: ${this.selector}`);
519
548
  return this.page.screenshot({
520
549
  ...opts,
521
550
  clip: { x: box.x, y: box.y, width: box.width, height: box.height }
@@ -527,14 +556,14 @@ var XBLocatorImpl = class _XBLocatorImpl {
527
556
  }
528
557
  async count() {
529
558
  return this.page.evaluate(`
530
- document.querySelectorAll(${JSON.stringify(this.selector)}).length
559
+ ${this._qa(this.selector)}.length
531
560
  `);
532
561
  }
533
562
  async isVisible() {
534
563
  try {
535
564
  const result = await this.page.evaluate(`
536
565
  (function() {
537
- const el = document.querySelector(${JSON.stringify(this.selector)});
566
+ const el = ${this._q(this.selector)};
538
567
  if (!el) return false;
539
568
  if (!el.isConnected) return false;
540
569
  const style = window.getComputedStyle(el);
@@ -554,7 +583,7 @@ var XBLocatorImpl = class _XBLocatorImpl {
554
583
  async isEnabled() {
555
584
  return this.page.evaluate(`
556
585
  (function() {
557
- const el = document.querySelector(${JSON.stringify(this.selector)});
586
+ const el = ${this._q(this.selector)};
558
587
  if (!el) return false;
559
588
  return !el.disabled;
560
589
  })()
@@ -564,15 +593,20 @@ var XBLocatorImpl = class _XBLocatorImpl {
564
593
  return !await this.isEnabled();
565
594
  }
566
595
  async boundingBox() {
567
- const nodeId = await this.page.querySelector(this.selector);
568
- if (!nodeId) return null;
569
- return this.page.getBoxModel(nodeId);
596
+ return this.page.evaluate(`
597
+ (function() {
598
+ const el = ${this._q(this.selector)};
599
+ if (!el) return null;
600
+ const r = el.getBoundingClientRect();
601
+ return { x: r.x, y: r.y, width: r.width, height: r.height };
602
+ })()
603
+ `);
570
604
  }
571
605
  // ── Text/HTML ───────────────────────────────────────────────
572
606
  async textContent() {
573
607
  return this.page.evaluate(`
574
608
  (function() {
575
- const el = document.querySelector(${JSON.stringify(this.selector)});
609
+ const el = ${this._q(this.selector)};
576
610
  return el?.textContent ?? null;
577
611
  })()
578
612
  `);
@@ -580,7 +614,7 @@ var XBLocatorImpl = class _XBLocatorImpl {
580
614
  async innerText() {
581
615
  return this.page.evaluate(`
582
616
  (function() {
583
- const el = document.querySelector(${JSON.stringify(this.selector)});
617
+ const el = ${this._q(this.selector)};
584
618
  if (!el) throw new Error('Element not found');
585
619
  return el.innerText;
586
620
  })()
@@ -589,7 +623,7 @@ var XBLocatorImpl = class _XBLocatorImpl {
589
623
  async innerHTML() {
590
624
  return this.page.evaluate(`
591
625
  (function() {
592
- const el = document.querySelector(${JSON.stringify(this.selector)});
626
+ const el = ${this._q(this.selector)};
593
627
  if (!el) throw new Error('Element not found');
594
628
  return el.innerHTML;
595
629
  })()
@@ -598,7 +632,7 @@ var XBLocatorImpl = class _XBLocatorImpl {
598
632
  async getAttribute(name) {
599
633
  return this.page.evaluate(`
600
634
  (function() {
601
- const el = document.querySelector(${JSON.stringify(this.selector)});
635
+ const el = ${this._q(this.selector)};
602
636
  return el?.getAttribute(${JSON.stringify(name)}) ?? null;
603
637
  })()
604
638
  `);
@@ -606,13 +640,17 @@ var XBLocatorImpl = class _XBLocatorImpl {
606
640
  // ── Evaluate ────────────────────────────────────────────────
607
641
  async evaluate(fn, ...args) {
608
642
  const fnBody = typeof fn === "function" ? fn.toString() : fn;
643
+ const sel = JSON.stringify(this.selector);
644
+ const xpathPrefix = this.selector.startsWith("xpath=") ? JSON.stringify(this.selector.slice(6)) : "null";
609
645
  return this.page.evaluate(
610
- `(function(sel, fnStr, ...evalArgs) {
611
- const el = document.querySelector(sel);
646
+ `(function(sel, xpathExpr, fnStr, ...evalArgs) {
647
+ const el = xpathExpr
648
+ ? document.evaluate(xpathExpr, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
649
+ : document.querySelector(sel);
612
650
  if (!el) throw new Error('No element found for selector: ' + sel);
613
651
  const fn = new Function('return ' + fnStr)();
614
652
  return fn(el, ...evalArgs);
615
- })(${JSON.stringify(this.selector)}, ${JSON.stringify(fnBody)}${args.length > 0 ? ", " + args.map((a) => JSON.stringify(a)).join(", ") : ""})`
653
+ })(${sel}, ${xpathPrefix}, ${JSON.stringify(fnBody)}${args.length > 0 ? ", " + args.map((a) => JSON.stringify(a)).join(", ") : ""})`
616
654
  );
617
655
  }
618
656
  async ariaSnapshot() {
@@ -639,7 +677,7 @@ var XBLocatorImpl = class _XBLocatorImpl {
639
677
  }
640
678
  async all() {
641
679
  const n = await this.page.evaluate(`
642
- document.querySelectorAll(${JSON.stringify(this.selector)}).length
680
+ ${this._qa(this.selector)}.length
643
681
  `);
644
682
  const locators = [];
645
683
  for (let i = 0; i < n; i++) {
@@ -650,16 +688,33 @@ var XBLocatorImpl = class _XBLocatorImpl {
650
688
  async focus() {
651
689
  await this.page.evaluate(`
652
690
  (function() {
653
- const el = document.querySelector(${JSON.stringify(this.selector)});
691
+ const el = ${this._q(this.selector)};
654
692
  if (el) el.focus();
655
693
  })()
656
694
  `);
657
695
  }
658
696
  };
659
697
  var FilteredLocator = class extends XBLocatorImpl {
698
+ index;
660
699
  constructor(page, selector, index) {
661
- const indexedSelector = index === -1 ? `${selector}:last-of-type` : `${selector}:nth-of-type(${index + 1})`;
700
+ const indexedSelector = selector.startsWith("xpath=") ? selector : index === -1 ? `${selector}:last-of-type` : `${selector}:nth-of-type(${index + 1})`;
662
701
  super(page, indexedSelector);
702
+ this.index = index;
703
+ this._rawSelector = selector;
704
+ }
705
+ /** Original selector before index filtering */
706
+ _rawSelector;
707
+ /** For xpath selectors, override _q to return the nth element from evaluate results */
708
+ _q(sel) {
709
+ if (!sel.startsWith("xpath=")) return super._q(sel);
710
+ const xpath = JSON.stringify(this._rawSelector.slice(6));
711
+ return `(() => { const it = document.evaluate(${xpath}, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); return ${this.index === -1 ? "it.snapshotItem(it.snapshotLength - 1)" : `it.snapshotItem(${this.index})`}; })()`;
712
+ }
713
+ /** For xpath selectors, override _qa to return all matching elements from evaluate */
714
+ _qa(sel) {
715
+ if (!sel.startsWith("xpath=")) return super._qa(sel);
716
+ const xpath = JSON.stringify(this._rawSelector.slice(6));
717
+ return `(() => { const it = document.evaluate(${xpath}, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); const r=[]; for(let i=0;i<it.snapshotLength;i++) r.push(it.snapshotItem(i)); return r; })()`;
663
718
  }
664
719
  };
665
720
  var VisibleFilteredLocator = class extends XBLocatorImpl {
@@ -667,7 +722,7 @@ var VisibleFilteredLocator = class extends XBLocatorImpl {
667
722
  const tag = `data-xb-vt-${Date.now()}-${Math.floor(Math.random() * 1e6)}`;
668
723
  const found = await this.page.evaluate(`
669
724
  (function() {
670
- const els = document.querySelectorAll(${JSON.stringify(this.selector)});
725
+ const els = ${this._qa(this.selector)};
671
726
  for (const el of els) {
672
727
  if (!el.isConnected) continue;
673
728
  const style = window.getComputedStyle(el);
@@ -685,7 +740,7 @@ var VisibleFilteredLocator = class extends XBLocatorImpl {
685
740
  return await fn(`[${tag}]`);
686
741
  } finally {
687
742
  await this.page.evaluate(`
688
- document.querySelectorAll(${JSON.stringify(`[${tag}]`)}).forEach(el => el.removeAttribute(${JSON.stringify(tag)}))
743
+ ${this._qa(`[${tag}]`)}.forEach(el => el.removeAttribute(${JSON.stringify(tag)}))
689
744
  `);
690
745
  }
691
746
  }
@@ -705,7 +760,7 @@ var VisibleFilteredLocator = class extends XBLocatorImpl {
705
760
  return this.page.evaluate(`
706
761
  (function() {
707
762
  let count = 0;
708
- const els = document.querySelectorAll(${JSON.stringify(this.selector)});
763
+ const els = ${this._qa(this.selector)};
709
764
  for (const el of els) {
710
765
  if (!el.isConnected) continue;
711
766
  const style = window.getComputedStyle(el);
@@ -722,7 +777,7 @@ var VisibleFilteredLocator = class extends XBLocatorImpl {
722
777
  try {
723
778
  const result = await this.page.evaluate(`
724
779
  (function() {
725
- const els = document.querySelectorAll(${JSON.stringify(this.selector)});
780
+ const els = ${this._qa(this.selector)};
726
781
  for (const el of els) {
727
782
  if (!el.isConnected) continue;
728
783
  const style = window.getComputedStyle(el);
@@ -1167,20 +1222,20 @@ var XBPageImpl = class _XBPageImpl {
1167
1222
  const deadline = Date.now() + timeout;
1168
1223
  while (Date.now() < deadline) {
1169
1224
  const exists = await this.evaluate(
1170
- `(function() { const el = document.querySelector(${JSON.stringify(selector)}); return !!el; })()`
1225
+ `(function() { const el = ${queryJS(selector)}; return !!el; })()`
1171
1226
  );
1172
1227
  if (state === "attached" && exists) return;
1173
1228
  if (state === "detached" && !exists) return;
1174
1229
  if (state === "visible" && exists) {
1175
1230
  const visible = await this.evaluate(
1176
- `(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'; })()`
1231
+ `(function() { const el = ${queryJS(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'; })()`
1177
1232
  );
1178
1233
  if (visible) return;
1179
1234
  }
1180
1235
  if (state === "hidden") {
1181
1236
  if (!exists) return;
1182
1237
  const visible = await this.evaluate(
1183
- `(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'; })()`
1238
+ `(function() { const el = ${queryJS(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'; })()`
1184
1239
  );
1185
1240
  if (!visible) return;
1186
1241
  }
@@ -1223,6 +1278,8 @@ Last error: ${lastError.message}` : "";
1223
1278
  // ── Evaluation ──────────────────────────────────────────────
1224
1279
  async evaluate(fn, ...args) {
1225
1280
  if (this._closed) throw new Error("Page is closed");
1281
+ this.conn.send("Page.handleJavaScriptDialog", { accept: false }, this.sessionId).catch(() => {
1282
+ });
1226
1283
  let expression;
1227
1284
  if (typeof fn === "string") {
1228
1285
  expression = fn;
@@ -1262,23 +1319,31 @@ Last error: ${lastError.message}` : "";
1262
1319
  }
1263
1320
  async $eval(selector, fn, ...args) {
1264
1321
  const fnBody = typeof fn === "function" ? fn.toString() : fn;
1322
+ const selJSON = JSON.stringify(selector);
1323
+ const xpathPrefix = selector.startsWith("xpath=") ? JSON.stringify(selector.slice(6)) : "null";
1265
1324
  return this.evaluate(
1266
- `(function(sel, fnStr, ...evalArgs) {
1267
- const el = document.querySelector(sel);
1325
+ `(function(sel, xpathExpr, fnStr, ...evalArgs) {
1326
+ const el = xpathExpr
1327
+ ? document.evaluate(xpathExpr, document, null, XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue
1328
+ : document.querySelector(sel);
1268
1329
  if (!el) throw new Error('No element found for selector: ' + sel);
1269
1330
  const fn = new Function('return ' + fnStr)();
1270
1331
  return fn(el, ...evalArgs);
1271
- })(${JSON.stringify(selector)}, ${JSON.stringify(fnBody)}${args.length > 0 ? ", " + args.map((a) => JSON.stringify(a)).join(", ") : ""})`
1332
+ })(${selJSON}, ${xpathPrefix}, ${JSON.stringify(fnBody)}${args.length > 0 ? ", " + args.map((a) => JSON.stringify(a)).join(", ") : ""})`
1272
1333
  );
1273
1334
  }
1274
1335
  async $$eval(selector, fn, ...args) {
1275
1336
  const fnBody = typeof fn === "function" ? fn.toString() : fn;
1337
+ const selJSON = JSON.stringify(selector);
1338
+ const xpathPrefix = selector.startsWith("xpath=") ? JSON.stringify(selector.slice(6)) : "null";
1276
1339
  return this.evaluate(
1277
- `(function(sel, fnStr, ...evalArgs) {
1278
- const els = Array.from(document.querySelectorAll(sel));
1340
+ `(function(sel, xpathExpr, fnStr, ...evalArgs) {
1341
+ const els = xpathExpr
1342
+ ? (() => { const it = document.evaluate(xpathExpr, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); const r=[]; for(let i=0;i<it.snapshotLength;i++) r.push(it.snapshotItem(i)); return r; })()
1343
+ : Array.from(document.querySelectorAll(sel));
1279
1344
  const fn = new Function('return ' + fnStr)();
1280
1345
  return fn(els, ...evalArgs);
1281
- })(${JSON.stringify(selector)}, ${JSON.stringify(fnBody)}${args.length > 0 ? ", " + args.map((a) => JSON.stringify(a)).join(", ") : ""})`
1346
+ })(${selJSON}, ${xpathPrefix}, ${JSON.stringify(fnBody)}${args.length > 0 ? ", " + args.map((a) => JSON.stringify(a)).join(", ") : ""})`
1282
1347
  );
1283
1348
  }
1284
1349
  // ── Locator ─────────────────────────────────────────────────
@@ -1351,22 +1416,22 @@ Last error: ${lastError.message}` : "";
1351
1416
  // ── Convenience selectors ───────────────────────────────────
1352
1417
  async textContent(selector) {
1353
1418
  return this.evaluate(
1354
- `(function() { const el = document.querySelector(${JSON.stringify(selector)}); return el?.textContent ?? null; })()`
1419
+ `(function() { const el = ${queryJS(selector)}; return el?.textContent ?? null; })()`
1355
1420
  );
1356
1421
  }
1357
1422
  async innerText(selector) {
1358
1423
  return this.evaluate(
1359
- `(function() { const el = document.querySelector(${JSON.stringify(selector)}); if (!el) throw new Error('Element not found'); return el.innerText; })()`
1424
+ `(function() { const el = ${queryJS(selector)}; if (!el) throw new Error('Element not found'); return el.innerText; })()`
1360
1425
  );
1361
1426
  }
1362
1427
  async innerHTML(selector) {
1363
1428
  return this.evaluate(
1364
- `(function() { const el = document.querySelector(${JSON.stringify(selector)}); if (!el) throw new Error('Element not found'); return el.innerHTML; })()`
1429
+ `(function() { const el = ${queryJS(selector)}; if (!el) throw new Error('Element not found'); return el.innerHTML; })()`
1365
1430
  );
1366
1431
  }
1367
1432
  async getAttribute(selector, name) {
1368
1433
  return this.evaluate(
1369
- `(function() { const el = document.querySelector(${JSON.stringify(selector)}); return el?.getAttribute(${JSON.stringify(name)}) ?? null; })()`
1434
+ `(function() { const el = ${queryJS(selector)}; return el?.getAttribute(${JSON.stringify(name)}) ?? null; })()`
1370
1435
  );
1371
1436
  }
1372
1437
  // ── Query ───────────────────────────────────────────────────
@@ -1672,7 +1737,7 @@ Last error: ${lastError.message}` : "";
1672
1737
  setTimeout(() => {
1673
1738
  this.conn.send("Page.handleJavaScriptDialog", { accept: false }, this.sessionId).catch(() => {
1674
1739
  });
1675
- }, 100);
1740
+ }, 0);
1676
1741
  })
1677
1742
  );
1678
1743
  this._subscriptions.push(
@@ -1987,7 +2052,7 @@ Last error: ${lastError.message}` : "";
1987
2052
  await this.evaluate(`
1988
2053
  (function() {
1989
2054
  var selector = ${JSON.stringify(selector)};
1990
- var input = document.querySelector(selector);
2055
+ var input = ${queryJS(selector)};
1991
2056
  if (!input) throw new Error('Element not found: ' + selector);
1992
2057
 
1993
2058
  var fileList = ${JSON.stringify(fileList)};
@@ -2012,7 +2077,7 @@ Last error: ${lastError.message}` : "";
2012
2077
  async dragAndDrop(source, target) {
2013
2078
  const sourceRect = await this.evaluate(`
2014
2079
  (function() {
2015
- const el = document.querySelector(${JSON.stringify(source)});
2080
+ const el = ${queryJS(source)};
2016
2081
  if (!el) throw new Error('Source not found: ${source}');
2017
2082
  el.scrollIntoView({ behavior: 'instant', block: 'center' });
2018
2083
  const r = el.getBoundingClientRect();
@@ -2021,7 +2086,7 @@ Last error: ${lastError.message}` : "";
2021
2086
  `);
2022
2087
  const targetRect = await this.evaluate(`
2023
2088
  (function() {
2024
- const el = document.querySelector(${JSON.stringify(target)});
2089
+ const el = ${queryJS(target)};
2025
2090
  if (!el) throw new Error('Target not found: ${target}');
2026
2091
  el.scrollIntoView({ behavior: 'instant', block: 'center' });
2027
2092
  const r = el.getBoundingClientRect();
@@ -14,7 +14,7 @@ import {
14
14
  scrollIntoView,
15
15
  waitForActionable,
16
16
  waitForNetworkIdle
17
- } from "./chunk-E5WWMKXB.js";
17
+ } from "./chunk-QFROODUU.js";
18
18
  import {
19
19
  connectToCDP,
20
20
  findChrome,
@@ -1,6 +1,6 @@
1
1
  import {
2
2
  launch
3
- } from "./chunk-E5WWMKXB.js";
3
+ } from "./chunk-QFROODUU.js";
4
4
  import {
5
5
  errMsg
6
6
  } from "./chunk-GDKLH7ZY.js";