tuna-agent 0.1.1 → 0.1.3
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/dist/browser/actions/download.d.ts +16 -0
- package/dist/browser/actions/download.js +39 -0
- package/dist/browser/actions/emulation.d.ts +53 -0
- package/dist/browser/actions/emulation.js +103 -0
- package/dist/browser/actions/evaluate.d.ts +29 -0
- package/dist/browser/actions/evaluate.js +92 -0
- package/dist/browser/actions/interaction.d.ts +79 -0
- package/dist/browser/actions/interaction.js +210 -0
- package/dist/browser/actions/keyboard.d.ts +6 -0
- package/dist/browser/actions/keyboard.js +9 -0
- package/dist/browser/actions/navigation.d.ts +40 -0
- package/dist/browser/actions/navigation.js +92 -0
- package/dist/browser/actions/wait.d.ts +12 -0
- package/dist/browser/actions/wait.js +33 -0
- package/dist/browser/browser.d.ts +722 -0
- package/dist/browser/browser.js +1066 -0
- package/dist/browser/capture/activity.d.ts +22 -0
- package/dist/browser/capture/activity.js +39 -0
- package/dist/browser/capture/pdf.d.ts +6 -0
- package/dist/browser/capture/pdf.js +6 -0
- package/dist/browser/capture/response.d.ts +8 -0
- package/dist/browser/capture/response.js +28 -0
- package/dist/browser/capture/screenshot.d.ts +30 -0
- package/dist/browser/capture/screenshot.js +72 -0
- package/dist/browser/capture/trace.d.ts +13 -0
- package/dist/browser/capture/trace.js +19 -0
- package/dist/browser/chrome-launcher.d.ts +8 -0
- package/dist/browser/chrome-launcher.js +543 -0
- package/dist/browser/connection.d.ts +42 -0
- package/dist/browser/connection.js +359 -0
- package/dist/browser/index.d.ts +6 -0
- package/dist/browser/index.js +3 -0
- package/dist/browser/security.d.ts +51 -0
- package/dist/browser/security.js +357 -0
- package/dist/browser/snapshot/ai-snapshot.d.ts +12 -0
- package/dist/browser/snapshot/ai-snapshot.js +47 -0
- package/dist/browser/snapshot/aria-snapshot.d.ts +26 -0
- package/dist/browser/snapshot/aria-snapshot.js +121 -0
- package/dist/browser/snapshot/ref-map.d.ts +31 -0
- package/dist/browser/snapshot/ref-map.js +250 -0
- package/dist/browser/storage/index.d.ts +36 -0
- package/dist/browser/storage/index.js +65 -0
- package/dist/browser/types.d.ts +429 -0
- package/dist/browser/types.js +2 -0
- package/dist/cli/index.js +7 -1
- package/dist/daemon/extension-handlers.d.ts +63 -0
- package/dist/daemon/extension-handlers.js +630 -0
- package/dist/daemon/index.js +78 -19
- package/dist/daemon/ws-client.d.ts +16 -0
- package/dist/daemon/ws-client.js +45 -0
- package/dist/mcp/browser-server.d.ts +11 -0
- package/dist/mcp/browser-server.js +467 -0
- package/dist/mcp/knowledge-server.js +43 -18
- package/dist/mcp/setup.js +10 -0
- package/dist/utils/claude-cli.js +18 -9
- package/package.json +2 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { DownloadResult } from '../types.js';
|
|
2
|
+
export declare function downloadViaPlaywright(opts: {
|
|
3
|
+
cdpUrl: string;
|
|
4
|
+
targetId?: string;
|
|
5
|
+
ref: string;
|
|
6
|
+
path: string;
|
|
7
|
+
timeoutMs?: number;
|
|
8
|
+
allowedOutputRoots?: string[];
|
|
9
|
+
}): Promise<DownloadResult>;
|
|
10
|
+
export declare function waitForDownloadViaPlaywright(opts: {
|
|
11
|
+
cdpUrl: string;
|
|
12
|
+
targetId?: string;
|
|
13
|
+
path?: string;
|
|
14
|
+
timeoutMs?: number;
|
|
15
|
+
allowedOutputRoots?: string[];
|
|
16
|
+
}): Promise<DownloadResult>;
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { getPageForTargetId, ensurePageState, restoreRoleRefsForTarget, refLocator, toAIFriendlyError, normalizeTimeoutMs, } from '../connection.js';
|
|
2
|
+
import { assertSafeOutputPath } from '../security.js';
|
|
3
|
+
export async function downloadViaPlaywright(opts) {
|
|
4
|
+
await assertSafeOutputPath(opts.path, opts.allowedOutputRoots);
|
|
5
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
6
|
+
ensurePageState(page);
|
|
7
|
+
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
8
|
+
const timeout = normalizeTimeoutMs(opts.timeoutMs, 30000, 120000);
|
|
9
|
+
const locator = refLocator(page, opts.ref);
|
|
10
|
+
try {
|
|
11
|
+
const [download] = await Promise.all([
|
|
12
|
+
page.waitForEvent('download', { timeout }),
|
|
13
|
+
locator.click({ timeout }),
|
|
14
|
+
]);
|
|
15
|
+
await download.saveAs(opts.path);
|
|
16
|
+
return {
|
|
17
|
+
url: download.url(),
|
|
18
|
+
suggestedFilename: download.suggestedFilename(),
|
|
19
|
+
path: opts.path,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
catch (err) {
|
|
23
|
+
throw toAIFriendlyError(err, opts.ref);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
export async function waitForDownloadViaPlaywright(opts) {
|
|
27
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
28
|
+
ensurePageState(page);
|
|
29
|
+
const timeout = normalizeTimeoutMs(opts.timeoutMs, 30000, 120000);
|
|
30
|
+
const download = await page.waitForEvent('download', { timeout });
|
|
31
|
+
const savePath = opts.path ?? download.suggestedFilename();
|
|
32
|
+
await assertSafeOutputPath(savePath, opts.allowedOutputRoots);
|
|
33
|
+
await download.saveAs(savePath);
|
|
34
|
+
return {
|
|
35
|
+
url: download.url(),
|
|
36
|
+
suggestedFilename: download.suggestedFilename(),
|
|
37
|
+
path: savePath,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import type { ColorScheme } from '../types.js';
|
|
2
|
+
export declare function emulateMediaViaPlaywright(opts: {
|
|
3
|
+
cdpUrl: string;
|
|
4
|
+
targetId?: string;
|
|
5
|
+
colorScheme: ColorScheme;
|
|
6
|
+
}): Promise<void>;
|
|
7
|
+
export declare function setDeviceViaPlaywright(opts: {
|
|
8
|
+
cdpUrl: string;
|
|
9
|
+
targetId?: string;
|
|
10
|
+
name: string;
|
|
11
|
+
}): Promise<void>;
|
|
12
|
+
export declare function setExtraHTTPHeadersViaPlaywright(opts: {
|
|
13
|
+
cdpUrl: string;
|
|
14
|
+
targetId?: string;
|
|
15
|
+
headers: Record<string, string>;
|
|
16
|
+
}): Promise<void>;
|
|
17
|
+
export declare function setGeolocationViaPlaywright(opts: {
|
|
18
|
+
cdpUrl: string;
|
|
19
|
+
targetId?: string;
|
|
20
|
+
latitude?: number;
|
|
21
|
+
longitude?: number;
|
|
22
|
+
accuracy?: number;
|
|
23
|
+
origin?: string;
|
|
24
|
+
clear?: boolean;
|
|
25
|
+
}): Promise<void>;
|
|
26
|
+
/**
|
|
27
|
+
* Set or clear HTTP credentials for the browser context.
|
|
28
|
+
* Note: Playwright's `setHTTPCredentials()` is deprecated — prefer providing credentials
|
|
29
|
+
* at context creation time. This function is retained for CDP-connected contexts where
|
|
30
|
+
* context creation is not controlled by the library.
|
|
31
|
+
*/
|
|
32
|
+
export declare function setHttpCredentialsViaPlaywright(opts: {
|
|
33
|
+
cdpUrl: string;
|
|
34
|
+
targetId?: string;
|
|
35
|
+
username?: string;
|
|
36
|
+
password?: string;
|
|
37
|
+
clear?: boolean;
|
|
38
|
+
}): Promise<void>;
|
|
39
|
+
export declare function setLocaleViaPlaywright(opts: {
|
|
40
|
+
cdpUrl: string;
|
|
41
|
+
targetId?: string;
|
|
42
|
+
locale: string;
|
|
43
|
+
}): Promise<void>;
|
|
44
|
+
export declare function setOfflineViaPlaywright(opts: {
|
|
45
|
+
cdpUrl: string;
|
|
46
|
+
targetId?: string;
|
|
47
|
+
offline: boolean;
|
|
48
|
+
}): Promise<void>;
|
|
49
|
+
export declare function setTimezoneViaPlaywright(opts: {
|
|
50
|
+
cdpUrl: string;
|
|
51
|
+
targetId?: string;
|
|
52
|
+
timezoneId: string;
|
|
53
|
+
}): Promise<void>;
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
import { devices } from 'playwright-core';
|
|
2
|
+
import { getPageForTargetId, ensurePageState, } from '../connection.js';
|
|
3
|
+
export async function emulateMediaViaPlaywright(opts) {
|
|
4
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
5
|
+
ensurePageState(page);
|
|
6
|
+
await page.emulateMedia({ colorScheme: opts.colorScheme });
|
|
7
|
+
}
|
|
8
|
+
export async function setDeviceViaPlaywright(opts) {
|
|
9
|
+
const device = devices[opts.name];
|
|
10
|
+
if (!device) {
|
|
11
|
+
const available = Object.keys(devices).slice(0, 10).join(', ');
|
|
12
|
+
throw new Error(`Unknown device "${opts.name}". Some available devices: ${available}...`);
|
|
13
|
+
}
|
|
14
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
15
|
+
ensurePageState(page);
|
|
16
|
+
if (device.viewport) {
|
|
17
|
+
await page.setViewportSize(device.viewport);
|
|
18
|
+
}
|
|
19
|
+
if (device.userAgent) {
|
|
20
|
+
const context = page.context();
|
|
21
|
+
// Playwright doesn't expose setUserAgent on context directly via CDP,
|
|
22
|
+
// so we use CDP Emulation.setUserAgentOverride
|
|
23
|
+
const session = await context.newCDPSession(page);
|
|
24
|
+
try {
|
|
25
|
+
await session.send('Emulation.setUserAgentOverride', {
|
|
26
|
+
userAgent: device.userAgent,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
finally {
|
|
30
|
+
await session.detach().catch(() => { });
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export async function setExtraHTTPHeadersViaPlaywright(opts) {
|
|
35
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
36
|
+
ensurePageState(page);
|
|
37
|
+
await page.setExtraHTTPHeaders(opts.headers);
|
|
38
|
+
}
|
|
39
|
+
export async function setGeolocationViaPlaywright(opts) {
|
|
40
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
41
|
+
ensurePageState(page);
|
|
42
|
+
const context = page.context();
|
|
43
|
+
if (opts.clear) {
|
|
44
|
+
await context.setGeolocation(null);
|
|
45
|
+
await context.clearPermissions();
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (opts.latitude === undefined || opts.longitude === undefined) {
|
|
49
|
+
throw new Error('latitude and longitude are required (or set clear=true)');
|
|
50
|
+
}
|
|
51
|
+
await context.grantPermissions(['geolocation'], opts.origin ? { origin: opts.origin } : undefined);
|
|
52
|
+
await context.setGeolocation({
|
|
53
|
+
latitude: opts.latitude,
|
|
54
|
+
longitude: opts.longitude,
|
|
55
|
+
accuracy: opts.accuracy,
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Set or clear HTTP credentials for the browser context.
|
|
60
|
+
* Note: Playwright's `setHTTPCredentials()` is deprecated — prefer providing credentials
|
|
61
|
+
* at context creation time. This function is retained for CDP-connected contexts where
|
|
62
|
+
* context creation is not controlled by the library.
|
|
63
|
+
*/
|
|
64
|
+
export async function setHttpCredentialsViaPlaywright(opts) {
|
|
65
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
66
|
+
ensurePageState(page);
|
|
67
|
+
const context = page.context();
|
|
68
|
+
if (opts.clear) {
|
|
69
|
+
await context.setHTTPCredentials({ username: '', password: '' });
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
await context.setHTTPCredentials({
|
|
73
|
+
username: opts.username ?? '',
|
|
74
|
+
password: opts.password ?? '',
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
export async function setLocaleViaPlaywright(opts) {
|
|
78
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
79
|
+
ensurePageState(page);
|
|
80
|
+
const session = await page.context().newCDPSession(page);
|
|
81
|
+
try {
|
|
82
|
+
await session.send('Emulation.setLocaleOverride', { locale: opts.locale });
|
|
83
|
+
}
|
|
84
|
+
finally {
|
|
85
|
+
await session.detach().catch(() => { });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export async function setOfflineViaPlaywright(opts) {
|
|
89
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
90
|
+
ensurePageState(page);
|
|
91
|
+
await page.context().setOffline(opts.offline);
|
|
92
|
+
}
|
|
93
|
+
export async function setTimezoneViaPlaywright(opts) {
|
|
94
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
95
|
+
ensurePageState(page);
|
|
96
|
+
const session = await page.context().newCDPSession(page);
|
|
97
|
+
try {
|
|
98
|
+
await session.send('Emulation.setTimezoneOverride', { timezoneId: opts.timezoneId });
|
|
99
|
+
}
|
|
100
|
+
finally {
|
|
101
|
+
await session.detach().catch(() => { });
|
|
102
|
+
}
|
|
103
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export interface FrameEvalResult {
|
|
2
|
+
frameUrl: string;
|
|
3
|
+
frameName: string;
|
|
4
|
+
result: unknown;
|
|
5
|
+
}
|
|
6
|
+
/**
|
|
7
|
+
* Evaluate JavaScript in ALL frames (including cross-origin iframes).
|
|
8
|
+
* Playwright can access cross-origin frames via CDP, bypassing same-origin policy.
|
|
9
|
+
* Returns results from each frame where evaluation succeeded.
|
|
10
|
+
*/
|
|
11
|
+
export declare function evaluateInAllFramesViaPlaywright(opts: {
|
|
12
|
+
cdpUrl: string;
|
|
13
|
+
targetId?: string;
|
|
14
|
+
fn: string;
|
|
15
|
+
}): Promise<FrameEvalResult[]>;
|
|
16
|
+
/**
|
|
17
|
+
* Evaluate JavaScript in the browser page context.
|
|
18
|
+
* This is intentionally using eval() to execute user-provided browser-side code,
|
|
19
|
+
* which is the core purpose of this function — running arbitrary JS in the page.
|
|
20
|
+
* The code runs in the browser sandbox, not in Node.js.
|
|
21
|
+
*/
|
|
22
|
+
export declare function evaluateViaPlaywright(opts: {
|
|
23
|
+
cdpUrl: string;
|
|
24
|
+
targetId?: string;
|
|
25
|
+
fn: string;
|
|
26
|
+
ref?: string;
|
|
27
|
+
timeoutMs?: number;
|
|
28
|
+
signal?: AbortSignal;
|
|
29
|
+
}): Promise<unknown>;
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { getPageForTargetId, ensurePageState, restoreRoleRefsForTarget, refLocator, } from '../connection.js';
|
|
2
|
+
/**
|
|
3
|
+
* Evaluate JavaScript in ALL frames (including cross-origin iframes).
|
|
4
|
+
* Playwright can access cross-origin frames via CDP, bypassing same-origin policy.
|
|
5
|
+
* Returns results from each frame where evaluation succeeded.
|
|
6
|
+
*/
|
|
7
|
+
export async function evaluateInAllFramesViaPlaywright(opts) {
|
|
8
|
+
const fnText = String(opts.fn ?? '').trim();
|
|
9
|
+
if (!fnText)
|
|
10
|
+
throw new Error('function is required');
|
|
11
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
12
|
+
const frames = page.frames();
|
|
13
|
+
const results = [];
|
|
14
|
+
for (const frame of frames) {
|
|
15
|
+
try {
|
|
16
|
+
// Runs in the frame's browser context (sandboxed), not in Node.js
|
|
17
|
+
const result = await frame.evaluate(
|
|
18
|
+
// eslint-disable-next-line no-eval
|
|
19
|
+
(fnBody) => {
|
|
20
|
+
'use strict';
|
|
21
|
+
try {
|
|
22
|
+
const candidate = (0, eval)('(' + fnBody + ')');
|
|
23
|
+
return typeof candidate === 'function' ? candidate() : candidate;
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
throw new Error('Invalid evaluate function: ' + (err instanceof Error ? err.message : String(err)));
|
|
27
|
+
}
|
|
28
|
+
}, fnText);
|
|
29
|
+
results.push({
|
|
30
|
+
frameUrl: frame.url(),
|
|
31
|
+
frameName: frame.name(),
|
|
32
|
+
result,
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
catch {
|
|
36
|
+
// Frame may have been detached or navigation in progress — skip
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return results;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Evaluate JavaScript in the browser page context.
|
|
43
|
+
* This is intentionally using eval() to execute user-provided browser-side code,
|
|
44
|
+
* which is the core purpose of this function — running arbitrary JS in the page.
|
|
45
|
+
* The code runs in the browser sandbox, not in Node.js.
|
|
46
|
+
*/
|
|
47
|
+
export async function evaluateViaPlaywright(opts) {
|
|
48
|
+
const fnText = String(opts.fn ?? '').trim();
|
|
49
|
+
if (!fnText)
|
|
50
|
+
throw new Error('function is required');
|
|
51
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
52
|
+
ensurePageState(page);
|
|
53
|
+
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
54
|
+
const timeout = opts.timeoutMs != null ? opts.timeoutMs : undefined;
|
|
55
|
+
if (opts.ref) {
|
|
56
|
+
const locator = refLocator(page, opts.ref);
|
|
57
|
+
// Runs in the browser page context (sandboxed), not in Node.js
|
|
58
|
+
return await locator.evaluate(
|
|
59
|
+
// eslint-disable-next-line no-eval
|
|
60
|
+
(el, fnBody) => {
|
|
61
|
+
'use strict';
|
|
62
|
+
try {
|
|
63
|
+
const candidate = (0, eval)('(' + fnBody + ')');
|
|
64
|
+
return typeof candidate === 'function' ? candidate(el) : candidate;
|
|
65
|
+
}
|
|
66
|
+
catch (err) {
|
|
67
|
+
throw new Error('Invalid evaluate function: ' + (err instanceof Error ? err.message : String(err)));
|
|
68
|
+
}
|
|
69
|
+
}, fnText, { timeout });
|
|
70
|
+
}
|
|
71
|
+
// Runs in the browser page context (sandboxed), not in Node.js
|
|
72
|
+
const evalPromise = page.evaluate(
|
|
73
|
+
// eslint-disable-next-line no-eval
|
|
74
|
+
(fnBody) => {
|
|
75
|
+
'use strict';
|
|
76
|
+
try {
|
|
77
|
+
const candidate = (0, eval)('(' + fnBody + ')');
|
|
78
|
+
return typeof candidate === 'function' ? candidate() : candidate;
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
throw new Error('Invalid evaluate function: ' + (err instanceof Error ? err.message : String(err)));
|
|
82
|
+
}
|
|
83
|
+
}, fnText);
|
|
84
|
+
if (!opts.signal)
|
|
85
|
+
return evalPromise;
|
|
86
|
+
return Promise.race([
|
|
87
|
+
evalPromise,
|
|
88
|
+
new Promise((_, reject) => {
|
|
89
|
+
opts.signal.addEventListener('abort', () => reject(new Error('Evaluate aborted')), { once: true });
|
|
90
|
+
}),
|
|
91
|
+
]);
|
|
92
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import type { FormField } from '../types.js';
|
|
2
|
+
type MouseButton = 'left' | 'right' | 'middle';
|
|
3
|
+
type KeyModifier = 'Alt' | 'Control' | 'ControlOrMeta' | 'Meta' | 'Shift';
|
|
4
|
+
export declare function clickViaPlaywright(opts: {
|
|
5
|
+
cdpUrl: string;
|
|
6
|
+
targetId?: string;
|
|
7
|
+
ref: string;
|
|
8
|
+
doubleClick?: boolean;
|
|
9
|
+
button?: MouseButton;
|
|
10
|
+
modifiers?: KeyModifier[];
|
|
11
|
+
timeoutMs?: number;
|
|
12
|
+
}): Promise<void>;
|
|
13
|
+
export declare function hoverViaPlaywright(opts: {
|
|
14
|
+
cdpUrl: string;
|
|
15
|
+
targetId?: string;
|
|
16
|
+
ref: string;
|
|
17
|
+
timeoutMs?: number;
|
|
18
|
+
}): Promise<void>;
|
|
19
|
+
export declare function typeViaPlaywright(opts: {
|
|
20
|
+
cdpUrl: string;
|
|
21
|
+
targetId?: string;
|
|
22
|
+
ref: string;
|
|
23
|
+
text: string;
|
|
24
|
+
submit?: boolean;
|
|
25
|
+
slowly?: boolean;
|
|
26
|
+
timeoutMs?: number;
|
|
27
|
+
}): Promise<void>;
|
|
28
|
+
export declare function selectOptionViaPlaywright(opts: {
|
|
29
|
+
cdpUrl: string;
|
|
30
|
+
targetId?: string;
|
|
31
|
+
ref: string;
|
|
32
|
+
values: string[];
|
|
33
|
+
timeoutMs?: number;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
export declare function dragViaPlaywright(opts: {
|
|
36
|
+
cdpUrl: string;
|
|
37
|
+
targetId?: string;
|
|
38
|
+
startRef: string;
|
|
39
|
+
endRef: string;
|
|
40
|
+
timeoutMs?: number;
|
|
41
|
+
}): Promise<void>;
|
|
42
|
+
export declare function fillFormViaPlaywright(opts: {
|
|
43
|
+
cdpUrl: string;
|
|
44
|
+
targetId?: string;
|
|
45
|
+
fields: FormField[];
|
|
46
|
+
timeoutMs?: number;
|
|
47
|
+
}): Promise<void>;
|
|
48
|
+
export declare function scrollIntoViewViaPlaywright(opts: {
|
|
49
|
+
cdpUrl: string;
|
|
50
|
+
targetId?: string;
|
|
51
|
+
ref: string;
|
|
52
|
+
timeoutMs?: number;
|
|
53
|
+
}): Promise<void>;
|
|
54
|
+
export declare function highlightViaPlaywright(opts: {
|
|
55
|
+
cdpUrl: string;
|
|
56
|
+
targetId?: string;
|
|
57
|
+
ref: string;
|
|
58
|
+
}): Promise<void>;
|
|
59
|
+
export declare function setInputFilesViaPlaywright(opts: {
|
|
60
|
+
cdpUrl: string;
|
|
61
|
+
targetId?: string;
|
|
62
|
+
ref?: string;
|
|
63
|
+
element?: string;
|
|
64
|
+
paths: string[];
|
|
65
|
+
}): Promise<void>;
|
|
66
|
+
export declare function armDialogViaPlaywright(opts: {
|
|
67
|
+
cdpUrl: string;
|
|
68
|
+
targetId?: string;
|
|
69
|
+
accept: boolean;
|
|
70
|
+
promptText?: string;
|
|
71
|
+
timeoutMs?: number;
|
|
72
|
+
}): Promise<void>;
|
|
73
|
+
export declare function armFileUploadViaPlaywright(opts: {
|
|
74
|
+
cdpUrl: string;
|
|
75
|
+
targetId?: string;
|
|
76
|
+
paths?: string[];
|
|
77
|
+
timeoutMs?: number;
|
|
78
|
+
}): Promise<void>;
|
|
79
|
+
export {};
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
import { getPageForTargetId, ensurePageState, restoreRoleRefsForTarget, refLocator, toAIFriendlyError, normalizeTimeoutMs, } from '../connection.js';
|
|
2
|
+
import { assertSafeUploadPaths } from '../security.js';
|
|
3
|
+
export async function clickViaPlaywright(opts) {
|
|
4
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
5
|
+
ensurePageState(page);
|
|
6
|
+
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
7
|
+
const locator = refLocator(page, opts.ref);
|
|
8
|
+
const timeout = normalizeTimeoutMs(opts.timeoutMs, 8000, 60000);
|
|
9
|
+
try {
|
|
10
|
+
if (opts.doubleClick) {
|
|
11
|
+
await locator.dblclick({ timeout, button: opts.button, modifiers: opts.modifiers });
|
|
12
|
+
}
|
|
13
|
+
else {
|
|
14
|
+
await locator.click({ timeout, button: opts.button, modifiers: opts.modifiers });
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
throw toAIFriendlyError(err, opts.ref);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export async function hoverViaPlaywright(opts) {
|
|
22
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
23
|
+
ensurePageState(page);
|
|
24
|
+
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
25
|
+
try {
|
|
26
|
+
await refLocator(page, opts.ref).hover({
|
|
27
|
+
timeout: normalizeTimeoutMs(opts.timeoutMs, 8000, 60000),
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
catch (err) {
|
|
31
|
+
throw toAIFriendlyError(err, opts.ref);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
export async function typeViaPlaywright(opts) {
|
|
35
|
+
const text = String(opts.text ?? '');
|
|
36
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
37
|
+
ensurePageState(page);
|
|
38
|
+
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
39
|
+
const locator = refLocator(page, opts.ref);
|
|
40
|
+
const timeout = normalizeTimeoutMs(opts.timeoutMs, 8000, 60000);
|
|
41
|
+
try {
|
|
42
|
+
if (opts.slowly) {
|
|
43
|
+
await locator.click({ timeout });
|
|
44
|
+
await locator.pressSequentially(text, { timeout, delay: 75 });
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
await locator.fill(text, { timeout });
|
|
48
|
+
}
|
|
49
|
+
if (opts.submit)
|
|
50
|
+
await locator.press('Enter', { timeout });
|
|
51
|
+
}
|
|
52
|
+
catch (err) {
|
|
53
|
+
throw toAIFriendlyError(err, opts.ref);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
export async function selectOptionViaPlaywright(opts) {
|
|
57
|
+
if (!opts.values?.length)
|
|
58
|
+
throw new Error('values are required');
|
|
59
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
60
|
+
ensurePageState(page);
|
|
61
|
+
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
62
|
+
try {
|
|
63
|
+
await refLocator(page, opts.ref).selectOption(opts.values, {
|
|
64
|
+
timeout: normalizeTimeoutMs(opts.timeoutMs, 8000, 60000),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
catch (err) {
|
|
68
|
+
throw toAIFriendlyError(err, opts.ref);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export async function dragViaPlaywright(opts) {
|
|
72
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
73
|
+
ensurePageState(page);
|
|
74
|
+
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
75
|
+
try {
|
|
76
|
+
await refLocator(page, opts.startRef).dragTo(refLocator(page, opts.endRef), {
|
|
77
|
+
timeout: normalizeTimeoutMs(opts.timeoutMs, 8000, 60000),
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
catch (err) {
|
|
81
|
+
throw toAIFriendlyError(err, `${opts.startRef} -> ${opts.endRef}`);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
export async function fillFormViaPlaywright(opts) {
|
|
85
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
86
|
+
ensurePageState(page);
|
|
87
|
+
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
88
|
+
const timeout = normalizeTimeoutMs(opts.timeoutMs, 8000, 60000);
|
|
89
|
+
for (let i = 0; i < opts.fields.length; i++) {
|
|
90
|
+
const field = opts.fields[i];
|
|
91
|
+
const ref = field.ref.trim();
|
|
92
|
+
const type = (typeof field.type === 'string' ? field.type.trim() : '') || 'text';
|
|
93
|
+
const rawValue = field.value;
|
|
94
|
+
const value = rawValue == null ? '' : String(rawValue);
|
|
95
|
+
if (!ref)
|
|
96
|
+
throw new Error(`fill(): field at index ${i} has empty ref`);
|
|
97
|
+
const locator = refLocator(page, ref);
|
|
98
|
+
if (type === 'checkbox' || type === 'radio') {
|
|
99
|
+
const checked = rawValue === true || rawValue === 1 || rawValue === '1' || rawValue === 'true';
|
|
100
|
+
try {
|
|
101
|
+
await locator.setChecked(checked, { timeout });
|
|
102
|
+
}
|
|
103
|
+
catch (err) {
|
|
104
|
+
throw toAIFriendlyError(err, ref);
|
|
105
|
+
}
|
|
106
|
+
continue;
|
|
107
|
+
}
|
|
108
|
+
try {
|
|
109
|
+
await locator.fill(value, { timeout });
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
throw toAIFriendlyError(err, ref);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
export async function scrollIntoViewViaPlaywright(opts) {
|
|
117
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
118
|
+
ensurePageState(page);
|
|
119
|
+
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
120
|
+
try {
|
|
121
|
+
await refLocator(page, opts.ref).scrollIntoViewIfNeeded({
|
|
122
|
+
timeout: normalizeTimeoutMs(opts.timeoutMs, 20000),
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
catch (err) {
|
|
126
|
+
throw toAIFriendlyError(err, opts.ref);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
export async function highlightViaPlaywright(opts) {
|
|
130
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
131
|
+
ensurePageState(page);
|
|
132
|
+
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
133
|
+
try {
|
|
134
|
+
await refLocator(page, opts.ref).highlight();
|
|
135
|
+
}
|
|
136
|
+
catch (err) {
|
|
137
|
+
throw toAIFriendlyError(err, opts.ref);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
export async function setInputFilesViaPlaywright(opts) {
|
|
141
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
142
|
+
ensurePageState(page);
|
|
143
|
+
restoreRoleRefsForTarget({ cdpUrl: opts.cdpUrl, targetId: opts.targetId, page });
|
|
144
|
+
const locator = opts.ref
|
|
145
|
+
? refLocator(page, opts.ref)
|
|
146
|
+
: opts.element
|
|
147
|
+
? page.locator(opts.element).first()
|
|
148
|
+
: null;
|
|
149
|
+
if (!locator)
|
|
150
|
+
throw new Error('Either ref or element is required for setInputFiles');
|
|
151
|
+
await assertSafeUploadPaths(opts.paths);
|
|
152
|
+
try {
|
|
153
|
+
await locator.setInputFiles(opts.paths);
|
|
154
|
+
}
|
|
155
|
+
catch (err) {
|
|
156
|
+
throw toAIFriendlyError(err, opts.ref ?? opts.element ?? 'unknown');
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
export async function armDialogViaPlaywright(opts) {
|
|
160
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
161
|
+
ensurePageState(page);
|
|
162
|
+
const timeout = normalizeTimeoutMs(opts.timeoutMs, 30000, 120000);
|
|
163
|
+
return new Promise((resolve, reject) => {
|
|
164
|
+
const timer = setTimeout(() => {
|
|
165
|
+
page.removeListener('dialog', handler);
|
|
166
|
+
reject(new Error(`No dialog appeared within ${timeout}ms`));
|
|
167
|
+
}, timeout);
|
|
168
|
+
const handler = async (dialog) => {
|
|
169
|
+
clearTimeout(timer);
|
|
170
|
+
try {
|
|
171
|
+
if (opts.accept) {
|
|
172
|
+
await dialog.accept(opts.promptText);
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
await dialog.dismiss();
|
|
176
|
+
}
|
|
177
|
+
resolve();
|
|
178
|
+
}
|
|
179
|
+
catch (err) {
|
|
180
|
+
reject(err);
|
|
181
|
+
}
|
|
182
|
+
};
|
|
183
|
+
page.once('dialog', handler);
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
export async function armFileUploadViaPlaywright(opts) {
|
|
187
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
188
|
+
ensurePageState(page);
|
|
189
|
+
const timeout = normalizeTimeoutMs(opts.timeoutMs, 30000, 120000);
|
|
190
|
+
return new Promise((resolve, reject) => {
|
|
191
|
+
const timer = setTimeout(() => {
|
|
192
|
+
page.removeListener('filechooser', handler);
|
|
193
|
+
reject(new Error(`No file chooser appeared within ${timeout}ms`));
|
|
194
|
+
}, timeout);
|
|
195
|
+
const handler = async (fc) => {
|
|
196
|
+
clearTimeout(timer);
|
|
197
|
+
try {
|
|
198
|
+
const paths = opts.paths ?? [];
|
|
199
|
+
if (paths.length > 0)
|
|
200
|
+
await assertSafeUploadPaths(paths);
|
|
201
|
+
await fc.setFiles(paths);
|
|
202
|
+
resolve();
|
|
203
|
+
}
|
|
204
|
+
catch (err) {
|
|
205
|
+
reject(err);
|
|
206
|
+
}
|
|
207
|
+
};
|
|
208
|
+
page.once('filechooser', handler);
|
|
209
|
+
});
|
|
210
|
+
}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { getPageForTargetId, ensurePageState } from '../connection.js';
|
|
2
|
+
export async function pressKeyViaPlaywright(opts) {
|
|
3
|
+
const key = String(opts.key ?? '').trim();
|
|
4
|
+
if (!key)
|
|
5
|
+
throw new Error('key is required');
|
|
6
|
+
const page = await getPageForTargetId({ cdpUrl: opts.cdpUrl, targetId: opts.targetId });
|
|
7
|
+
ensurePageState(page);
|
|
8
|
+
await page.keyboard.press(key, { delay: Math.max(0, Math.floor(opts.delayMs ?? 0)) });
|
|
9
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { BrowserTab, SsrfPolicy } from '../types.js';
|
|
2
|
+
export declare function navigateViaPlaywright(opts: {
|
|
3
|
+
cdpUrl: string;
|
|
4
|
+
targetId?: string;
|
|
5
|
+
url: string;
|
|
6
|
+
timeoutMs?: number;
|
|
7
|
+
ssrfPolicy?: SsrfPolicy;
|
|
8
|
+
/** @deprecated Use ssrfPolicy: { dangerouslyAllowPrivateNetwork: true } instead */
|
|
9
|
+
allowInternal?: boolean;
|
|
10
|
+
}): Promise<{
|
|
11
|
+
url: string;
|
|
12
|
+
}>;
|
|
13
|
+
export declare function listPagesViaPlaywright(opts: {
|
|
14
|
+
cdpUrl: string;
|
|
15
|
+
}): Promise<BrowserTab[]>;
|
|
16
|
+
export declare function createPageViaPlaywright(opts: {
|
|
17
|
+
cdpUrl: string;
|
|
18
|
+
url?: string;
|
|
19
|
+
ssrfPolicy?: SsrfPolicy;
|
|
20
|
+
/** @deprecated Use ssrfPolicy: { dangerouslyAllowPrivateNetwork: true } instead */
|
|
21
|
+
allowInternal?: boolean;
|
|
22
|
+
}): Promise<BrowserTab>;
|
|
23
|
+
export declare function closePageViaPlaywright(opts: {
|
|
24
|
+
cdpUrl: string;
|
|
25
|
+
targetId?: string;
|
|
26
|
+
}): Promise<void>;
|
|
27
|
+
export declare function closePageByTargetIdViaPlaywright(opts: {
|
|
28
|
+
cdpUrl: string;
|
|
29
|
+
targetId: string;
|
|
30
|
+
}): Promise<void>;
|
|
31
|
+
export declare function focusPageByTargetIdViaPlaywright(opts: {
|
|
32
|
+
cdpUrl: string;
|
|
33
|
+
targetId: string;
|
|
34
|
+
}): Promise<void>;
|
|
35
|
+
export declare function resizeViewportViaPlaywright(opts: {
|
|
36
|
+
cdpUrl: string;
|
|
37
|
+
targetId?: string;
|
|
38
|
+
width: number;
|
|
39
|
+
height: number;
|
|
40
|
+
}): Promise<void>;
|