@hera-al/browser-server 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +212 -0
  3. package/dist/config.d.ts +44 -0
  4. package/dist/config.js +74 -0
  5. package/dist/core/cdp.d.ts +124 -0
  6. package/dist/core/cdp.helpers.d.ts +14 -0
  7. package/dist/core/cdp.helpers.js +148 -0
  8. package/dist/core/cdp.js +309 -0
  9. package/dist/core/chrome.d.ts +21 -0
  10. package/dist/core/chrome.executables.d.ts +10 -0
  11. package/dist/core/chrome.executables.js +559 -0
  12. package/dist/core/chrome.js +257 -0
  13. package/dist/core/chrome.profile-decoration.d.ts +11 -0
  14. package/dist/core/chrome.profile-decoration.js +148 -0
  15. package/dist/core/constants.d.ts +9 -0
  16. package/dist/core/constants.js +9 -0
  17. package/dist/core/profiles.d.ts +31 -0
  18. package/dist/core/profiles.js +99 -0
  19. package/dist/core/target-id.d.ts +12 -0
  20. package/dist/core/target-id.js +21 -0
  21. package/dist/data-dir.d.ts +2 -0
  22. package/dist/data-dir.js +6 -0
  23. package/dist/logger.d.ts +16 -0
  24. package/dist/logger.js +125 -0
  25. package/dist/playwright/pw-role-snapshot.d.ts +32 -0
  26. package/dist/playwright/pw-role-snapshot.js +337 -0
  27. package/dist/playwright/pw-session.d.ts +119 -0
  28. package/dist/playwright/pw-session.js +530 -0
  29. package/dist/playwright/pw-tools-core.activity.d.ts +22 -0
  30. package/dist/playwright/pw-tools-core.activity.js +47 -0
  31. package/dist/playwright/pw-tools-core.d.ts +9 -0
  32. package/dist/playwright/pw-tools-core.downloads.d.ts +35 -0
  33. package/dist/playwright/pw-tools-core.downloads.js +186 -0
  34. package/dist/playwright/pw-tools-core.interactions.d.ts +104 -0
  35. package/dist/playwright/pw-tools-core.interactions.js +404 -0
  36. package/dist/playwright/pw-tools-core.js +9 -0
  37. package/dist/playwright/pw-tools-core.responses.d.ts +14 -0
  38. package/dist/playwright/pw-tools-core.responses.js +91 -0
  39. package/dist/playwright/pw-tools-core.shared.d.ts +7 -0
  40. package/dist/playwright/pw-tools-core.shared.js +50 -0
  41. package/dist/playwright/pw-tools-core.snapshot.d.ts +65 -0
  42. package/dist/playwright/pw-tools-core.snapshot.js +144 -0
  43. package/dist/playwright/pw-tools-core.state.d.ts +47 -0
  44. package/dist/playwright/pw-tools-core.state.js +154 -0
  45. package/dist/playwright/pw-tools-core.storage.d.ts +48 -0
  46. package/dist/playwright/pw-tools-core.storage.js +76 -0
  47. package/dist/playwright/pw-tools-core.trace.d.ts +13 -0
  48. package/dist/playwright/pw-tools-core.trace.js +26 -0
  49. package/dist/server/browser-context.d.ts +29 -0
  50. package/dist/server/browser-context.js +137 -0
  51. package/dist/server/browser-server.d.ts +7 -0
  52. package/dist/server/browser-server.js +49 -0
  53. package/dist/server/routes/act.d.ts +4 -0
  54. package/dist/server/routes/act.js +176 -0
  55. package/dist/server/routes/basic.d.ts +4 -0
  56. package/dist/server/routes/basic.js +36 -0
  57. package/dist/server/routes/index.d.ts +4 -0
  58. package/dist/server/routes/index.js +16 -0
  59. package/dist/server/routes/snapshot.d.ts +4 -0
  60. package/dist/server/routes/snapshot.js +143 -0
  61. package/dist/server/routes/storage.d.ts +4 -0
  62. package/dist/server/routes/storage.js +117 -0
  63. package/dist/server/routes/tabs.d.ts +4 -0
  64. package/dist/server/routes/tabs.js +51 -0
  65. package/dist/server/standalone.d.ts +9 -0
  66. package/dist/server/standalone.js +42 -0
  67. package/dist/utils.d.ts +18 -0
  68. package/dist/utils.js +58 -0
  69. package/package.json +66 -0
@@ -0,0 +1,186 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import os from "node:os";
4
+ import path from "node:path";
5
+ import { ensurePageState, getPageForTargetId, refLocator, restoreRoleRefsForTarget, } from "./pw-session.js";
6
+ import { bumpDialogArmId, bumpDownloadArmId, bumpUploadArmId, normalizeTimeoutMs, requireRef, toAIFriendlyError, } from "./pw-tools-core.shared.js";
7
+ function buildTempDownloadPath(fileName) {
8
+ const id = crypto.randomUUID();
9
+ const safeName = fileName.trim() ? fileName.trim() : "download.bin";
10
+ return path.join(os.tmpdir(), "grabmeabeer", "downloads", `${id}-${safeName}`);
11
+ }
12
+ function createPageDownloadWaiter(page, timeoutMs) {
13
+ let done = false;
14
+ let timer;
15
+ let handler;
16
+ const cleanup = () => {
17
+ if (timer) {
18
+ clearTimeout(timer);
19
+ }
20
+ timer = undefined;
21
+ if (handler) {
22
+ page.off("download", handler);
23
+ handler = undefined;
24
+ }
25
+ };
26
+ const promise = new Promise((resolve, reject) => {
27
+ handler = (download) => {
28
+ if (done) {
29
+ return;
30
+ }
31
+ done = true;
32
+ cleanup();
33
+ resolve(download);
34
+ };
35
+ page.on("download", handler);
36
+ timer = setTimeout(() => {
37
+ if (done) {
38
+ return;
39
+ }
40
+ done = true;
41
+ cleanup();
42
+ reject(new Error("Timeout waiting for download"));
43
+ }, timeoutMs);
44
+ });
45
+ return {
46
+ promise,
47
+ cancel: () => {
48
+ if (done) {
49
+ return;
50
+ }
51
+ done = true;
52
+ cleanup();
53
+ },
54
+ };
55
+ }
56
+ export async function armFileUploadViaPlaywright(opts) {
57
+ const page = await getPageForTargetId(opts);
58
+ const state = ensurePageState(page);
59
+ const timeout = Math.max(500, Math.min(120_000, opts.timeoutMs ?? 120_000));
60
+ state.armIdUpload = bumpUploadArmId();
61
+ const armId = state.armIdUpload;
62
+ void page
63
+ .waitForEvent("filechooser", { timeout })
64
+ .then(async (fileChooser) => {
65
+ if (state.armIdUpload !== armId) {
66
+ return;
67
+ }
68
+ if (!opts.paths?.length) {
69
+ try {
70
+ await page.keyboard.press("Escape");
71
+ }
72
+ catch {
73
+ // Best-effort.
74
+ }
75
+ return;
76
+ }
77
+ await fileChooser.setFiles(opts.paths);
78
+ try {
79
+ const input = typeof fileChooser.element === "function"
80
+ ? await Promise.resolve(fileChooser.element())
81
+ : null;
82
+ if (input) {
83
+ await input.evaluate((el) => {
84
+ el.dispatchEvent(new Event("input", { bubbles: true }));
85
+ el.dispatchEvent(new Event("change", { bubbles: true }));
86
+ });
87
+ }
88
+ }
89
+ catch {
90
+ // Best-effort
91
+ }
92
+ })
93
+ .catch(() => {
94
+ // Ignore timeouts
95
+ });
96
+ }
97
+ export async function armDialogViaPlaywright(opts) {
98
+ const page = await getPageForTargetId(opts);
99
+ const state = ensurePageState(page);
100
+ const timeout = normalizeTimeoutMs(opts.timeoutMs, 120_000);
101
+ state.armIdDialog = bumpDialogArmId();
102
+ const armId = state.armIdDialog;
103
+ void page
104
+ .waitForEvent("dialog", { timeout })
105
+ .then(async (dialog) => {
106
+ if (state.armIdDialog !== armId) {
107
+ return;
108
+ }
109
+ if (opts.accept) {
110
+ await dialog.accept(opts.promptText);
111
+ }
112
+ else {
113
+ await dialog.dismiss();
114
+ }
115
+ })
116
+ .catch(() => {
117
+ // Ignore timeouts
118
+ });
119
+ }
120
+ export async function waitForDownloadViaPlaywright(opts) {
121
+ const page = await getPageForTargetId(opts);
122
+ const state = ensurePageState(page);
123
+ const timeout = normalizeTimeoutMs(opts.timeoutMs, 120_000);
124
+ state.armIdDownload = bumpDownloadArmId();
125
+ const armId = state.armIdDownload;
126
+ const waiter = createPageDownloadWaiter(page, timeout);
127
+ try {
128
+ const download = (await waiter.promise);
129
+ if (state.armIdDownload !== armId) {
130
+ throw new Error("Download was superseded by another waiter");
131
+ }
132
+ const suggested = download.suggestedFilename?.() || "download.bin";
133
+ const outPath = opts.path?.trim() || buildTempDownloadPath(suggested);
134
+ await fs.mkdir(path.dirname(outPath), { recursive: true });
135
+ await download.saveAs?.(outPath);
136
+ return {
137
+ url: download.url?.() || "",
138
+ suggestedFilename: suggested,
139
+ path: path.resolve(outPath),
140
+ };
141
+ }
142
+ catch (err) {
143
+ waiter.cancel();
144
+ throw err;
145
+ }
146
+ }
147
+ export async function downloadViaPlaywright(opts) {
148
+ const page = await getPageForTargetId(opts);
149
+ const state = ensurePageState(page);
150
+ restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
151
+ const timeout = normalizeTimeoutMs(opts.timeoutMs, 120_000);
152
+ const ref = requireRef(opts.ref);
153
+ const outPath = String(opts.path ?? "").trim();
154
+ if (!outPath) {
155
+ throw new Error("path is required");
156
+ }
157
+ state.armIdDownload = bumpDownloadArmId();
158
+ const armId = state.armIdDownload;
159
+ const waiter = createPageDownloadWaiter(page, timeout);
160
+ try {
161
+ const locator = refLocator(page, ref);
162
+ try {
163
+ await locator.click({ timeout });
164
+ }
165
+ catch (err) {
166
+ throw toAIFriendlyError(err, ref);
167
+ }
168
+ const download = (await waiter.promise);
169
+ if (state.armIdDownload !== armId) {
170
+ throw new Error("Download was superseded by another waiter");
171
+ }
172
+ const suggested = download.suggestedFilename?.() || "download.bin";
173
+ await fs.mkdir(path.dirname(outPath), { recursive: true });
174
+ await download.saveAs?.(outPath);
175
+ return {
176
+ url: download.url?.() || "",
177
+ suggestedFilename: suggested,
178
+ path: path.resolve(outPath),
179
+ };
180
+ }
181
+ catch (err) {
182
+ waiter.cancel();
183
+ throw err;
184
+ }
185
+ }
186
+ //# sourceMappingURL=pw-tools-core.downloads.js.map
@@ -0,0 +1,104 @@
1
+ export type BrowserFormField = {
2
+ ref: string;
3
+ type: string;
4
+ value?: string | number | boolean;
5
+ };
6
+ export declare function highlightViaPlaywright(opts: {
7
+ cdpUrl: string;
8
+ targetId?: string;
9
+ ref: string;
10
+ }): Promise<void>;
11
+ export declare function clickViaPlaywright(opts: {
12
+ cdpUrl: string;
13
+ targetId?: string;
14
+ ref: string;
15
+ doubleClick?: boolean;
16
+ button?: "left" | "right" | "middle";
17
+ modifiers?: Array<"Alt" | "Control" | "ControlOrMeta" | "Meta" | "Shift">;
18
+ timeoutMs?: number;
19
+ }): Promise<void>;
20
+ export declare function hoverViaPlaywright(opts: {
21
+ cdpUrl: string;
22
+ targetId?: string;
23
+ ref: string;
24
+ timeoutMs?: number;
25
+ }): Promise<void>;
26
+ export declare function dragViaPlaywright(opts: {
27
+ cdpUrl: string;
28
+ targetId?: string;
29
+ startRef: string;
30
+ endRef: string;
31
+ timeoutMs?: number;
32
+ }): Promise<void>;
33
+ export declare function selectOptionViaPlaywright(opts: {
34
+ cdpUrl: string;
35
+ targetId?: string;
36
+ ref: string;
37
+ values: string[];
38
+ timeoutMs?: number;
39
+ }): Promise<void>;
40
+ export declare function pressKeyViaPlaywright(opts: {
41
+ cdpUrl: string;
42
+ targetId?: string;
43
+ key: string;
44
+ delayMs?: number;
45
+ }): Promise<void>;
46
+ export declare function typeViaPlaywright(opts: {
47
+ cdpUrl: string;
48
+ targetId?: string;
49
+ ref: string;
50
+ text: string;
51
+ submit?: boolean;
52
+ slowly?: boolean;
53
+ timeoutMs?: number;
54
+ }): Promise<void>;
55
+ export declare function fillFormViaPlaywright(opts: {
56
+ cdpUrl: string;
57
+ targetId?: string;
58
+ fields: BrowserFormField[];
59
+ timeoutMs?: number;
60
+ }): Promise<void>;
61
+ export declare function evaluateViaPlaywright(opts: {
62
+ cdpUrl: string;
63
+ targetId?: string;
64
+ fn: string;
65
+ ref?: string;
66
+ timeoutMs?: number;
67
+ signal?: AbortSignal;
68
+ }): Promise<unknown>;
69
+ export declare function scrollIntoViewViaPlaywright(opts: {
70
+ cdpUrl: string;
71
+ targetId?: string;
72
+ ref: string;
73
+ timeoutMs?: number;
74
+ }): Promise<void>;
75
+ export declare function waitForViaPlaywright(opts: {
76
+ cdpUrl: string;
77
+ targetId?: string;
78
+ timeMs?: number;
79
+ text?: string;
80
+ textGone?: string;
81
+ selector?: string;
82
+ url?: string;
83
+ loadState?: "load" | "domcontentloaded" | "networkidle";
84
+ fn?: string;
85
+ timeoutMs?: number;
86
+ }): Promise<void>;
87
+ export declare function takeScreenshotViaPlaywright(opts: {
88
+ cdpUrl: string;
89
+ targetId?: string;
90
+ ref?: string;
91
+ element?: string;
92
+ fullPage?: boolean;
93
+ type?: "png" | "jpeg";
94
+ }): Promise<{
95
+ buffer: Buffer;
96
+ }>;
97
+ export declare function setInputFilesViaPlaywright(opts: {
98
+ cdpUrl: string;
99
+ targetId?: string;
100
+ inputRef?: string;
101
+ element?: string;
102
+ paths: string[];
103
+ }): Promise<void>;
104
+ //# sourceMappingURL=pw-tools-core.interactions.d.ts.map
@@ -0,0 +1,404 @@
1
+ import { ensurePageState, forceDisconnectPlaywrightForTarget, getPageForTargetId, refLocator, restoreRoleRefsForTarget, } from "./pw-session.js";
2
+ import { normalizeTimeoutMs, requireRef, toAIFriendlyError } from "./pw-tools-core.shared.js";
3
+ export async function highlightViaPlaywright(opts) {
4
+ const page = await getPageForTargetId(opts);
5
+ ensurePageState(page);
6
+ restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
7
+ const ref = requireRef(opts.ref);
8
+ try {
9
+ await refLocator(page, ref).highlight();
10
+ }
11
+ catch (err) {
12
+ throw toAIFriendlyError(err, ref);
13
+ }
14
+ }
15
+ export async function clickViaPlaywright(opts) {
16
+ const page = await getPageForTargetId({
17
+ cdpUrl: opts.cdpUrl,
18
+ targetId: opts.targetId,
19
+ });
20
+ ensurePageState(page);
21
+ restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
22
+ const ref = requireRef(opts.ref);
23
+ const locator = refLocator(page, ref);
24
+ const timeout = Math.max(500, Math.min(60_000, Math.floor(opts.timeoutMs ?? 8000)));
25
+ try {
26
+ if (opts.doubleClick) {
27
+ await locator.dblclick({
28
+ timeout,
29
+ button: opts.button,
30
+ modifiers: opts.modifiers,
31
+ });
32
+ }
33
+ else {
34
+ await locator.click({
35
+ timeout,
36
+ button: opts.button,
37
+ modifiers: opts.modifiers,
38
+ });
39
+ }
40
+ }
41
+ catch (err) {
42
+ throw toAIFriendlyError(err, ref);
43
+ }
44
+ }
45
+ export async function hoverViaPlaywright(opts) {
46
+ const ref = requireRef(opts.ref);
47
+ const page = await getPageForTargetId(opts);
48
+ ensurePageState(page);
49
+ restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
50
+ try {
51
+ await refLocator(page, ref).hover({
52
+ timeout: Math.max(500, Math.min(60_000, opts.timeoutMs ?? 8000)),
53
+ });
54
+ }
55
+ catch (err) {
56
+ throw toAIFriendlyError(err, ref);
57
+ }
58
+ }
59
+ export async function dragViaPlaywright(opts) {
60
+ const startRef = requireRef(opts.startRef);
61
+ const endRef = requireRef(opts.endRef);
62
+ if (!startRef || !endRef) {
63
+ throw new Error("startRef and endRef are required");
64
+ }
65
+ const page = await getPageForTargetId(opts);
66
+ ensurePageState(page);
67
+ restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
68
+ try {
69
+ await refLocator(page, startRef).dragTo(refLocator(page, endRef), {
70
+ timeout: Math.max(500, Math.min(60_000, opts.timeoutMs ?? 8000)),
71
+ });
72
+ }
73
+ catch (err) {
74
+ throw toAIFriendlyError(err, `${startRef} -> ${endRef}`);
75
+ }
76
+ }
77
+ export async function selectOptionViaPlaywright(opts) {
78
+ const ref = requireRef(opts.ref);
79
+ if (!opts.values?.length) {
80
+ throw new Error("values are required");
81
+ }
82
+ const page = await getPageForTargetId(opts);
83
+ ensurePageState(page);
84
+ restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
85
+ try {
86
+ await refLocator(page, ref).selectOption(opts.values, {
87
+ timeout: Math.max(500, Math.min(60_000, opts.timeoutMs ?? 8000)),
88
+ });
89
+ }
90
+ catch (err) {
91
+ throw toAIFriendlyError(err, ref);
92
+ }
93
+ }
94
+ export async function pressKeyViaPlaywright(opts) {
95
+ const key = String(opts.key ?? "").trim();
96
+ if (!key) {
97
+ throw new Error("key is required");
98
+ }
99
+ const page = await getPageForTargetId(opts);
100
+ ensurePageState(page);
101
+ await page.keyboard.press(key, {
102
+ delay: Math.max(0, Math.floor(opts.delayMs ?? 0)),
103
+ });
104
+ }
105
+ export async function typeViaPlaywright(opts) {
106
+ const text = String(opts.text ?? "");
107
+ const page = await getPageForTargetId(opts);
108
+ ensurePageState(page);
109
+ restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
110
+ const ref = requireRef(opts.ref);
111
+ const locator = refLocator(page, ref);
112
+ const timeout = Math.max(500, Math.min(60_000, opts.timeoutMs ?? 8000));
113
+ try {
114
+ if (opts.slowly) {
115
+ await locator.click({ timeout });
116
+ await locator.type(text, { timeout, delay: 75 });
117
+ }
118
+ else {
119
+ await locator.fill(text, { timeout });
120
+ }
121
+ if (opts.submit) {
122
+ await locator.press("Enter", { timeout });
123
+ }
124
+ }
125
+ catch (err) {
126
+ throw toAIFriendlyError(err, ref);
127
+ }
128
+ }
129
+ export async function fillFormViaPlaywright(opts) {
130
+ const page = await getPageForTargetId(opts);
131
+ ensurePageState(page);
132
+ restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
133
+ const timeout = Math.max(500, Math.min(60_000, opts.timeoutMs ?? 8000));
134
+ for (const field of opts.fields) {
135
+ const ref = field.ref.trim();
136
+ const type = field.type.trim();
137
+ const rawValue = field.value;
138
+ const value = typeof rawValue === "string"
139
+ ? rawValue
140
+ : typeof rawValue === "number" || typeof rawValue === "boolean"
141
+ ? String(rawValue)
142
+ : "";
143
+ if (!ref || !type) {
144
+ continue;
145
+ }
146
+ const locator = refLocator(page, ref);
147
+ if (type === "checkbox" || type === "radio") {
148
+ const checked = rawValue === true || rawValue === 1 || rawValue === "1" || rawValue === "true";
149
+ try {
150
+ await locator.setChecked(checked, { timeout });
151
+ }
152
+ catch (err) {
153
+ throw toAIFriendlyError(err, ref);
154
+ }
155
+ continue;
156
+ }
157
+ try {
158
+ await locator.fill(value, { timeout });
159
+ }
160
+ catch (err) {
161
+ throw toAIFriendlyError(err, ref);
162
+ }
163
+ }
164
+ }
165
+ export async function evaluateViaPlaywright(opts) {
166
+ const fnText = String(opts.fn ?? "").trim();
167
+ if (!fnText) {
168
+ throw new Error("function is required");
169
+ }
170
+ const page = await getPageForTargetId(opts);
171
+ ensurePageState(page);
172
+ restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
173
+ const outerTimeout = normalizeTimeoutMs(opts.timeoutMs, 20_000);
174
+ let evaluateTimeout = Math.max(1000, Math.min(120_000, outerTimeout - 500));
175
+ evaluateTimeout = Math.min(evaluateTimeout, outerTimeout);
176
+ const signal = opts.signal;
177
+ let abortListener;
178
+ let abortReject;
179
+ let abortPromise;
180
+ if (signal) {
181
+ abortPromise = new Promise((_, reject) => {
182
+ abortReject = reject;
183
+ });
184
+ void abortPromise.catch(() => { });
185
+ }
186
+ if (signal) {
187
+ const disconnect = () => {
188
+ void forceDisconnectPlaywrightForTarget({
189
+ cdpUrl: opts.cdpUrl,
190
+ targetId: opts.targetId,
191
+ reason: "evaluate aborted",
192
+ }).catch(() => { });
193
+ };
194
+ if (signal.aborted) {
195
+ disconnect();
196
+ throw signal.reason ?? new Error("aborted");
197
+ }
198
+ abortListener = () => {
199
+ disconnect();
200
+ abortReject?.(signal.reason ?? new Error("aborted"));
201
+ };
202
+ signal.addEventListener("abort", abortListener, { once: true });
203
+ if (signal.aborted) {
204
+ abortListener();
205
+ throw signal.reason ?? new Error("aborted");
206
+ }
207
+ }
208
+ try {
209
+ if (opts.ref) {
210
+ const locator = refLocator(page, opts.ref);
211
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
212
+ const elementEvaluator = new Function("el", "args", `
213
+ "use strict";
214
+ var fnBody = args.fnBody, timeoutMs = args.timeoutMs;
215
+ try {
216
+ var candidate = eval("(" + fnBody + ")");
217
+ var result = typeof candidate === "function" ? candidate(el) : candidate;
218
+ if (result && typeof result.then === "function") {
219
+ return Promise.race([
220
+ result,
221
+ new Promise(function(_, reject) {
222
+ setTimeout(function() { reject(new Error("evaluate timed out after " + timeoutMs + "ms")); }, timeoutMs);
223
+ })
224
+ ]);
225
+ }
226
+ return result;
227
+ } catch (err) {
228
+ throw new Error("Invalid evaluate function: " + (err && err.message ? err.message : String(err)));
229
+ }
230
+ `);
231
+ const evalPromise = locator.evaluate(elementEvaluator, {
232
+ fnBody: fnText,
233
+ timeoutMs: evaluateTimeout,
234
+ });
235
+ if (!abortPromise) {
236
+ return await evalPromise;
237
+ }
238
+ try {
239
+ return await Promise.race([evalPromise, abortPromise]);
240
+ }
241
+ catch (err) {
242
+ void evalPromise.catch(() => { });
243
+ throw err;
244
+ }
245
+ }
246
+ // eslint-disable-next-line @typescript-eslint/no-implied-eval
247
+ const browserEvaluator = new Function("args", `
248
+ "use strict";
249
+ var fnBody = args.fnBody, timeoutMs = args.timeoutMs;
250
+ try {
251
+ var candidate = eval("(" + fnBody + ")");
252
+ var result = typeof candidate === "function" ? candidate() : candidate;
253
+ if (result && typeof result.then === "function") {
254
+ return Promise.race([
255
+ result,
256
+ new Promise(function(_, reject) {
257
+ setTimeout(function() { reject(new Error("evaluate timed out after " + timeoutMs + "ms")); }, timeoutMs);
258
+ })
259
+ ]);
260
+ }
261
+ return result;
262
+ } catch (err) {
263
+ throw new Error("Invalid evaluate function: " + (err && err.message ? err.message : String(err)));
264
+ }
265
+ `);
266
+ const evalPromise = page.evaluate(browserEvaluator, {
267
+ fnBody: fnText,
268
+ timeoutMs: evaluateTimeout,
269
+ });
270
+ if (!abortPromise) {
271
+ return await evalPromise;
272
+ }
273
+ try {
274
+ return await Promise.race([evalPromise, abortPromise]);
275
+ }
276
+ catch (err) {
277
+ void evalPromise.catch(() => { });
278
+ throw err;
279
+ }
280
+ }
281
+ finally {
282
+ if (signal && abortListener) {
283
+ signal.removeEventListener("abort", abortListener);
284
+ }
285
+ }
286
+ }
287
+ export async function scrollIntoViewViaPlaywright(opts) {
288
+ const page = await getPageForTargetId(opts);
289
+ ensurePageState(page);
290
+ restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
291
+ const timeout = normalizeTimeoutMs(opts.timeoutMs, 20_000);
292
+ const ref = requireRef(opts.ref);
293
+ const locator = refLocator(page, ref);
294
+ try {
295
+ await locator.scrollIntoViewIfNeeded({ timeout });
296
+ }
297
+ catch (err) {
298
+ throw toAIFriendlyError(err, ref);
299
+ }
300
+ }
301
+ export async function waitForViaPlaywright(opts) {
302
+ const page = await getPageForTargetId(opts);
303
+ ensurePageState(page);
304
+ const timeout = normalizeTimeoutMs(opts.timeoutMs, 20_000);
305
+ if (typeof opts.timeMs === "number" && Number.isFinite(opts.timeMs)) {
306
+ await page.waitForTimeout(Math.max(0, opts.timeMs));
307
+ }
308
+ if (opts.text) {
309
+ await page.getByText(opts.text).first().waitFor({
310
+ state: "visible",
311
+ timeout,
312
+ });
313
+ }
314
+ if (opts.textGone) {
315
+ await page.getByText(opts.textGone).first().waitFor({
316
+ state: "hidden",
317
+ timeout,
318
+ });
319
+ }
320
+ if (opts.selector) {
321
+ const selector = String(opts.selector).trim();
322
+ if (selector) {
323
+ await page.locator(selector).first().waitFor({ state: "visible", timeout });
324
+ }
325
+ }
326
+ if (opts.url) {
327
+ const url = String(opts.url).trim();
328
+ if (url) {
329
+ await page.waitForURL(url, { timeout });
330
+ }
331
+ }
332
+ if (opts.loadState) {
333
+ await page.waitForLoadState(opts.loadState, { timeout });
334
+ }
335
+ if (opts.fn) {
336
+ const fn = String(opts.fn).trim();
337
+ if (fn) {
338
+ await page.waitForFunction(fn, { timeout });
339
+ }
340
+ }
341
+ }
342
+ export async function takeScreenshotViaPlaywright(opts) {
343
+ const page = await getPageForTargetId(opts);
344
+ ensurePageState(page);
345
+ restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
346
+ const type = opts.type ?? "png";
347
+ if (opts.ref) {
348
+ if (opts.fullPage) {
349
+ throw new Error("fullPage is not supported for element screenshots");
350
+ }
351
+ const locator = refLocator(page, opts.ref);
352
+ const buffer = await locator.screenshot({ type });
353
+ return { buffer };
354
+ }
355
+ if (opts.element) {
356
+ if (opts.fullPage) {
357
+ throw new Error("fullPage is not supported for element screenshots");
358
+ }
359
+ const locator = page.locator(opts.element).first();
360
+ const buffer = await locator.screenshot({ type });
361
+ return { buffer };
362
+ }
363
+ const buffer = await page.screenshot({
364
+ type,
365
+ fullPage: Boolean(opts.fullPage),
366
+ });
367
+ return { buffer };
368
+ }
369
+ export async function setInputFilesViaPlaywright(opts) {
370
+ const page = await getPageForTargetId(opts);
371
+ ensurePageState(page);
372
+ restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
373
+ if (!opts.paths.length) {
374
+ throw new Error("paths are required");
375
+ }
376
+ const inputRef = typeof opts.inputRef === "string" ? opts.inputRef.trim() : "";
377
+ const element = typeof opts.element === "string" ? opts.element.trim() : "";
378
+ if (inputRef && element) {
379
+ throw new Error("inputRef and element are mutually exclusive");
380
+ }
381
+ if (!inputRef && !element) {
382
+ throw new Error("inputRef or element is required");
383
+ }
384
+ const locator = inputRef ? refLocator(page, inputRef) : page.locator(element).first();
385
+ try {
386
+ await locator.setInputFiles(opts.paths);
387
+ }
388
+ catch (err) {
389
+ throw toAIFriendlyError(err, inputRef || element);
390
+ }
391
+ try {
392
+ const handle = await locator.elementHandle();
393
+ if (handle) {
394
+ await handle.evaluate((el) => {
395
+ el.dispatchEvent(new Event("input", { bubbles: true }));
396
+ el.dispatchEvent(new Event("change", { bubbles: true }));
397
+ });
398
+ }
399
+ }
400
+ catch {
401
+ // Best-effort
402
+ }
403
+ }
404
+ //# sourceMappingURL=pw-tools-core.interactions.js.map
@@ -0,0 +1,9 @@
1
+ export * from "./pw-tools-core.activity.js";
2
+ export * from "./pw-tools-core.downloads.js";
3
+ export * from "./pw-tools-core.interactions.js";
4
+ export * from "./pw-tools-core.responses.js";
5
+ export * from "./pw-tools-core.snapshot.js";
6
+ export * from "./pw-tools-core.state.js";
7
+ export * from "./pw-tools-core.storage.js";
8
+ export * from "./pw-tools-core.trace.js";
9
+ //# sourceMappingURL=pw-tools-core.js.map