@workglow/browser-control 0.2.30 → 0.2.32
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 +44 -0
- package/dist/task/index.d.ts +0 -1
- package/dist/task/index.d.ts.map +1 -1
- package/dist/task.d.ts +0 -2
- package/dist/task.d.ts.map +1 -1
- package/dist/task.js +1 -957
- package/dist/task.js.map +4 -7
- package/package.json +9 -28
- package/dist/task/BunWebViewBackend.d.ts +0 -59
- package/dist/task/BunWebViewBackend.d.ts.map +0 -1
- package/dist/task/ElectronBackend.d.ts +0 -57
- package/dist/task/ElectronBackend.d.ts.map +0 -1
- package/dist/task/PlaywrightBackend.d.ts +0 -78
- package/dist/task/PlaywrightBackend.d.ts.map +0 -1
- package/dist/task/register.electron.d.ts +0 -11
- package/dist/task/register.electron.d.ts.map +0 -1
- package/dist/task/register.server.d.ts +0 -11
- package/dist/task/register.server.d.ts.map +0 -1
- package/dist/task-electron.d.ts +0 -15
- package/dist/task-electron.d.ts.map +0 -1
- package/dist/task-electron.js +0 -3449
- package/dist/task-electron.js.map +0 -42
- package/dist/task-server.d.ts +0 -15
- package/dist/task-server.d.ts.map +0 -1
- package/dist/task-server.js +0 -3468
- package/dist/task-server.js.map +0 -42
package/dist/task.js
CHANGED
|
@@ -1,11 +1,3 @@
|
|
|
1
|
-
var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
|
|
2
|
-
get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
|
|
3
|
-
}) : x)(function(x) {
|
|
4
|
-
if (typeof require !== "undefined")
|
|
5
|
-
return require.apply(this, arguments);
|
|
6
|
-
throw Error('Dynamic require of "' + x + '" is not supported');
|
|
7
|
-
});
|
|
8
|
-
|
|
9
1
|
// src/task/BrowserSessionRegistry.ts
|
|
10
2
|
import { uuid4 } from "@workglow/util";
|
|
11
3
|
var sessions = new Map;
|
|
@@ -41,9 +33,6 @@ var BrowserSessionRegistry = {
|
|
|
41
33
|
sessions.clear();
|
|
42
34
|
}
|
|
43
35
|
};
|
|
44
|
-
// src/task/BunWebViewBackend.ts
|
|
45
|
-
import { sleep } from "@workglow/util";
|
|
46
|
-
|
|
47
36
|
// src/task/CDPBrowserBackend.ts
|
|
48
37
|
var IGNORED_ROLES = new Set(["none", "generic", "ignored", "InlineTextBox"]);
|
|
49
38
|
function parseCDPAXTree(nodes, refCounter, refMap) {
|
|
@@ -556,236 +545,6 @@ class CDPBrowserBackend {
|
|
|
556
545
|
});
|
|
557
546
|
}
|
|
558
547
|
}
|
|
559
|
-
|
|
560
|
-
// src/task/BunWebViewBackend.ts
|
|
561
|
-
class BunWebViewBackend extends CDPBrowserBackend {
|
|
562
|
-
defaultChromePath;
|
|
563
|
-
_wv = null;
|
|
564
|
-
_connected = false;
|
|
565
|
-
_dialogHandler = null;
|
|
566
|
-
backendName = "BunWebViewBackend";
|
|
567
|
-
constructor(defaultChromePath) {
|
|
568
|
-
super();
|
|
569
|
-
this.defaultChromePath = defaultChromePath;
|
|
570
|
-
}
|
|
571
|
-
async cdp(method, params = {}) {
|
|
572
|
-
return this.wv.cdp(method, params);
|
|
573
|
-
}
|
|
574
|
-
async evaluateInPage(script) {
|
|
575
|
-
return this.wv.evaluate(script);
|
|
576
|
-
}
|
|
577
|
-
async connect(options = {}) {
|
|
578
|
-
const BunWebView = globalThis.Bun?.WebView;
|
|
579
|
-
if (!BunWebView) {
|
|
580
|
-
throw new Error("BunWebViewBackend: Bun.WebView is not available — " + "this backend requires Bun with WebView support");
|
|
581
|
-
}
|
|
582
|
-
const { headless = true, chromePath = this.defaultChromePath } = options;
|
|
583
|
-
const webViewOptions = {
|
|
584
|
-
headless,
|
|
585
|
-
url: "about:blank",
|
|
586
|
-
backend: chromePath ? { type: "chrome", path: chromePath } : { type: "chrome" }
|
|
587
|
-
};
|
|
588
|
-
this._wv = new BunWebView(webViewOptions);
|
|
589
|
-
await new Promise((resolve, reject) => {
|
|
590
|
-
const timeout = setTimeout(() => {
|
|
591
|
-
reject(new Error("BunWebViewBackend: initial navigation timed out"));
|
|
592
|
-
}, 30000);
|
|
593
|
-
this._wv.onNavigated = () => {
|
|
594
|
-
clearTimeout(timeout);
|
|
595
|
-
this._wv.onNavigated = null;
|
|
596
|
-
this._wv.onNavigationFailed = null;
|
|
597
|
-
resolve();
|
|
598
|
-
};
|
|
599
|
-
this._wv.onNavigationFailed = (error) => {
|
|
600
|
-
clearTimeout(timeout);
|
|
601
|
-
this._wv.onNavigated = null;
|
|
602
|
-
this._wv.onNavigationFailed = null;
|
|
603
|
-
reject(new Error(`BunWebViewBackend: initial navigation failed — ${error}`));
|
|
604
|
-
};
|
|
605
|
-
});
|
|
606
|
-
await this.cdp("Accessibility.enable");
|
|
607
|
-
this._connected = true;
|
|
608
|
-
}
|
|
609
|
-
async disconnect() {
|
|
610
|
-
this._connected = false;
|
|
611
|
-
try {
|
|
612
|
-
if (this._wv) {
|
|
613
|
-
this._wv.close();
|
|
614
|
-
}
|
|
615
|
-
} finally {
|
|
616
|
-
this._wv = null;
|
|
617
|
-
this._refMap.clear();
|
|
618
|
-
this._refCounter.count = 0;
|
|
619
|
-
}
|
|
620
|
-
}
|
|
621
|
-
isConnected() {
|
|
622
|
-
return this._connected && this._wv !== null;
|
|
623
|
-
}
|
|
624
|
-
get wv() {
|
|
625
|
-
if (!this._wv || !this._connected) {
|
|
626
|
-
throw new Error("BunWebViewBackend: not connected — call connect() first");
|
|
627
|
-
}
|
|
628
|
-
return this._wv;
|
|
629
|
-
}
|
|
630
|
-
async navigate(url, options = {}) {
|
|
631
|
-
const timeout = options.timeout ?? 30000;
|
|
632
|
-
await new Promise((resolve, reject) => {
|
|
633
|
-
const timer = setTimeout(() => {
|
|
634
|
-
reject(new Error("BunWebViewBackend: navigate timed out"));
|
|
635
|
-
}, timeout);
|
|
636
|
-
this.wv.onNavigated = () => {
|
|
637
|
-
clearTimeout(timer);
|
|
638
|
-
this.wv.onNavigated = null;
|
|
639
|
-
resolve();
|
|
640
|
-
};
|
|
641
|
-
this.wv.onNavigationFailed = (error) => {
|
|
642
|
-
clearTimeout(timer);
|
|
643
|
-
this.wv.onNavigationFailed = null;
|
|
644
|
-
this.wv.onNavigated = null;
|
|
645
|
-
reject(new Error(`BunWebViewBackend: navigation failed — ${error}`));
|
|
646
|
-
};
|
|
647
|
-
this.wv.navigate(url);
|
|
648
|
-
});
|
|
649
|
-
}
|
|
650
|
-
async goBack(_options = {}) {
|
|
651
|
-
this.wv.back();
|
|
652
|
-
await this.waitForNavigation();
|
|
653
|
-
}
|
|
654
|
-
async goForward(_options = {}) {
|
|
655
|
-
this.wv.forward();
|
|
656
|
-
await this.waitForNavigation();
|
|
657
|
-
}
|
|
658
|
-
async reload(_options = {}) {
|
|
659
|
-
this.wv.reload();
|
|
660
|
-
await this.waitForNavigation();
|
|
661
|
-
}
|
|
662
|
-
async currentUrl() {
|
|
663
|
-
return this.wv.url;
|
|
664
|
-
}
|
|
665
|
-
async title() {
|
|
666
|
-
return this.wv.title;
|
|
667
|
-
}
|
|
668
|
-
async content() {
|
|
669
|
-
return this.wv.evaluate("document.documentElement.outerHTML");
|
|
670
|
-
}
|
|
671
|
-
async evaluate(expression) {
|
|
672
|
-
return this.wv.evaluate(expression);
|
|
673
|
-
}
|
|
674
|
-
async screenshot(options = {}) {
|
|
675
|
-
const { format = "png", quality } = options;
|
|
676
|
-
return this.wv.screenshot({
|
|
677
|
-
encoding: "buffer",
|
|
678
|
-
format,
|
|
679
|
-
...quality !== undefined && { quality }
|
|
680
|
-
});
|
|
681
|
-
}
|
|
682
|
-
async pressKey(key, _options = {}) {
|
|
683
|
-
await this.wv.press(key);
|
|
684
|
-
}
|
|
685
|
-
async type(text, _options = {}) {
|
|
686
|
-
await this.wv.type(text);
|
|
687
|
-
}
|
|
688
|
-
async download(_trigger, _options = {}) {
|
|
689
|
-
throw new Error("BunWebViewBackend: download is not supported");
|
|
690
|
-
}
|
|
691
|
-
onDialog(handler) {
|
|
692
|
-
this._dialogHandler = handler;
|
|
693
|
-
this.cdp("Page.enable").then(() => {
|
|
694
|
-
this.wv.addEventListener("Page.javascriptDialogOpening", async (params) => {
|
|
695
|
-
const info = {
|
|
696
|
-
type: params.type,
|
|
697
|
-
message: params.message,
|
|
698
|
-
defaultValue: params.defaultPrompt || undefined
|
|
699
|
-
};
|
|
700
|
-
if (this._dialogHandler) {
|
|
701
|
-
const action = await this._dialogHandler(info);
|
|
702
|
-
const accept = action.accept;
|
|
703
|
-
const promptText = accept && "promptText" in action ? action.promptText : undefined;
|
|
704
|
-
await this.cdp("Page.handleJavaScriptDialog", {
|
|
705
|
-
accept,
|
|
706
|
-
...promptText !== undefined && { promptText }
|
|
707
|
-
});
|
|
708
|
-
} else {
|
|
709
|
-
await this.cdp("Page.handleJavaScriptDialog", { accept: false });
|
|
710
|
-
}
|
|
711
|
-
});
|
|
712
|
-
}).catch(() => {});
|
|
713
|
-
}
|
|
714
|
-
async tabs() {
|
|
715
|
-
const url = this.wv.url;
|
|
716
|
-
const title = this.wv.title;
|
|
717
|
-
return [{ tabId: "0", url, title }];
|
|
718
|
-
}
|
|
719
|
-
async switchTab(_tabId) {}
|
|
720
|
-
async newTab(url) {
|
|
721
|
-
if (url) {
|
|
722
|
-
await this.navigate(url);
|
|
723
|
-
}
|
|
724
|
-
return {
|
|
725
|
-
tabId: "0",
|
|
726
|
-
url: this.wv.url,
|
|
727
|
-
title: this.wv.title
|
|
728
|
-
};
|
|
729
|
-
}
|
|
730
|
-
async closeTab(_tabId) {
|
|
731
|
-
await this.disconnect();
|
|
732
|
-
}
|
|
733
|
-
async waitForNavigation(options = {}) {
|
|
734
|
-
const timeout = options.timeout ?? 30000;
|
|
735
|
-
return new Promise((resolve, reject) => {
|
|
736
|
-
const timer = setTimeout(() => {
|
|
737
|
-
this.wv.onNavigated = null;
|
|
738
|
-
this.wv.onNavigationFailed = null;
|
|
739
|
-
reject(new Error("BunWebViewBackend: waitForNavigation timed out"));
|
|
740
|
-
}, timeout);
|
|
741
|
-
this.wv.onNavigated = () => {
|
|
742
|
-
clearTimeout(timer);
|
|
743
|
-
this.wv.onNavigated = null;
|
|
744
|
-
this.wv.onNavigationFailed = null;
|
|
745
|
-
resolve();
|
|
746
|
-
};
|
|
747
|
-
this.wv.onNavigationFailed = (error) => {
|
|
748
|
-
clearTimeout(timer);
|
|
749
|
-
this.wv.onNavigated = null;
|
|
750
|
-
this.wv.onNavigationFailed = null;
|
|
751
|
-
reject(new Error(`BunWebViewBackend: navigation failed — ${error}`));
|
|
752
|
-
};
|
|
753
|
-
});
|
|
754
|
-
}
|
|
755
|
-
async waitForSelector(selector, options = {}) {
|
|
756
|
-
const timeout = options.timeout ?? 30000;
|
|
757
|
-
const interval = 100;
|
|
758
|
-
const deadline = Date.now() + timeout;
|
|
759
|
-
while (Date.now() < deadline) {
|
|
760
|
-
const found = await this.wv.evaluate(`!!document.querySelector(${JSON.stringify(selector)})`);
|
|
761
|
-
if (found) {
|
|
762
|
-
const ref = await this.querySelector(selector);
|
|
763
|
-
if (ref)
|
|
764
|
-
return ref;
|
|
765
|
-
}
|
|
766
|
-
await sleep(interval);
|
|
767
|
-
}
|
|
768
|
-
throw new Error(`BunWebViewBackend: waitForSelector timed out for "${selector}"`);
|
|
769
|
-
}
|
|
770
|
-
async waitForIdle(options = {}) {
|
|
771
|
-
const timeout = options.timeout ?? 30000;
|
|
772
|
-
const interval = 100;
|
|
773
|
-
const deadline = Date.now() + timeout;
|
|
774
|
-
while (Date.now() < deadline) {
|
|
775
|
-
const ready = await this.wv.evaluate(`document.readyState === "complete"`);
|
|
776
|
-
if (ready)
|
|
777
|
-
return;
|
|
778
|
-
await sleep(interval);
|
|
779
|
-
}
|
|
780
|
-
throw new Error("BunWebViewBackend: waitForIdle timed out");
|
|
781
|
-
}
|
|
782
|
-
networkRequests = (_filter) => {
|
|
783
|
-
return Promise.resolve([]);
|
|
784
|
-
};
|
|
785
|
-
consoleMessages = () => {
|
|
786
|
-
return Promise.resolve([]);
|
|
787
|
-
};
|
|
788
|
-
}
|
|
789
548
|
// src/task/tasks/BrowserSessionTask.ts
|
|
790
549
|
import {
|
|
791
550
|
Entitlements,
|
|
@@ -2683,725 +2442,10 @@ class BrowserLoginTask extends Task26 {
|
|
|
2683
2442
|
return { sessionId: input.sessionId };
|
|
2684
2443
|
}
|
|
2685
2444
|
}
|
|
2686
|
-
// src/task/ElectronBackend.ts
|
|
2687
|
-
import { sleep as sleep2 } from "@workglow/util";
|
|
2688
|
-
var electronModule = null;
|
|
2689
|
-
async function getElectron() {
|
|
2690
|
-
if (!electronModule) {
|
|
2691
|
-
electronModule = await new Function("m", "return import(m)")("electron");
|
|
2692
|
-
}
|
|
2693
|
-
return electronModule;
|
|
2694
|
-
}
|
|
2695
|
-
|
|
2696
|
-
class ElectronBackend extends CDPBrowserBackend {
|
|
2697
|
-
_window = null;
|
|
2698
|
-
_webContents = null;
|
|
2699
|
-
_connected = false;
|
|
2700
|
-
_dialogHandler = null;
|
|
2701
|
-
backendName = "ElectronBackend";
|
|
2702
|
-
async cdp(method, params = {}) {
|
|
2703
|
-
if (!this._webContents) {
|
|
2704
|
-
throw new Error("ElectronBackend: not connected — call connect() first");
|
|
2705
|
-
}
|
|
2706
|
-
return this._webContents.debugger.sendCommand(method, params);
|
|
2707
|
-
}
|
|
2708
|
-
async evaluateInPage(script) {
|
|
2709
|
-
return this.wc.executeJavaScript(script);
|
|
2710
|
-
}
|
|
2711
|
-
async connect(options = {}) {
|
|
2712
|
-
const electron = await getElectron();
|
|
2713
|
-
const { BrowserWindow, session: electronSession } = electron;
|
|
2714
|
-
const { projectId = "default", profileName = "default", headless = false } = options;
|
|
2715
|
-
const partitionString = `persist:${projectId}:${profileName}`;
|
|
2716
|
-
const sess = electronSession.fromPartition(partitionString);
|
|
2717
|
-
this._window = new BrowserWindow({
|
|
2718
|
-
width: 1280,
|
|
2719
|
-
height: 800,
|
|
2720
|
-
show: !headless,
|
|
2721
|
-
webPreferences: {
|
|
2722
|
-
session: sess,
|
|
2723
|
-
nodeIntegration: false,
|
|
2724
|
-
contextIsolation: true
|
|
2725
|
-
}
|
|
2726
|
-
});
|
|
2727
|
-
this._webContents = this._window.webContents;
|
|
2728
|
-
try {
|
|
2729
|
-
this._webContents.debugger.attach("1.3");
|
|
2730
|
-
} catch {}
|
|
2731
|
-
await this.cdp("Accessibility.enable");
|
|
2732
|
-
this._webContents.on("select-client-certificate", (_event, _url, _list, callback) => {
|
|
2733
|
-
callback(undefined);
|
|
2734
|
-
});
|
|
2735
|
-
this._webContents.on("will-prevent-unload", (event) => {
|
|
2736
|
-
event.preventDefault();
|
|
2737
|
-
});
|
|
2738
|
-
this._connected = true;
|
|
2739
|
-
}
|
|
2740
|
-
async disconnect() {
|
|
2741
|
-
this._connected = false;
|
|
2742
|
-
try {
|
|
2743
|
-
if (this._webContents) {
|
|
2744
|
-
try {
|
|
2745
|
-
this._webContents.debugger.detach();
|
|
2746
|
-
} catch {}
|
|
2747
|
-
}
|
|
2748
|
-
if (this._window && !this._window.isDestroyed()) {
|
|
2749
|
-
this._window.close();
|
|
2750
|
-
}
|
|
2751
|
-
} finally {
|
|
2752
|
-
this._window = null;
|
|
2753
|
-
this._webContents = null;
|
|
2754
|
-
this._refMap.clear();
|
|
2755
|
-
this._refCounter.count = 0;
|
|
2756
|
-
}
|
|
2757
|
-
}
|
|
2758
|
-
isConnected() {
|
|
2759
|
-
return this._connected && this._window !== null && !this._window.isDestroyed();
|
|
2760
|
-
}
|
|
2761
|
-
get wc() {
|
|
2762
|
-
if (!this._webContents || !this._connected) {
|
|
2763
|
-
throw new Error("ElectronBackend: not connected — call connect() first");
|
|
2764
|
-
}
|
|
2765
|
-
return this._webContents;
|
|
2766
|
-
}
|
|
2767
|
-
async navigate(url, _options = {}) {
|
|
2768
|
-
await this.wc.loadURL(url);
|
|
2769
|
-
}
|
|
2770
|
-
async goBack(_options = {}) {
|
|
2771
|
-
this.wc.navigationHistory.goBack();
|
|
2772
|
-
await this.waitForNavigation();
|
|
2773
|
-
}
|
|
2774
|
-
async goForward(_options = {}) {
|
|
2775
|
-
this.wc.navigationHistory.goForward();
|
|
2776
|
-
await this.waitForNavigation();
|
|
2777
|
-
}
|
|
2778
|
-
async reload(_options = {}) {
|
|
2779
|
-
this.wc.reload();
|
|
2780
|
-
await this.waitForNavigation();
|
|
2781
|
-
}
|
|
2782
|
-
async currentUrl() {
|
|
2783
|
-
return this.wc.getURL();
|
|
2784
|
-
}
|
|
2785
|
-
async title() {
|
|
2786
|
-
return this.wc.getTitle();
|
|
2787
|
-
}
|
|
2788
|
-
async content() {
|
|
2789
|
-
return this.wc.executeJavaScript("document.documentElement.outerHTML");
|
|
2790
|
-
}
|
|
2791
|
-
async evaluate(expression) {
|
|
2792
|
-
return this.wc.executeJavaScript(expression);
|
|
2793
|
-
}
|
|
2794
|
-
async screenshot(options = {}) {
|
|
2795
|
-
const { format = "png", quality } = options;
|
|
2796
|
-
const image = await this.wc.capturePage();
|
|
2797
|
-
if (format === "jpeg") {
|
|
2798
|
-
return image.toJPEG(quality ?? 90);
|
|
2799
|
-
}
|
|
2800
|
-
return image.toPNG();
|
|
2801
|
-
}
|
|
2802
|
-
async download(trigger, options = {}) {
|
|
2803
|
-
const os = await import("node:os");
|
|
2804
|
-
const downloadDir = os.tmpdir();
|
|
2805
|
-
const timeout = options.timeout ?? 30000;
|
|
2806
|
-
await this.cdp("Browser.setDownloadBehavior", {
|
|
2807
|
-
behavior: "allow",
|
|
2808
|
-
downloadPath: downloadDir
|
|
2809
|
-
});
|
|
2810
|
-
let downloadPath = "";
|
|
2811
|
-
let suggestedFilename = "";
|
|
2812
|
-
const downloadPromise = new Promise((resolve, reject) => {
|
|
2813
|
-
const timer = setTimeout(() => {
|
|
2814
|
-
reject(new Error("ElectronBackend: download timed out"));
|
|
2815
|
-
}, timeout);
|
|
2816
|
-
const handler = (_event, item, _webContents) => {
|
|
2817
|
-
suggestedFilename = item.getFilename ? item.getFilename() : "download";
|
|
2818
|
-
item.once?.("done", (_e, state) => {
|
|
2819
|
-
clearTimeout(timer);
|
|
2820
|
-
if (state === "completed") {
|
|
2821
|
-
downloadPath = item.getSavePath ? item.getSavePath() : downloadDir + "/" + suggestedFilename;
|
|
2822
|
-
}
|
|
2823
|
-
resolve();
|
|
2824
|
-
});
|
|
2825
|
-
};
|
|
2826
|
-
this.wc.session.once("will-download", handler);
|
|
2827
|
-
});
|
|
2828
|
-
await trigger();
|
|
2829
|
-
await downloadPromise;
|
|
2830
|
-
if (!downloadPath) {
|
|
2831
|
-
throw new Error("ElectronBackend: download failed — no path received");
|
|
2832
|
-
}
|
|
2833
|
-
return { path: downloadPath, suggestedFilename };
|
|
2834
|
-
}
|
|
2835
|
-
onDialog(handler) {
|
|
2836
|
-
this._dialogHandler = handler;
|
|
2837
|
-
this.cdp("Page.enable").then(() => {
|
|
2838
|
-
this.wc.debugger.on("message", async (_event, method, params) => {
|
|
2839
|
-
if (method !== "Page.javascriptDialogOpening")
|
|
2840
|
-
return;
|
|
2841
|
-
const info = {
|
|
2842
|
-
type: params.type,
|
|
2843
|
-
message: params.message,
|
|
2844
|
-
defaultValue: params.defaultPrompt || undefined
|
|
2845
|
-
};
|
|
2846
|
-
if (this._dialogHandler) {
|
|
2847
|
-
const action = await this._dialogHandler(info);
|
|
2848
|
-
const accept = action.accept;
|
|
2849
|
-
const promptText = accept && "promptText" in action ? action.promptText : undefined;
|
|
2850
|
-
await this.cdp("Page.handleJavaScriptDialog", {
|
|
2851
|
-
accept,
|
|
2852
|
-
...promptText !== undefined && { promptText }
|
|
2853
|
-
});
|
|
2854
|
-
} else {
|
|
2855
|
-
await this.cdp("Page.handleJavaScriptDialog", { accept: false });
|
|
2856
|
-
}
|
|
2857
|
-
});
|
|
2858
|
-
});
|
|
2859
|
-
}
|
|
2860
|
-
async tabs() {
|
|
2861
|
-
const url = this.wc.getURL();
|
|
2862
|
-
const title = this.wc.getTitle();
|
|
2863
|
-
return [{ tabId: "0", url, title }];
|
|
2864
|
-
}
|
|
2865
|
-
async switchTab(_tabId) {}
|
|
2866
|
-
async newTab(url) {
|
|
2867
|
-
if (url) {
|
|
2868
|
-
await this.navigate(url);
|
|
2869
|
-
}
|
|
2870
|
-
return {
|
|
2871
|
-
tabId: "0",
|
|
2872
|
-
url: this.wc.getURL(),
|
|
2873
|
-
title: this.wc.getTitle()
|
|
2874
|
-
};
|
|
2875
|
-
}
|
|
2876
|
-
async closeTab(_tabId) {
|
|
2877
|
-
await this.disconnect();
|
|
2878
|
-
}
|
|
2879
|
-
async waitForNavigation(options = {}) {
|
|
2880
|
-
const timeout = options.timeout ?? 30000;
|
|
2881
|
-
return new Promise((resolve, reject) => {
|
|
2882
|
-
const timer = setTimeout(() => {
|
|
2883
|
-
reject(new Error("ElectronBackend: waitForNavigation timed out"));
|
|
2884
|
-
}, timeout);
|
|
2885
|
-
this.wc.once("did-finish-load", () => {
|
|
2886
|
-
clearTimeout(timer);
|
|
2887
|
-
resolve();
|
|
2888
|
-
});
|
|
2889
|
-
});
|
|
2890
|
-
}
|
|
2891
|
-
async waitForSelector(selector, options = {}) {
|
|
2892
|
-
const timeout = options.timeout ?? 30000;
|
|
2893
|
-
const interval = 100;
|
|
2894
|
-
const deadline = Date.now() + timeout;
|
|
2895
|
-
while (Date.now() < deadline) {
|
|
2896
|
-
const found = await this.wc.executeJavaScript(`!!document.querySelector(${JSON.stringify(selector)})`);
|
|
2897
|
-
if (found) {
|
|
2898
|
-
const ref = await this.querySelector(selector);
|
|
2899
|
-
if (ref)
|
|
2900
|
-
return ref;
|
|
2901
|
-
}
|
|
2902
|
-
await sleep2(interval);
|
|
2903
|
-
}
|
|
2904
|
-
throw new Error(`ElectronBackend: waitForSelector timed out for "${selector}"`);
|
|
2905
|
-
}
|
|
2906
|
-
async waitForIdle(options = {}) {
|
|
2907
|
-
const timeout = options.timeout ?? 30000;
|
|
2908
|
-
const interval = 100;
|
|
2909
|
-
const deadline = Date.now() + timeout;
|
|
2910
|
-
while (Date.now() < deadline) {
|
|
2911
|
-
const ready = await this.wc.executeJavaScript(`document.readyState === "complete"`);
|
|
2912
|
-
if (ready)
|
|
2913
|
-
return;
|
|
2914
|
-
await sleep2(interval);
|
|
2915
|
-
}
|
|
2916
|
-
throw new Error("ElectronBackend: waitForIdle timed out");
|
|
2917
|
-
}
|
|
2918
|
-
networkRequests = (_filter) => {
|
|
2919
|
-
return Promise.resolve([]);
|
|
2920
|
-
};
|
|
2921
|
-
consoleMessages = () => {
|
|
2922
|
-
return Promise.resolve([]);
|
|
2923
|
-
};
|
|
2924
|
-
}
|
|
2925
|
-
// src/task/PlaywrightBackend.ts
|
|
2926
|
-
var playwrightModule;
|
|
2927
|
-
async function getPlaywright() {
|
|
2928
|
-
if (!playwrightModule) {
|
|
2929
|
-
playwrightModule = await import("playwright");
|
|
2930
|
-
}
|
|
2931
|
-
return playwrightModule;
|
|
2932
|
-
}
|
|
2933
|
-
function parseAriaLine(line) {
|
|
2934
|
-
const match = line.match(/^(\s*)-\s+(.*)$/);
|
|
2935
|
-
if (!match)
|
|
2936
|
-
return null;
|
|
2937
|
-
const indent = match[1].length;
|
|
2938
|
-
let rest = match[2].trim();
|
|
2939
|
-
const hasChildren = rest.endsWith(":");
|
|
2940
|
-
if (hasChildren)
|
|
2941
|
-
rest = rest.slice(0, -1).trim();
|
|
2942
|
-
const attrs = {};
|
|
2943
|
-
rest = rest.replace(/\[([^\]]+)\]/g, (_m, attr) => {
|
|
2944
|
-
const eqIdx = attr.indexOf("=");
|
|
2945
|
-
if (eqIdx !== -1) {
|
|
2946
|
-
attrs[attr.slice(0, eqIdx).trim()] = attr.slice(eqIdx + 1).trim();
|
|
2947
|
-
} else {
|
|
2948
|
-
attrs[attr.trim()] = "true";
|
|
2949
|
-
}
|
|
2950
|
-
return "";
|
|
2951
|
-
}).trim();
|
|
2952
|
-
const roleNameMatch = rest.match(/^(\S+)(?:\s+"((?:[^"\\]|\\.)*)")?/);
|
|
2953
|
-
if (!roleNameMatch)
|
|
2954
|
-
return null;
|
|
2955
|
-
const role = roleNameMatch[1];
|
|
2956
|
-
const name = roleNameMatch[2] !== undefined ? roleNameMatch[2].replace(/\\"/g, '"') : "";
|
|
2957
|
-
return { indent, role, name, attrs, hasChildren };
|
|
2958
|
-
}
|
|
2959
|
-
function parseAriaYaml(yaml, refCounter, refMap) {
|
|
2960
|
-
const lines = yaml.split(`
|
|
2961
|
-
`);
|
|
2962
|
-
const stack = [];
|
|
2963
|
-
let root = null;
|
|
2964
|
-
for (const line of lines) {
|
|
2965
|
-
if (!line.trim())
|
|
2966
|
-
continue;
|
|
2967
|
-
const parsed = parseAriaLine(line);
|
|
2968
|
-
if (!parsed)
|
|
2969
|
-
continue;
|
|
2970
|
-
const ref = `e${++refCounter.count}`;
|
|
2971
|
-
const locatorStr = buildLocatorString(parsed.role, parsed.name);
|
|
2972
|
-
refMap.set(ref, locatorStr);
|
|
2973
|
-
const node = {
|
|
2974
|
-
ref,
|
|
2975
|
-
role: parsed.role,
|
|
2976
|
-
name: parsed.name
|
|
2977
|
-
};
|
|
2978
|
-
if (parsed.attrs.level !== undefined) {
|
|
2979
|
-
node.level = parseInt(parsed.attrs.level, 10);
|
|
2980
|
-
}
|
|
2981
|
-
if (parsed.attrs.checked !== undefined) {
|
|
2982
|
-
node.checked = parsed.attrs.checked === "mixed" ? "mixed" : parsed.attrs.checked === "true";
|
|
2983
|
-
}
|
|
2984
|
-
if (parsed.attrs.disabled !== undefined) {
|
|
2985
|
-
node.disabled = parsed.attrs.disabled === "true";
|
|
2986
|
-
}
|
|
2987
|
-
if (parsed.attrs.expanded !== undefined) {
|
|
2988
|
-
node.expanded = parsed.attrs.expanded === "true";
|
|
2989
|
-
}
|
|
2990
|
-
if (parsed.attrs.pressed !== undefined) {
|
|
2991
|
-
node.pressed = parsed.attrs.pressed === "mixed" ? "mixed" : parsed.attrs.pressed === "true";
|
|
2992
|
-
}
|
|
2993
|
-
if (parsed.attrs.selected !== undefined) {
|
|
2994
|
-
node.selected = parsed.attrs.selected === "true";
|
|
2995
|
-
}
|
|
2996
|
-
if (parsed.attrs.value !== undefined) {
|
|
2997
|
-
const numVal = Number(parsed.attrs.value);
|
|
2998
|
-
node.value = isNaN(numVal) ? parsed.attrs.value : numVal;
|
|
2999
|
-
}
|
|
3000
|
-
if (parsed.hasChildren) {
|
|
3001
|
-
node.children = [];
|
|
3002
|
-
}
|
|
3003
|
-
while (stack.length > 0 && stack[stack.length - 1].indent >= parsed.indent) {
|
|
3004
|
-
stack.pop();
|
|
3005
|
-
}
|
|
3006
|
-
if (stack.length === 0) {
|
|
3007
|
-
root = node;
|
|
3008
|
-
} else {
|
|
3009
|
-
const parent = stack[stack.length - 1].node;
|
|
3010
|
-
if (!parent.children)
|
|
3011
|
-
parent.children = [];
|
|
3012
|
-
parent.children.push(node);
|
|
3013
|
-
}
|
|
3014
|
-
stack.push({ node, indent: parsed.indent });
|
|
3015
|
-
}
|
|
3016
|
-
if (!root) {
|
|
3017
|
-
const ref = `e${++refCounter.count}`;
|
|
3018
|
-
refMap.set(ref, 'locator("body")');
|
|
3019
|
-
return { ref, role: "document", name: "" };
|
|
3020
|
-
}
|
|
3021
|
-
return root;
|
|
3022
|
-
}
|
|
3023
|
-
function buildLocatorString(role, name) {
|
|
3024
|
-
if (role === "text" || role === "StaticText") {
|
|
3025
|
-
return `getByText:${name}`;
|
|
3026
|
-
}
|
|
3027
|
-
if (name) {
|
|
3028
|
-
return `getByRole:${role}:${name}`;
|
|
3029
|
-
}
|
|
3030
|
-
return `getByRole:${role}:`;
|
|
3031
|
-
}
|
|
3032
|
-
|
|
3033
|
-
class PlaywrightBackend {
|
|
3034
|
-
sharedLocalBrowser;
|
|
3035
|
-
constructor(sharedLocalBrowser) {
|
|
3036
|
-
this.sharedLocalBrowser = sharedLocalBrowser;
|
|
3037
|
-
}
|
|
3038
|
-
_browser = null;
|
|
3039
|
-
_context = null;
|
|
3040
|
-
_page = null;
|
|
3041
|
-
_connected = false;
|
|
3042
|
-
_launchedLocalChromium = false;
|
|
3043
|
-
_refMap = new Map;
|
|
3044
|
-
_refCounter = { count: 0 };
|
|
3045
|
-
_dialogHandler = null;
|
|
3046
|
-
async connect(options = {}) {
|
|
3047
|
-
const pw = await getPlaywright();
|
|
3048
|
-
const { headless = true, cdpUrl, backend = "local" } = options;
|
|
3049
|
-
if (backend === "cloud" || cdpUrl) {
|
|
3050
|
-
if (this.sharedLocalBrowser) {
|
|
3051
|
-
throw new Error("PlaywrightBackend: sharedLocalBrowser is only supported for local backend");
|
|
3052
|
-
}
|
|
3053
|
-
this._launchedLocalChromium = false;
|
|
3054
|
-
if (!cdpUrl) {
|
|
3055
|
-
throw new Error("PlaywrightBackend: cdpUrl is required for cloud backend");
|
|
3056
|
-
}
|
|
3057
|
-
this._browser = await pw.chromium.connectOverCDP(cdpUrl);
|
|
3058
|
-
const contexts = this._browser.contexts();
|
|
3059
|
-
this._context = contexts.length > 0 ? contexts[0] : await this._browser.newContext();
|
|
3060
|
-
const pages = this._context.pages();
|
|
3061
|
-
this._page = pages.length > 0 ? pages[0] : await this._context.newPage();
|
|
3062
|
-
} else {
|
|
3063
|
-
if (this.sharedLocalBrowser) {
|
|
3064
|
-
this._browser = this.sharedLocalBrowser;
|
|
3065
|
-
this._launchedLocalChromium = false;
|
|
3066
|
-
} else {
|
|
3067
|
-
this._launchedLocalChromium = true;
|
|
3068
|
-
this._browser = await pw.chromium.launch({
|
|
3069
|
-
headless,
|
|
3070
|
-
args: ["--disable-dev-shm-usage"]
|
|
3071
|
-
});
|
|
3072
|
-
}
|
|
3073
|
-
this._context = await this._browser.newContext();
|
|
3074
|
-
this._page = await this._context.newPage();
|
|
3075
|
-
}
|
|
3076
|
-
this._page.on("dialog", async (dialog) => {
|
|
3077
|
-
const info = {
|
|
3078
|
-
type: dialog.type(),
|
|
3079
|
-
message: dialog.message(),
|
|
3080
|
-
defaultValue: dialog.defaultValue() ?? undefined
|
|
3081
|
-
};
|
|
3082
|
-
if (this._dialogHandler) {
|
|
3083
|
-
const action = await this._dialogHandler(info);
|
|
3084
|
-
if (action.accept) {
|
|
3085
|
-
await dialog.accept("promptText" in action ? action.promptText : undefined);
|
|
3086
|
-
} else {
|
|
3087
|
-
await dialog.dismiss();
|
|
3088
|
-
}
|
|
3089
|
-
} else {
|
|
3090
|
-
await dialog.dismiss();
|
|
3091
|
-
}
|
|
3092
|
-
});
|
|
3093
|
-
this._connected = true;
|
|
3094
|
-
}
|
|
3095
|
-
async disconnect() {
|
|
3096
|
-
this._connected = false;
|
|
3097
|
-
const page = this._page;
|
|
3098
|
-
const context = this._context;
|
|
3099
|
-
const browser = this._browser;
|
|
3100
|
-
const launchedLocal = this._launchedLocalChromium;
|
|
3101
|
-
const sharedLocal = this.sharedLocalBrowser !== undefined;
|
|
3102
|
-
this._page = null;
|
|
3103
|
-
this._context = null;
|
|
3104
|
-
this._browser = null;
|
|
3105
|
-
this._launchedLocalChromium = false;
|
|
3106
|
-
try {
|
|
3107
|
-
if (launchedLocal || sharedLocal) {
|
|
3108
|
-
if (page) {
|
|
3109
|
-
try {
|
|
3110
|
-
await page.close({ runBeforeUnload: false });
|
|
3111
|
-
} catch {}
|
|
3112
|
-
}
|
|
3113
|
-
if (context) {
|
|
3114
|
-
try {
|
|
3115
|
-
await context.close();
|
|
3116
|
-
} catch {}
|
|
3117
|
-
}
|
|
3118
|
-
}
|
|
3119
|
-
if (browser && !sharedLocal) {
|
|
3120
|
-
await browser.close();
|
|
3121
|
-
}
|
|
3122
|
-
} finally {
|
|
3123
|
-
this._refMap.clear();
|
|
3124
|
-
this._refCounter.count = 0;
|
|
3125
|
-
}
|
|
3126
|
-
}
|
|
3127
|
-
isConnected() {
|
|
3128
|
-
return this._connected;
|
|
3129
|
-
}
|
|
3130
|
-
get page() {
|
|
3131
|
-
if (!this._page)
|
|
3132
|
-
throw new Error("PlaywrightBackend: not connected — call connect() first");
|
|
3133
|
-
return this._page;
|
|
3134
|
-
}
|
|
3135
|
-
get context() {
|
|
3136
|
-
if (!this._context)
|
|
3137
|
-
throw new Error("PlaywrightBackend: not connected — call connect() first");
|
|
3138
|
-
return this._context;
|
|
3139
|
-
}
|
|
3140
|
-
resolveRef(ref) {
|
|
3141
|
-
const descriptor = this._refMap.get(ref);
|
|
3142
|
-
if (!descriptor) {
|
|
3143
|
-
throw new Error(`PlaywrightBackend: unknown ref "${ref}"`);
|
|
3144
|
-
}
|
|
3145
|
-
return this.descriptorToLocator(descriptor);
|
|
3146
|
-
}
|
|
3147
|
-
descriptorToLocator(descriptor) {
|
|
3148
|
-
const page = this.page;
|
|
3149
|
-
if (descriptor.startsWith("getByRole:")) {
|
|
3150
|
-
const rest = descriptor.slice("getByRole:".length);
|
|
3151
|
-
const colonIdx = rest.indexOf(":");
|
|
3152
|
-
const role = rest.slice(0, colonIdx);
|
|
3153
|
-
const name = rest.slice(colonIdx + 1);
|
|
3154
|
-
if (name) {
|
|
3155
|
-
return page.getByRole(role, { name });
|
|
3156
|
-
}
|
|
3157
|
-
return page.getByRole(role);
|
|
3158
|
-
}
|
|
3159
|
-
if (descriptor.startsWith("getByText:")) {
|
|
3160
|
-
const text = descriptor.slice("getByText:".length);
|
|
3161
|
-
return page.getByText(text);
|
|
3162
|
-
}
|
|
3163
|
-
if (descriptor.startsWith("css:")) {
|
|
3164
|
-
const selector = descriptor.slice("css:".length);
|
|
3165
|
-
return page.locator(selector);
|
|
3166
|
-
}
|
|
3167
|
-
if (descriptor.startsWith("nth:")) {
|
|
3168
|
-
const withoutPrefix = descriptor.slice("nth:".length);
|
|
3169
|
-
const lastColon = withoutPrefix.lastIndexOf(":");
|
|
3170
|
-
const inner = withoutPrefix.slice(0, lastColon);
|
|
3171
|
-
const idx = parseInt(withoutPrefix.slice(lastColon + 1), 10);
|
|
3172
|
-
return this.descriptorToLocator(inner).nth(idx);
|
|
3173
|
-
}
|
|
3174
|
-
return page.locator(descriptor);
|
|
3175
|
-
}
|
|
3176
|
-
async navigate(url, options = {}) {
|
|
3177
|
-
const { waitUntil = "load", timeout = 30000 } = options;
|
|
3178
|
-
await this.page.goto(url, { waitUntil, timeout });
|
|
3179
|
-
}
|
|
3180
|
-
async goBack(options = {}) {
|
|
3181
|
-
const { waitUntil = "load", timeout = 30000 } = options;
|
|
3182
|
-
await this.page.goBack({ waitUntil, timeout });
|
|
3183
|
-
}
|
|
3184
|
-
async goForward(options = {}) {
|
|
3185
|
-
const { waitUntil = "load", timeout = 30000 } = options;
|
|
3186
|
-
await this.page.goForward({ waitUntil, timeout });
|
|
3187
|
-
}
|
|
3188
|
-
async reload(options = {}) {
|
|
3189
|
-
const { waitUntil = "load", timeout = 30000 } = options;
|
|
3190
|
-
await this.page.reload({ waitUntil, timeout });
|
|
3191
|
-
}
|
|
3192
|
-
async currentUrl() {
|
|
3193
|
-
return this.page.url();
|
|
3194
|
-
}
|
|
3195
|
-
async title() {
|
|
3196
|
-
return this.page.title();
|
|
3197
|
-
}
|
|
3198
|
-
async snapshot(options = {}) {
|
|
3199
|
-
let locator;
|
|
3200
|
-
if (options.ref) {
|
|
3201
|
-
locator = this.resolveRef(options.ref);
|
|
3202
|
-
} else {
|
|
3203
|
-
locator = this.page.locator("body");
|
|
3204
|
-
}
|
|
3205
|
-
const yaml = await locator.ariaSnapshot();
|
|
3206
|
-
const root = parseAriaYaml(yaml, this._refCounter, this._refMap);
|
|
3207
|
-
return { root, yaml };
|
|
3208
|
-
}
|
|
3209
|
-
async click(ref, options = {}) {
|
|
3210
|
-
const locator = this.resolveRef(ref);
|
|
3211
|
-
const { modifiers, button, clickCount, timeout } = options;
|
|
3212
|
-
await locator.click({
|
|
3213
|
-
...modifiers !== undefined ? { modifiers } : {},
|
|
3214
|
-
...button !== undefined ? { button } : {},
|
|
3215
|
-
...clickCount !== undefined ? { clickCount } : {},
|
|
3216
|
-
...timeout !== undefined ? { timeout } : {}
|
|
3217
|
-
});
|
|
3218
|
-
}
|
|
3219
|
-
async fill(ref, value, options = {}) {
|
|
3220
|
-
const locator = this.resolveRef(ref);
|
|
3221
|
-
const { timeout } = options;
|
|
3222
|
-
await locator.fill(value, ...timeout !== undefined ? [{ timeout }] : []);
|
|
3223
|
-
}
|
|
3224
|
-
async selectOption(ref, values, options = {}) {
|
|
3225
|
-
const locator = this.resolveRef(ref);
|
|
3226
|
-
const { timeout } = options;
|
|
3227
|
-
await locator.selectOption(values, ...timeout !== undefined ? [{ timeout }] : []);
|
|
3228
|
-
}
|
|
3229
|
-
async hover(ref, options = {}) {
|
|
3230
|
-
const locator = this.resolveRef(ref);
|
|
3231
|
-
const { timeout } = options;
|
|
3232
|
-
await locator.hover(...timeout !== undefined ? [{ timeout }] : []);
|
|
3233
|
-
}
|
|
3234
|
-
async clickByRole(role, name, options = {}) {
|
|
3235
|
-
const { modifiers, button, clickCount, timeout } = options;
|
|
3236
|
-
await this.page.getByRole(role, { name }).click({
|
|
3237
|
-
...modifiers !== undefined ? { modifiers } : {},
|
|
3238
|
-
...button !== undefined ? { button } : {},
|
|
3239
|
-
...clickCount !== undefined ? { clickCount } : {},
|
|
3240
|
-
...timeout !== undefined ? { timeout } : {}
|
|
3241
|
-
});
|
|
3242
|
-
}
|
|
3243
|
-
async fillByLabel(label, value, options = {}) {
|
|
3244
|
-
const { timeout } = options;
|
|
3245
|
-
await this.page.getByLabel(label).fill(value, ...timeout !== undefined ? [{ timeout }] : []);
|
|
3246
|
-
}
|
|
3247
|
-
async content() {
|
|
3248
|
-
return this.page.content();
|
|
3249
|
-
}
|
|
3250
|
-
async innerHTML(ref) {
|
|
3251
|
-
const locator = this.resolveRef(ref);
|
|
3252
|
-
return locator.innerHTML();
|
|
3253
|
-
}
|
|
3254
|
-
async textContent(ref) {
|
|
3255
|
-
const locator = this.resolveRef(ref);
|
|
3256
|
-
return locator.textContent();
|
|
3257
|
-
}
|
|
3258
|
-
async attribute(ref, name) {
|
|
3259
|
-
const locator = this.resolveRef(ref);
|
|
3260
|
-
return locator.getAttribute(name);
|
|
3261
|
-
}
|
|
3262
|
-
async querySelector(selector) {
|
|
3263
|
-
const locator = this.page.locator(selector);
|
|
3264
|
-
const count = await locator.count();
|
|
3265
|
-
if (count === 0)
|
|
3266
|
-
return null;
|
|
3267
|
-
const ref = `e${++this._refCounter.count}`;
|
|
3268
|
-
this._refMap.set(ref, `css:${selector}`);
|
|
3269
|
-
return ref;
|
|
3270
|
-
}
|
|
3271
|
-
async querySelectorAll(selector) {
|
|
3272
|
-
const locator = this.page.locator(selector);
|
|
3273
|
-
const count = await locator.count();
|
|
3274
|
-
const refs = [];
|
|
3275
|
-
for (let i = 0;i < count; i++) {
|
|
3276
|
-
const ref = `e${++this._refCounter.count}`;
|
|
3277
|
-
this._refMap.set(ref, `nth:css:${selector}:${i}`);
|
|
3278
|
-
refs.push(ref);
|
|
3279
|
-
}
|
|
3280
|
-
return refs;
|
|
3281
|
-
}
|
|
3282
|
-
async evaluate(expression) {
|
|
3283
|
-
return this.page.evaluate(expression);
|
|
3284
|
-
}
|
|
3285
|
-
async screenshot(options = {}) {
|
|
3286
|
-
const { format = "png", quality, fullPage = false } = options;
|
|
3287
|
-
const screenshotOptions = {
|
|
3288
|
-
type: format,
|
|
3289
|
-
fullPage
|
|
3290
|
-
};
|
|
3291
|
-
if (format === "jpeg" && quality !== undefined) {
|
|
3292
|
-
screenshotOptions.quality = quality;
|
|
3293
|
-
}
|
|
3294
|
-
return this.page.screenshot(screenshotOptions);
|
|
3295
|
-
}
|
|
3296
|
-
async pressKey(key, _options = {}) {
|
|
3297
|
-
await this.page.keyboard.press(key);
|
|
3298
|
-
}
|
|
3299
|
-
async type(text, _options = {}) {
|
|
3300
|
-
await this.page.keyboard.type(text);
|
|
3301
|
-
}
|
|
3302
|
-
async scroll(x, y, ref) {
|
|
3303
|
-
if (ref) {
|
|
3304
|
-
const locator = this.resolveRef(ref);
|
|
3305
|
-
await locator.evaluate((el, args) => {
|
|
3306
|
-
el.scrollBy(args.x, args.y);
|
|
3307
|
-
}, { x, y });
|
|
3308
|
-
} else {
|
|
3309
|
-
await this.page.mouse.wheel(x, y);
|
|
3310
|
-
}
|
|
3311
|
-
}
|
|
3312
|
-
async uploadFile(ref, paths) {
|
|
3313
|
-
const locator = this.resolveRef(ref);
|
|
3314
|
-
await locator.setInputFiles(paths);
|
|
3315
|
-
}
|
|
3316
|
-
async download(trigger, options = {}) {
|
|
3317
|
-
const { timeout } = options;
|
|
3318
|
-
const [download] = await Promise.all([
|
|
3319
|
-
this.page.waitForEvent("download", ...timeout !== undefined ? [{ timeout }] : []),
|
|
3320
|
-
trigger()
|
|
3321
|
-
]);
|
|
3322
|
-
const path = await download.path();
|
|
3323
|
-
const suggestedFilename = download.suggestedFilename();
|
|
3324
|
-
if (!path) {
|
|
3325
|
-
throw new Error("PlaywrightBackend: download failed — path is null");
|
|
3326
|
-
}
|
|
3327
|
-
return { path, suggestedFilename };
|
|
3328
|
-
}
|
|
3329
|
-
onDialog(handler) {
|
|
3330
|
-
this._dialogHandler = handler;
|
|
3331
|
-
}
|
|
3332
|
-
async tabs() {
|
|
3333
|
-
const pages = this.context.pages();
|
|
3334
|
-
return Promise.all(pages.map(async (p, idx) => ({
|
|
3335
|
-
tabId: String(idx),
|
|
3336
|
-
url: p.url(),
|
|
3337
|
-
title: await p.title()
|
|
3338
|
-
})));
|
|
3339
|
-
}
|
|
3340
|
-
async switchTab(tabId) {
|
|
3341
|
-
const pages = this.context.pages();
|
|
3342
|
-
const idx = parseInt(tabId, 10);
|
|
3343
|
-
if (isNaN(idx) || idx < 0 || idx >= pages.length) {
|
|
3344
|
-
throw new Error(`PlaywrightBackend: no tab with id "${tabId}"`);
|
|
3345
|
-
}
|
|
3346
|
-
this._page = pages[idx];
|
|
3347
|
-
await this._page.bringToFront();
|
|
3348
|
-
}
|
|
3349
|
-
async newTab(url) {
|
|
3350
|
-
const newPage = await this.context.newPage();
|
|
3351
|
-
if (url) {
|
|
3352
|
-
await newPage.goto(url, { waitUntil: "load" });
|
|
3353
|
-
}
|
|
3354
|
-
const pages = this.context.pages();
|
|
3355
|
-
const idx = pages.indexOf(newPage);
|
|
3356
|
-
const tabId = String(idx >= 0 ? idx : pages.length - 1);
|
|
3357
|
-
return {
|
|
3358
|
-
tabId,
|
|
3359
|
-
url: newPage.url(),
|
|
3360
|
-
title: await newPage.title()
|
|
3361
|
-
};
|
|
3362
|
-
}
|
|
3363
|
-
async closeTab(tabId) {
|
|
3364
|
-
const pages = this.context.pages();
|
|
3365
|
-
const idx = parseInt(tabId, 10);
|
|
3366
|
-
if (isNaN(idx) || idx < 0 || idx >= pages.length) {
|
|
3367
|
-
throw new Error(`PlaywrightBackend: no tab with id "${tabId}"`);
|
|
3368
|
-
}
|
|
3369
|
-
const target = pages[idx];
|
|
3370
|
-
await target.close();
|
|
3371
|
-
if (this._page === target) {
|
|
3372
|
-
const remaining = this.context.pages();
|
|
3373
|
-
this._page = remaining.length > 0 ? remaining[remaining.length - 1] : null;
|
|
3374
|
-
}
|
|
3375
|
-
}
|
|
3376
|
-
async waitForNavigation(options = {}) {
|
|
3377
|
-
const { timeout } = options;
|
|
3378
|
-
await this.page.waitForLoadState("load", ...timeout !== undefined ? [{ timeout }] : []);
|
|
3379
|
-
}
|
|
3380
|
-
async waitForSelector(selector, options = {}) {
|
|
3381
|
-
const { timeout } = options;
|
|
3382
|
-
await this.page.waitForSelector(selector, ...timeout !== undefined ? [{ timeout }] : []);
|
|
3383
|
-
const ref = `e${++this._refCounter.count}`;
|
|
3384
|
-
this._refMap.set(ref, `css:${selector}`);
|
|
3385
|
-
return ref;
|
|
3386
|
-
}
|
|
3387
|
-
async waitForIdle(options = {}) {
|
|
3388
|
-
const { timeout } = options;
|
|
3389
|
-
await this.page.waitForLoadState("networkidle", ...timeout !== undefined ? [{ timeout }] : []);
|
|
3390
|
-
}
|
|
3391
|
-
networkRequests = (_filter) => {
|
|
3392
|
-
return Promise.resolve([]);
|
|
3393
|
-
};
|
|
3394
|
-
consoleMessages = () => {
|
|
3395
|
-
return Promise.resolve([]);
|
|
3396
|
-
};
|
|
3397
|
-
}
|
|
3398
2445
|
export {
|
|
3399
2446
|
registerBrowserDeps,
|
|
3400
2447
|
getBrowserDeps,
|
|
3401
|
-
PlaywrightBackend,
|
|
3402
|
-
ElectronBackend,
|
|
3403
2448
|
CDPBrowserBackend,
|
|
3404
|
-
BunWebViewBackend,
|
|
3405
2449
|
BrowserWaitTask,
|
|
3406
2450
|
BrowserUploadTask,
|
|
3407
2451
|
BrowserTypeTask,
|
|
@@ -3432,4 +2476,4 @@ export {
|
|
|
3432
2476
|
BROWSER_CONTROL_TASK_DEPS
|
|
3433
2477
|
};
|
|
3434
2478
|
|
|
3435
|
-
//# debugId=
|
|
2479
|
+
//# debugId=BB75C3F860F618D964756E2164756E21
|