@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 +5 -3
- package/dist/{browser-ZF4EJ3SK.js → browser-CWI6BXYK.js} +2 -2
- package/dist/{browser-H55TWH2I.js → browser-JP2LFPR2.js} +2 -2
- package/dist/{browser-DZVIVKOA.js → browser-PZX7PO23.js} +1 -1
- package/dist/{cdp-driver-DF5JHA7W.js → cdp-driver-RPUNQBGM.js} +117 -52
- package/dist/{cdp-driver-WWQBRTPF.js → cdp-driver-S5STYUZZ.js} +1 -1
- package/dist/{chunk-OH7CB2P6.js → chunk-6V57JME6.js} +1 -1
- package/dist/{chunk-XVZ6NKRJ.js → chunk-MNFOCOL6.js} +117 -52
- package/dist/{chunk-E5WWMKXB.js → chunk-QFROODUU.js} +117 -52
- package/dist/{chunk-NDAMCPIJ.js → chunk-SLQR57XZ.js} +1 -1
- package/dist/cli.js +122 -13
- package/dist/daemon-main.js +67 -6
- package/dist/index.js +123 -14
- package/dist/{session-replayer-UHITXIOZ.js → session-replayer-IXLSCF5U.js} +1 -1
- package/package.json +2 -2
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.
|
|
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
|
[](https://github.com/dyyz1993/xbrowser/actions)
|
|
6
6
|
[](https://codecov.io/gh/dyyz1993/xbrowser)
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
## 特性
|
|
11
11
|
|
|
12
|
-
- **
|
|
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/ #
|
|
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-
|
|
24
|
-
import "./chunk-
|
|
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-
|
|
24
|
-
import "./chunk-
|
|
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";
|
|
@@ -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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
568
|
-
|
|
569
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
})(${
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
})(${
|
|
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 =
|
|
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
|
-
})(${
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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
|
-
},
|
|
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 =
|
|
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 =
|
|
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 =
|
|
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();
|