@monoes/monobrowse 1.0.11 → 1.0.12
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/src/__tests__/adapters.test.js +5 -2
- package/dist/src/__tests__/adapters.test.js.map +1 -1
- package/dist/src/__tests__/batch-eval-parsing.test.js +4 -2
- package/dist/src/__tests__/batch-eval-parsing.test.js.map +1 -1
- package/dist/src/__tests__/box-model.test.js +9 -5
- package/dist/src/__tests__/box-model.test.js.map +1 -1
- package/dist/src/__tests__/browser-launch.test.d.ts +10 -0
- package/dist/src/__tests__/browser-launch.test.d.ts.map +1 -1
- package/dist/src/__tests__/browser-launch.test.js +17 -10
- package/dist/src/__tests__/browser-launch.test.js.map +1 -1
- package/dist/src/__tests__/cdp-client.test.js +9 -3
- package/dist/src/__tests__/cdp-client.test.js.map +1 -1
- package/dist/src/__tests__/close-browser.test.d.ts +13 -0
- package/dist/src/__tests__/close-browser.test.d.ts.map +1 -1
- package/dist/src/__tests__/close-browser.test.js +5 -5
- package/dist/src/__tests__/close-browser.test.js.map +1 -1
- package/dist/src/__tests__/console-log.test.js +17 -6
- package/dist/src/__tests__/console-log.test.js.map +1 -1
- package/dist/src/__tests__/find.test.js +26 -6
- package/dist/src/__tests__/find.test.js.map +1 -1
- package/dist/src/__tests__/frame-command.test.js +9 -3
- package/dist/src/__tests__/frame-command.test.js.map +1 -1
- package/dist/src/__tests__/open-url-navigate-error.test.js +1 -3
- package/dist/src/__tests__/open-url-navigate-error.test.js.map +1 -1
- package/dist/src/__tests__/output.test.js +1 -1
- package/dist/src/__tests__/output.test.js.map +1 -1
- package/dist/src/__tests__/reap-idle-browser.test.d.ts +15 -0
- package/dist/src/__tests__/reap-idle-browser.test.d.ts.map +1 -0
- package/dist/src/__tests__/reap-idle-browser.test.js +238 -0
- package/dist/src/__tests__/reap-idle-browser.test.js.map +1 -0
- package/dist/src/__tests__/ref-cache.test.js +18 -10
- package/dist/src/__tests__/ref-cache.test.js.map +1 -1
- package/dist/src/__tests__/session-state.test.d.ts +8 -0
- package/dist/src/__tests__/session-state.test.d.ts.map +1 -1
- package/dist/src/__tests__/session-state.test.js +10 -10
- package/dist/src/__tests__/session-state.test.js.map +1 -1
- package/dist/src/__tests__/snapshot.test.js +44 -14
- package/dist/src/__tests__/snapshot.test.js.map +1 -1
- package/dist/src/__tests__/storage-eval.test.js +14 -6
- package/dist/src/__tests__/storage-eval.test.js.map +1 -1
- package/dist/src/__tests__/tabs.test.js +25 -9
- package/dist/src/__tests__/tabs.test.js.map +1 -1
- package/dist/src/browser/action-builder/analyzer.d.ts +1 -1
- package/dist/src/browser/action-builder/analyzer.d.ts.map +1 -1
- package/dist/src/browser/action-builder/analyzer.js +25 -9
- package/dist/src/browser/action-builder/analyzer.js.map +1 -1
- package/dist/src/browser/action-builder/types.d.ts.map +1 -1
- package/dist/src/browser/actions.d.ts +1 -1
- package/dist/src/browser/actions.d.ts.map +1 -1
- package/dist/src/browser/actions.js +16 -6
- package/dist/src/browser/actions.js.map +1 -1
- package/dist/src/browser/adapters/gemini.d.ts.map +1 -1
- package/dist/src/browser/adapters/gemini.js.map +1 -1
- package/dist/src/browser/adapters/google.d.ts.map +1 -1
- package/dist/src/browser/adapters/google.js.map +1 -1
- package/dist/src/browser/adapters/index.d.ts.map +1 -1
- package/dist/src/browser/adapters/index.js +3 -3
- package/dist/src/browser/adapters/index.js.map +1 -1
- package/dist/src/browser/adapters/instagram.d.ts.map +1 -1
- package/dist/src/browser/adapters/instagram.js.map +1 -1
- package/dist/src/browser/adapters/linkedin.d.ts.map +1 -1
- package/dist/src/browser/adapters/linkedin.js.map +1 -1
- package/dist/src/browser/adapters/microsoft.d.ts.map +1 -1
- package/dist/src/browser/adapters/microsoft.js.map +1 -1
- package/dist/src/browser/adapters/x.d.ts.map +1 -1
- package/dist/src/browser/adapters/x.js.map +1 -1
- package/dist/src/browser/browser.d.ts +30 -0
- package/dist/src/browser/browser.d.ts.map +1 -1
- package/dist/src/browser/browser.js +122 -15
- package/dist/src/browser/browser.js.map +1 -1
- package/dist/src/browser/cdp.d.ts.map +1 -1
- package/dist/src/browser/cdp.js +21 -8
- package/dist/src/browser/cdp.js.map +1 -1
- package/dist/src/browser/console-log.d.ts.map +1 -1
- package/dist/src/browser/console-log.js +2 -2
- package/dist/src/browser/console-log.js.map +1 -1
- package/dist/src/browser/dashboard/server.d.ts.map +1 -1
- package/dist/src/browser/dashboard/server.js +29 -19
- package/dist/src/browser/dashboard/server.js.map +1 -1
- package/dist/src/browser/dashboard/ui.html +153 -165
- package/dist/src/browser/dialog.d.ts.map +1 -1
- package/dist/src/browser/dialog.js +3 -1
- package/dist/src/browser/dialog.js.map +1 -1
- package/dist/src/browser/emulation.d.ts.map +1 -1
- package/dist/src/browser/emulation.js +21 -6
- package/dist/src/browser/emulation.js.map +1 -1
- package/dist/src/browser/find.d.ts +4 -4
- package/dist/src/browser/find.d.ts.map +1 -1
- package/dist/src/browser/find.js +14 -6
- package/dist/src/browser/find.js.map +1 -1
- package/dist/src/browser/har.d.ts.map +1 -1
- package/dist/src/browser/har.js +14 -7
- package/dist/src/browser/har.js.map +1 -1
- package/dist/src/browser/index.d.ts +16 -16
- package/dist/src/browser/index.d.ts.map +1 -1
- package/dist/src/browser/index.js +16 -16
- package/dist/src/browser/index.js.map +1 -1
- package/dist/src/browser/network.d.ts.map +1 -1
- package/dist/src/browser/network.js +14 -4
- package/dist/src/browser/network.js.map +1 -1
- package/dist/src/browser/pdf.d.ts.map +1 -1
- package/dist/src/browser/pdf.js +3 -3
- package/dist/src/browser/pdf.js.map +1 -1
- package/dist/src/browser/profiler.d.ts.map +1 -1
- package/dist/src/browser/profiler.js +10 -5
- package/dist/src/browser/profiler.js.map +1 -1
- package/dist/src/browser/record.d.ts +1 -1
- package/dist/src/browser/record.d.ts.map +1 -1
- package/dist/src/browser/record.js +18 -7
- package/dist/src/browser/record.js.map +1 -1
- package/dist/src/browser/ref-cache.d.ts.map +1 -1
- package/dist/src/browser/ref-cache.js +9 -4
- package/dist/src/browser/ref-cache.js.map +1 -1
- package/dist/src/browser/screenshot.d.ts.map +1 -1
- package/dist/src/browser/screenshot.js +11 -7
- package/dist/src/browser/screenshot.js.map +1 -1
- package/dist/src/browser/session.d.ts +1 -1
- package/dist/src/browser/session.d.ts.map +1 -1
- package/dist/src/browser/session.js +27 -8
- package/dist/src/browser/session.js.map +1 -1
- package/dist/src/browser/snapshot.d.ts +1 -1
- package/dist/src/browser/snapshot.d.ts.map +1 -1
- package/dist/src/browser/snapshot.js +8 -4
- package/dist/src/browser/snapshot.js.map +1 -1
- package/dist/src/browser/storage.d.ts.map +1 -1
- package/dist/src/browser/storage.js.map +1 -1
- package/dist/src/browser/tabs.d.ts.map +1 -1
- package/dist/src/browser/tabs.js +9 -3
- package/dist/src/browser/tabs.js.map +1 -1
- package/dist/src/browser/trace.d.ts.map +1 -1
- package/dist/src/browser/trace.js +3 -3
- package/dist/src/browser/trace.js.map +1 -1
- package/dist/src/browser/types.d.ts.map +1 -1
- package/dist/src/browser/types.js +23 -4
- package/dist/src/browser/types.js.map +1 -1
- package/dist/src/browser/vitals.d.ts.map +1 -1
- package/dist/src/browser/vitals.js +1 -1
- package/dist/src/browser/vitals.js.map +1 -1
- package/dist/src/browser/wait.d.ts.map +1 -1
- package/dist/src/browser/wait.js +11 -5
- package/dist/src/browser/wait.js.map +1 -1
- package/dist/src/cli/action.d.ts.map +1 -1
- package/dist/src/cli/action.js +14 -11
- package/dist/src/cli/action.js.map +1 -1
- package/dist/src/cli/commands.d.ts.map +1 -1
- package/dist/src/cli/commands.js +453 -159
- package/dist/src/cli/commands.js.map +1 -1
- package/dist/src/cli/output.js +5 -5
- package/dist/src/cli/output.js.map +1 -1
- package/dist/src/cli/platform.d.ts.map +1 -1
- package/dist/src/cli/platform.js +15 -11
- package/dist/src/cli/platform.js.map +1 -1
- package/dist/src/cli/types.d.ts +1 -1
- package/dist/src/cli/types.d.ts.map +1 -1
- package/dist/src/cli.js +21 -9
- package/dist/src/cli.js.map +1 -1
- package/dist/src/index.d.ts +2 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +2 -2
- package/dist/src/index.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/src/__tests__/adapters.test.ts +15 -8
- package/src/__tests__/batch-eval-parsing.test.ts +4 -2
- package/src/__tests__/box-model.test.ts +9 -5
- package/src/__tests__/browser-launch.test.ts +26 -15
- package/src/__tests__/cdp-client.test.ts +25 -9
- package/src/__tests__/close-browser.test.ts +11 -6
- package/src/__tests__/console-log.test.ts +36 -13
- package/src/__tests__/find.test.ts +58 -20
- package/src/__tests__/frame-command.test.ts +12 -6
- package/src/__tests__/open-url-navigate-error.test.ts +2 -4
- package/src/__tests__/output.test.ts +1 -1
- package/src/__tests__/reap-idle-browser.test.ts +276 -0
- package/src/__tests__/ref-cache.test.ts +25 -12
- package/src/__tests__/session-state.test.ts +40 -28
- package/src/__tests__/snapshot.test.ts +60 -21
- package/src/__tests__/storage-eval.test.ts +30 -16
- package/src/__tests__/tabs.test.ts +26 -10
- package/src/browser/action-builder/analyzer.ts +39 -15
- package/src/browser/action-builder/types.ts +45 -8
- package/src/browser/actions.ts +288 -123
- package/src/browser/adapters/gemini.ts +7 -3
- package/src/browser/adapters/google.ts +5 -3
- package/src/browser/adapters/index.ts +5 -4
- package/src/browser/adapters/instagram.ts +4 -2
- package/src/browser/adapters/linkedin.ts +4 -2
- package/src/browser/adapters/microsoft.ts +9 -5
- package/src/browser/adapters/x.ts +7 -3
- package/src/browser/browser.ts +188 -42
- package/src/browser/cdp.ts +43 -11
- package/src/browser/console-log.ts +24 -7
- package/src/browser/dashboard/server.ts +44 -25
- package/src/browser/dashboard/ui.html +153 -165
- package/src/browser/dialog.ts +25 -8
- package/src/browser/emulation.ts +86 -36
- package/src/browser/find.ts +110 -47
- package/src/browser/har.ts +58 -26
- package/src/browser/index.ts +16 -16
- package/src/browser/network.ts +147 -46
- package/src/browser/pdf.ts +4 -4
- package/src/browser/profiler.ts +23 -10
- package/src/browser/record.ts +38 -19
- package/src/browser/ref-cache.ts +38 -17
- package/src/browser/screenshot.ts +58 -32
- package/src/browser/session.ts +51 -18
- package/src/browser/snapshot.ts +34 -20
- package/src/browser/storage.ts +73 -17
- package/src/browser/tabs.ts +29 -17
- package/src/browser/trace.ts +14 -10
- package/src/browser/types.ts +23 -4
- package/src/browser/vitals.ts +33 -16
- package/src/browser/wait.ts +47 -21
- package/src/cli/action.ts +52 -21
- package/src/cli/commands.ts +1093 -385
- package/src/cli/output.ts +5 -5
- package/src/cli/platform.ts +44 -23
- package/src/cli/types.ts +1 -1
- package/src/cli.ts +34 -18
- package/src/index.ts +2 -2
package/src/browser/actions.ts
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
|
-
import { resolve } from 'path';
|
|
1
|
+
import { resolve } from 'node:path';
|
|
2
2
|
import type { CdpClient } from './cdp.js';
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
3
|
+
import { getElementBox, getObjectIdForRef } from './snapshot.js';
|
|
4
|
+
import type { ClickOptions, ElementRef } from './types.js';
|
|
5
5
|
|
|
6
6
|
export async function clickElement(
|
|
7
7
|
client: CdpClient,
|
|
8
8
|
sessionId: string,
|
|
9
9
|
ref: ElementRef,
|
|
10
|
-
options: ClickOptions = {}
|
|
10
|
+
options: ClickOptions = {},
|
|
11
11
|
): Promise<void> {
|
|
12
12
|
const box = await getElementBox(client, sessionId, ref);
|
|
13
13
|
|
|
@@ -19,11 +19,15 @@ export async function clickElement(
|
|
|
19
19
|
// Fallback: use JS click via objectId
|
|
20
20
|
const objectId = await getObjectIdForRef(client, sessionId, ref);
|
|
21
21
|
if (objectId) {
|
|
22
|
-
await client.send(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
22
|
+
await client.send(
|
|
23
|
+
'Runtime.callFunctionOn',
|
|
24
|
+
{
|
|
25
|
+
functionDeclaration: 'function() { this.click(); }',
|
|
26
|
+
objectId,
|
|
27
|
+
returnByValue: true,
|
|
28
|
+
},
|
|
29
|
+
sessionId,
|
|
30
|
+
);
|
|
27
31
|
return;
|
|
28
32
|
}
|
|
29
33
|
|
|
@@ -35,7 +39,7 @@ export async function clickPoint(
|
|
|
35
39
|
sessionId: string,
|
|
36
40
|
x: number,
|
|
37
41
|
y: number,
|
|
38
|
-
options: ClickOptions = {}
|
|
42
|
+
options: ClickOptions = {},
|
|
39
43
|
): Promise<void> {
|
|
40
44
|
const button = options.button ?? 'left';
|
|
41
45
|
// Cap clickCount to prevent unbounded loop DoS
|
|
@@ -47,8 +51,21 @@ export async function clickPoint(
|
|
|
47
51
|
|
|
48
52
|
for (let i = 0; i < clickCount; i++) {
|
|
49
53
|
const count = i + 1;
|
|
50
|
-
await client.send(
|
|
51
|
-
|
|
54
|
+
await client.send(
|
|
55
|
+
'Input.dispatchMouseEvent',
|
|
56
|
+
{ ...shared, type: 'mousePressed', buttons: buttonsMask, clickCount: count },
|
|
57
|
+
sessionId,
|
|
58
|
+
);
|
|
59
|
+
await client.send(
|
|
60
|
+
'Input.dispatchMouseEvent',
|
|
61
|
+
{
|
|
62
|
+
...shared,
|
|
63
|
+
type: 'mouseReleased',
|
|
64
|
+
buttons: i < clickCount - 1 ? buttonsMask : 0,
|
|
65
|
+
clickCount: count,
|
|
66
|
+
},
|
|
67
|
+
sessionId,
|
|
68
|
+
);
|
|
52
69
|
}
|
|
53
70
|
}
|
|
54
71
|
|
|
@@ -56,7 +73,7 @@ export async function fillElement(
|
|
|
56
73
|
client: CdpClient,
|
|
57
74
|
sessionId: string,
|
|
58
75
|
ref: ElementRef,
|
|
59
|
-
value: string
|
|
76
|
+
value: string,
|
|
60
77
|
): Promise<void> {
|
|
61
78
|
const box = await getElementBox(client, sessionId, ref);
|
|
62
79
|
|
|
@@ -68,8 +85,13 @@ export async function fillElement(
|
|
|
68
85
|
// Select all and replace
|
|
69
86
|
const objectId = await getObjectIdForRef(client, sessionId, ref);
|
|
70
87
|
if (objectId) {
|
|
71
|
-
const fillSelectResult = await client.send<{
|
|
72
|
-
|
|
88
|
+
const fillSelectResult = await client.send<{
|
|
89
|
+
result: unknown;
|
|
90
|
+
exceptionDetails?: { text: string; exception?: { description?: string } };
|
|
91
|
+
}>(
|
|
92
|
+
'Runtime.callFunctionOn',
|
|
93
|
+
{
|
|
94
|
+
functionDeclaration: `function() {
|
|
73
95
|
this.focus();
|
|
74
96
|
if (this.tagName === 'INPUT' || this.tagName === 'TEXTAREA') {
|
|
75
97
|
this.select();
|
|
@@ -80,11 +102,15 @@ export async function fillElement(
|
|
|
80
102
|
if (sel) { sel.removeAllRanges(); sel.addRange(range); }
|
|
81
103
|
}
|
|
82
104
|
}`,
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
105
|
+
objectId,
|
|
106
|
+
returnByValue: true,
|
|
107
|
+
},
|
|
108
|
+
sessionId,
|
|
109
|
+
);
|
|
86
110
|
if (fillSelectResult.exceptionDetails) {
|
|
87
|
-
throw new Error(
|
|
111
|
+
throw new Error(
|
|
112
|
+
`fillElement select-all failed: ${fillSelectResult.exceptionDetails.exception?.description ?? fillSelectResult.exceptionDetails.text}`,
|
|
113
|
+
);
|
|
88
114
|
}
|
|
89
115
|
} else if (box) {
|
|
90
116
|
// Fallback for elements without a resolvable objectId: keyboard select-all clears existing content
|
|
@@ -108,42 +134,62 @@ export async function pressKeyCombo(
|
|
|
108
134
|
client: CdpClient,
|
|
109
135
|
sessionId: string,
|
|
110
136
|
key: string,
|
|
111
|
-
modifiers: number
|
|
137
|
+
modifiers: number,
|
|
112
138
|
): Promise<void> {
|
|
113
139
|
const { text: _text, ...keyInfo } = resolveKey(key);
|
|
114
140
|
// rawKeyDown prevents Chrome from inserting the character text; only the shortcut fires
|
|
115
|
-
await client.send(
|
|
141
|
+
await client.send(
|
|
142
|
+
'Input.dispatchKeyEvent',
|
|
143
|
+
{ type: 'rawKeyDown', ...keyInfo, modifiers },
|
|
144
|
+
sessionId,
|
|
145
|
+
);
|
|
116
146
|
await client.send('Input.dispatchKeyEvent', { type: 'keyUp', ...keyInfo, modifiers }, sessionId);
|
|
117
147
|
}
|
|
118
148
|
|
|
119
|
-
export async function pressKey(
|
|
120
|
-
client: CdpClient,
|
|
121
|
-
sessionId: string,
|
|
122
|
-
key: string
|
|
123
|
-
): Promise<void> {
|
|
149
|
+
export async function pressKey(client: CdpClient, sessionId: string, key: string): Promise<void> {
|
|
124
150
|
const { text, ...keyInfo } = resolveKey(key);
|
|
125
151
|
|
|
126
152
|
// rawKeyDown does not insert text; the explicit char event handles insertion
|
|
127
|
-
await client.send(
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
153
|
+
await client.send(
|
|
154
|
+
'Input.dispatchKeyEvent',
|
|
155
|
+
{
|
|
156
|
+
type: 'rawKeyDown',
|
|
157
|
+
...keyInfo,
|
|
158
|
+
},
|
|
159
|
+
sessionId,
|
|
160
|
+
);
|
|
131
161
|
|
|
132
162
|
if (text) {
|
|
133
|
-
await client.send(
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
163
|
+
await client.send(
|
|
164
|
+
'Input.dispatchKeyEvent',
|
|
165
|
+
{
|
|
166
|
+
type: 'char',
|
|
167
|
+
text,
|
|
168
|
+
},
|
|
169
|
+
sessionId,
|
|
170
|
+
);
|
|
137
171
|
}
|
|
138
172
|
|
|
139
|
-
await client.send(
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
173
|
+
await client.send(
|
|
174
|
+
'Input.dispatchKeyEvent',
|
|
175
|
+
{
|
|
176
|
+
type: 'keyUp',
|
|
177
|
+
...keyInfo,
|
|
178
|
+
},
|
|
179
|
+
sessionId,
|
|
180
|
+
);
|
|
143
181
|
}
|
|
144
182
|
|
|
145
|
-
function resolveKey(key: string): {
|
|
146
|
-
|
|
183
|
+
function resolveKey(key: string): {
|
|
184
|
+
key: string;
|
|
185
|
+
code: string;
|
|
186
|
+
text?: string;
|
|
187
|
+
windowsVirtualKeyCode?: number;
|
|
188
|
+
} {
|
|
189
|
+
const keyMap: Record<
|
|
190
|
+
string,
|
|
191
|
+
{ key: string; code: string; text?: string; windowsVirtualKeyCode?: number }
|
|
192
|
+
> = {
|
|
147
193
|
Enter: { key: 'Enter', code: 'Enter', text: '\r', windowsVirtualKeyCode: 13 },
|
|
148
194
|
Tab: { key: 'Tab', code: 'Tab', windowsVirtualKeyCode: 9 },
|
|
149
195
|
Escape: { key: 'Escape', code: 'Escape', windowsVirtualKeyCode: 27 },
|
|
@@ -186,14 +232,19 @@ function resolveKey(key: string): { key: string; code: string; text?: string; wi
|
|
|
186
232
|
return { key, code: `Digit${key}`, text: key, windowsVirtualKeyCode: charCode };
|
|
187
233
|
}
|
|
188
234
|
if ((charCode >= 65 && charCode <= 90) || (charCode >= 97 && charCode <= 122)) {
|
|
189
|
-
return {
|
|
235
|
+
return {
|
|
236
|
+
key,
|
|
237
|
+
code: `Key${key.toUpperCase()}`,
|
|
238
|
+
text: key,
|
|
239
|
+
windowsVirtualKeyCode: key.toUpperCase().charCodeAt(0),
|
|
240
|
+
};
|
|
190
241
|
}
|
|
191
242
|
// Symbol keys: map to the physical key's code and Windows virtual key code
|
|
192
243
|
const symbolMap: Record<string, { code: string; windowsVirtualKeyCode: number }> = {
|
|
193
244
|
'!': { code: 'Digit1', windowsVirtualKeyCode: 49 },
|
|
194
245
|
'@': { code: 'Digit2', windowsVirtualKeyCode: 50 },
|
|
195
246
|
'#': { code: 'Digit3', windowsVirtualKeyCode: 51 },
|
|
196
|
-
|
|
247
|
+
$: { code: 'Digit4', windowsVirtualKeyCode: 52 },
|
|
197
248
|
'%': { code: 'Digit5', windowsVirtualKeyCode: 53 },
|
|
198
249
|
'^': { code: 'Digit6', windowsVirtualKeyCode: 54 },
|
|
199
250
|
'&': { code: 'Digit7', windowsVirtualKeyCode: 55 },
|
|
@@ -201,7 +252,7 @@ function resolveKey(key: string): { key: string; code: string; text?: string; wi
|
|
|
201
252
|
'(': { code: 'Digit9', windowsVirtualKeyCode: 57 },
|
|
202
253
|
')': { code: 'Digit0', windowsVirtualKeyCode: 48 },
|
|
203
254
|
'-': { code: 'Minus', windowsVirtualKeyCode: 189 },
|
|
204
|
-
|
|
255
|
+
_: { code: 'Minus', windowsVirtualKeyCode: 189 },
|
|
205
256
|
'=': { code: 'Equal', windowsVirtualKeyCode: 187 },
|
|
206
257
|
'+': { code: 'Equal', windowsVirtualKeyCode: 187 },
|
|
207
258
|
'[': { code: 'BracketLeft', windowsVirtualKeyCode: 219 },
|
|
@@ -224,7 +275,8 @@ function resolveKey(key: string): { key: string; code: string; text?: string; wi
|
|
|
224
275
|
'?': { code: 'Slash', windowsVirtualKeyCode: 191 },
|
|
225
276
|
};
|
|
226
277
|
const sym = symbolMap[key];
|
|
227
|
-
if (sym)
|
|
278
|
+
if (sym)
|
|
279
|
+
return { key, code: sym.code, text: key, windowsVirtualKeyCode: sym.windowsVirtualKeyCode };
|
|
228
280
|
// Unknown/non-ASCII character — omit code since no valid DOM KeyboardEvent.code exists
|
|
229
281
|
return { key, code: '', text: key };
|
|
230
282
|
}
|
|
@@ -237,7 +289,7 @@ export async function scrollElement(
|
|
|
237
289
|
sessionId: string,
|
|
238
290
|
direction: 'up' | 'down' | 'left' | 'right',
|
|
239
291
|
amount = 300,
|
|
240
|
-
ref?: ElementRef
|
|
292
|
+
ref?: ElementRef,
|
|
241
293
|
): Promise<void> {
|
|
242
294
|
// Cap scroll amount to prevent extreme delta values
|
|
243
295
|
amount = Math.min(Math.max(1, Math.floor(amount)), 100_000);
|
|
@@ -249,60 +301,86 @@ export async function scrollElement(
|
|
|
249
301
|
if (ref) {
|
|
250
302
|
const box = await getElementBox(client, sessionId, ref);
|
|
251
303
|
if (!box) throw new Error(`Cannot scroll: ref @${ref.ref} not found in DOM`);
|
|
252
|
-
x = box.x;
|
|
304
|
+
x = box.x;
|
|
305
|
+
y = box.y;
|
|
253
306
|
} else {
|
|
254
307
|
// Center of viewport
|
|
255
|
-
const vp = await client.send<{ result: { value: string } }>(
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
308
|
+
const vp = await client.send<{ result: { value: string } }>(
|
|
309
|
+
'Runtime.evaluate',
|
|
310
|
+
{
|
|
311
|
+
expression: 'JSON.stringify({w: window.innerWidth, h: window.innerHeight})',
|
|
312
|
+
returnByValue: true,
|
|
313
|
+
},
|
|
314
|
+
sessionId,
|
|
315
|
+
);
|
|
259
316
|
const dims = JSON.parse(vp.result?.value ?? '{"w":1280,"h":720}');
|
|
260
317
|
x = dims.w / 2;
|
|
261
318
|
y = dims.h / 2;
|
|
262
319
|
}
|
|
263
320
|
|
|
264
321
|
switch (direction) {
|
|
265
|
-
case 'down':
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
case '
|
|
322
|
+
case 'down':
|
|
323
|
+
deltaY = amount;
|
|
324
|
+
break;
|
|
325
|
+
case 'up':
|
|
326
|
+
deltaY = -amount;
|
|
327
|
+
break;
|
|
328
|
+
case 'right':
|
|
329
|
+
deltaX = amount;
|
|
330
|
+
break;
|
|
331
|
+
case 'left':
|
|
332
|
+
deltaX = -amount;
|
|
333
|
+
break;
|
|
269
334
|
}
|
|
270
335
|
|
|
271
|
-
await client.send(
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
336
|
+
await client.send(
|
|
337
|
+
'Input.dispatchMouseEvent',
|
|
338
|
+
{
|
|
339
|
+
type: 'mouseWheel',
|
|
340
|
+
x,
|
|
341
|
+
y,
|
|
342
|
+
deltaX,
|
|
343
|
+
deltaY,
|
|
344
|
+
},
|
|
345
|
+
sessionId,
|
|
346
|
+
);
|
|
278
347
|
}
|
|
279
348
|
|
|
280
349
|
export async function hoverElement(
|
|
281
350
|
client: CdpClient,
|
|
282
351
|
sessionId: string,
|
|
283
|
-
ref: ElementRef
|
|
352
|
+
ref: ElementRef,
|
|
284
353
|
): Promise<void> {
|
|
285
354
|
const box = await getElementBox(client, sessionId, ref);
|
|
286
355
|
if (!box) throw new Error(`Cannot hover ref @${ref.ref}: element not found in DOM`);
|
|
287
356
|
|
|
288
|
-
await client.send(
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
357
|
+
await client.send(
|
|
358
|
+
'Input.dispatchMouseEvent',
|
|
359
|
+
{
|
|
360
|
+
type: 'mouseMoved',
|
|
361
|
+
x: box.x,
|
|
362
|
+
y: box.y,
|
|
363
|
+
},
|
|
364
|
+
sessionId,
|
|
365
|
+
);
|
|
293
366
|
}
|
|
294
367
|
|
|
295
368
|
export async function selectOption(
|
|
296
369
|
client: CdpClient,
|
|
297
370
|
sessionId: string,
|
|
298
371
|
ref: ElementRef,
|
|
299
|
-
value: string
|
|
372
|
+
value: string,
|
|
300
373
|
): Promise<void> {
|
|
301
374
|
const objectId = await getObjectIdForRef(client, sessionId, ref);
|
|
302
375
|
if (!objectId) throw new Error(`Cannot select: ref @${ref.ref} not found in DOM`);
|
|
303
376
|
|
|
304
|
-
const selectResult = await client.send<{
|
|
305
|
-
|
|
377
|
+
const selectResult = await client.send<{
|
|
378
|
+
result: unknown;
|
|
379
|
+
exceptionDetails?: { text: string; exception?: { description?: string } };
|
|
380
|
+
}>(
|
|
381
|
+
'Runtime.callFunctionOn',
|
|
382
|
+
{
|
|
383
|
+
functionDeclaration: `function(value) {
|
|
306
384
|
if (this.tagName !== 'SELECT') throw new Error('Not a select element');
|
|
307
385
|
for (const opt of this.options) {
|
|
308
386
|
if (opt.value === value || opt.textContent.trim() === value) {
|
|
@@ -313,12 +391,16 @@ export async function selectOption(
|
|
|
313
391
|
}
|
|
314
392
|
throw new Error('Option not found: ' + value);
|
|
315
393
|
}`,
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
394
|
+
objectId,
|
|
395
|
+
arguments: [{ value }],
|
|
396
|
+
returnByValue: true,
|
|
397
|
+
},
|
|
398
|
+
sessionId,
|
|
399
|
+
);
|
|
320
400
|
if (selectResult.exceptionDetails) {
|
|
321
|
-
throw new Error(
|
|
401
|
+
throw new Error(
|
|
402
|
+
`selectOption failed: ${selectResult.exceptionDetails.exception?.description ?? selectResult.exceptionDetails.text}`,
|
|
403
|
+
);
|
|
322
404
|
}
|
|
323
405
|
}
|
|
324
406
|
|
|
@@ -326,34 +408,51 @@ export async function checkElement(
|
|
|
326
408
|
client: CdpClient,
|
|
327
409
|
sessionId: string,
|
|
328
410
|
ref: ElementRef,
|
|
329
|
-
checked = true
|
|
411
|
+
checked = true,
|
|
330
412
|
): Promise<void> {
|
|
331
413
|
const objectId = await getObjectIdForRef(client, sessionId, ref);
|
|
332
414
|
if (!objectId) throw new Error(`Cannot check: ref @${ref.ref} not found in DOM`);
|
|
333
415
|
|
|
334
|
-
const checkResult = await client.send<{
|
|
335
|
-
|
|
416
|
+
const checkResult = await client.send<{
|
|
417
|
+
result: unknown;
|
|
418
|
+
exceptionDetails?: { text: string; exception?: { description?: string } };
|
|
419
|
+
}>(
|
|
420
|
+
'Runtime.callFunctionOn',
|
|
421
|
+
{
|
|
422
|
+
functionDeclaration: `function(checked) {
|
|
336
423
|
if (this.checked !== checked) {
|
|
337
424
|
this.click();
|
|
338
425
|
}
|
|
339
426
|
}`,
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
427
|
+
objectId,
|
|
428
|
+
arguments: [{ value: checked }],
|
|
429
|
+
returnByValue: true,
|
|
430
|
+
},
|
|
431
|
+
sessionId,
|
|
432
|
+
);
|
|
344
433
|
if (checkResult.exceptionDetails) {
|
|
345
|
-
throw new Error(
|
|
434
|
+
throw new Error(
|
|
435
|
+
`checkElement failed: ${checkResult.exceptionDetails.exception?.description ?? checkResult.exceptionDetails.text}`,
|
|
436
|
+
);
|
|
346
437
|
}
|
|
347
438
|
}
|
|
348
439
|
|
|
349
|
-
export async function focusElement(
|
|
440
|
+
export async function focusElement(
|
|
441
|
+
client: CdpClient,
|
|
442
|
+
sessionId: string,
|
|
443
|
+
ref: ElementRef,
|
|
444
|
+
): Promise<void> {
|
|
350
445
|
const objectId = await getObjectIdForRef(client, sessionId, ref);
|
|
351
446
|
if (objectId) {
|
|
352
|
-
await client.send(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
447
|
+
await client.send(
|
|
448
|
+
'Runtime.callFunctionOn',
|
|
449
|
+
{
|
|
450
|
+
functionDeclaration: 'function() { this.focus(); }',
|
|
451
|
+
objectId,
|
|
452
|
+
returnByValue: true,
|
|
453
|
+
},
|
|
454
|
+
sessionId,
|
|
455
|
+
);
|
|
357
456
|
return;
|
|
358
457
|
}
|
|
359
458
|
const box = await getElementBox(client, sessionId, ref);
|
|
@@ -368,7 +467,7 @@ export async function typeIntoElement(
|
|
|
368
467
|
client: CdpClient,
|
|
369
468
|
sessionId: string,
|
|
370
469
|
ref: ElementRef,
|
|
371
|
-
text: string
|
|
470
|
+
text: string,
|
|
372
471
|
): Promise<void> {
|
|
373
472
|
await focusElement(client, sessionId, ref);
|
|
374
473
|
await typeText(client, sessionId, text);
|
|
@@ -384,7 +483,12 @@ export async function keyUp(client: CdpClient, sessionId: string, key: string):
|
|
|
384
483
|
await client.send('Input.dispatchKeyEvent', { type: 'keyUp', ...keyInfo }, sessionId);
|
|
385
484
|
}
|
|
386
485
|
|
|
387
|
-
export async function mouseMove(
|
|
486
|
+
export async function mouseMove(
|
|
487
|
+
client: CdpClient,
|
|
488
|
+
sessionId: string,
|
|
489
|
+
x: number,
|
|
490
|
+
y: number,
|
|
491
|
+
): Promise<void> {
|
|
388
492
|
await client.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x, y }, sessionId);
|
|
389
493
|
}
|
|
390
494
|
|
|
@@ -393,10 +497,14 @@ export async function mouseDown(
|
|
|
393
497
|
sessionId: string,
|
|
394
498
|
x: number,
|
|
395
499
|
y: number,
|
|
396
|
-
button: 'left' | 'right' | 'middle' = 'left'
|
|
500
|
+
button: 'left' | 'right' | 'middle' = 'left',
|
|
397
501
|
): Promise<void> {
|
|
398
502
|
const buttonsMask = button === 'right' ? 2 : button === 'middle' ? 4 : 1;
|
|
399
|
-
await client.send(
|
|
503
|
+
await client.send(
|
|
504
|
+
'Input.dispatchMouseEvent',
|
|
505
|
+
{ type: 'mousePressed', x, y, button, buttons: buttonsMask, clickCount: 1 },
|
|
506
|
+
sessionId,
|
|
507
|
+
);
|
|
400
508
|
}
|
|
401
509
|
|
|
402
510
|
export async function mouseUp(
|
|
@@ -404,9 +512,13 @@ export async function mouseUp(
|
|
|
404
512
|
sessionId: string,
|
|
405
513
|
x: number,
|
|
406
514
|
y: number,
|
|
407
|
-
button: 'left' | 'right' | 'middle' = 'left'
|
|
515
|
+
button: 'left' | 'right' | 'middle' = 'left',
|
|
408
516
|
): Promise<void> {
|
|
409
|
-
await client.send(
|
|
517
|
+
await client.send(
|
|
518
|
+
'Input.dispatchMouseEvent',
|
|
519
|
+
{ type: 'mouseReleased', x, y, button, buttons: 0, clickCount: 1 },
|
|
520
|
+
sessionId,
|
|
521
|
+
);
|
|
410
522
|
}
|
|
411
523
|
|
|
412
524
|
export async function mouseWheel(
|
|
@@ -415,47 +527,71 @@ export async function mouseWheel(
|
|
|
415
527
|
x: number,
|
|
416
528
|
y: number,
|
|
417
529
|
deltaY: number,
|
|
418
|
-
deltaX = 0
|
|
530
|
+
deltaX = 0,
|
|
419
531
|
): Promise<void> {
|
|
420
|
-
await client.send(
|
|
532
|
+
await client.send(
|
|
533
|
+
'Input.dispatchMouseEvent',
|
|
534
|
+
{ type: 'mouseWheel', x, y, deltaX, deltaY },
|
|
535
|
+
sessionId,
|
|
536
|
+
);
|
|
421
537
|
}
|
|
422
538
|
|
|
423
539
|
export async function dragAndDrop(
|
|
424
540
|
client: CdpClient,
|
|
425
541
|
sessionId: string,
|
|
426
542
|
src: ElementRef,
|
|
427
|
-
tgt: ElementRef
|
|
543
|
+
tgt: ElementRef,
|
|
428
544
|
): Promise<void> {
|
|
429
545
|
const srcBox = await getElementBox(client, sessionId, src);
|
|
430
546
|
const tgtBox = await getElementBox(client, sessionId, tgt);
|
|
431
547
|
if (!srcBox || !tgtBox) throw new Error('Cannot drag: one or both elements not found in DOM');
|
|
432
548
|
|
|
433
|
-
await client.send(
|
|
434
|
-
|
|
549
|
+
await client.send(
|
|
550
|
+
'Input.dispatchMouseEvent',
|
|
551
|
+
{ type: 'mousePressed', x: srcBox.x, y: srcBox.y, button: 'left', buttons: 1, clickCount: 1 },
|
|
552
|
+
sessionId,
|
|
553
|
+
);
|
|
554
|
+
await client.send(
|
|
555
|
+
'Input.dispatchMouseEvent',
|
|
556
|
+
{ type: 'mouseMoved', x: srcBox.x, y: srcBox.y, button: 'left', buttons: 1 },
|
|
557
|
+
sessionId,
|
|
558
|
+
);
|
|
435
559
|
|
|
436
560
|
// Move in steps for smooth drag
|
|
437
561
|
const steps = 10;
|
|
438
562
|
for (let i = 1; i <= steps; i++) {
|
|
439
563
|
const x = srcBox.x + (tgtBox.x - srcBox.x) * (i / steps);
|
|
440
564
|
const y = srcBox.y + (tgtBox.y - srcBox.y) * (i / steps);
|
|
441
|
-
await client.send(
|
|
565
|
+
await client.send(
|
|
566
|
+
'Input.dispatchMouseEvent',
|
|
567
|
+
{ type: 'mouseMoved', x, y, button: 'left', buttons: 1 },
|
|
568
|
+
sessionId,
|
|
569
|
+
);
|
|
442
570
|
}
|
|
443
571
|
|
|
444
|
-
await client.send(
|
|
572
|
+
await client.send(
|
|
573
|
+
'Input.dispatchMouseEvent',
|
|
574
|
+
{ type: 'mouseReleased', x: tgtBox.x, y: tgtBox.y, button: 'left', buttons: 0, clickCount: 1 },
|
|
575
|
+
sessionId,
|
|
576
|
+
);
|
|
445
577
|
}
|
|
446
578
|
|
|
447
579
|
export async function uploadFile(
|
|
448
580
|
client: CdpClient,
|
|
449
581
|
sessionId: string,
|
|
450
582
|
ref: ElementRef,
|
|
451
|
-
filePaths: string[]
|
|
583
|
+
filePaths: string[],
|
|
452
584
|
): Promise<void> {
|
|
453
585
|
if (!ref.backendDOMNodeId) throw new Error(`Cannot upload: ref @${ref.ref} has no DOM node`);
|
|
454
586
|
|
|
455
|
-
await client.send(
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
587
|
+
await client.send(
|
|
588
|
+
'DOM.setFileInputFiles',
|
|
589
|
+
{
|
|
590
|
+
files: filePaths.map((f) => resolve(f)),
|
|
591
|
+
backendNodeId: ref.backendDOMNodeId,
|
|
592
|
+
},
|
|
593
|
+
sessionId,
|
|
594
|
+
);
|
|
459
595
|
}
|
|
460
596
|
|
|
461
597
|
export async function readClipboard(client: CdpClient, sessionId: string): Promise<string> {
|
|
@@ -463,7 +599,11 @@ export async function readClipboard(client: CdpClient, sessionId: string): Promi
|
|
|
463
599
|
return result as string;
|
|
464
600
|
}
|
|
465
601
|
|
|
466
|
-
export async function writeClipboard(
|
|
602
|
+
export async function writeClipboard(
|
|
603
|
+
client: CdpClient,
|
|
604
|
+
sessionId: string,
|
|
605
|
+
text: string,
|
|
606
|
+
): Promise<void> {
|
|
467
607
|
// Cap to 100 KB to prevent OOM when serializing the CDP expression
|
|
468
608
|
const safeText = text.length > 102_400 ? text.slice(0, 102_400) : text;
|
|
469
609
|
await evaluateJs(client, sessionId, `navigator.clipboard.writeText(${JSON.stringify(safeText)})`);
|
|
@@ -471,7 +611,10 @@ export async function writeClipboard(client: CdpClient, sessionId: string, text:
|
|
|
471
611
|
|
|
472
612
|
export async function pushState(client: CdpClient, sessionId: string, url: string): Promise<void> {
|
|
473
613
|
// Try Next.js router first, then fallback to history.pushState
|
|
474
|
-
await evaluateJs(
|
|
614
|
+
await evaluateJs(
|
|
615
|
+
client,
|
|
616
|
+
sessionId,
|
|
617
|
+
`
|
|
475
618
|
(function() {
|
|
476
619
|
const url = ${JSON.stringify(url)};
|
|
477
620
|
if (window.next && window.next.router) {
|
|
@@ -482,17 +625,30 @@ export async function pushState(client: CdpClient, sessionId: string, url: strin
|
|
|
482
625
|
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
483
626
|
}
|
|
484
627
|
})()
|
|
485
|
-
|
|
628
|
+
`,
|
|
629
|
+
);
|
|
486
630
|
}
|
|
487
631
|
|
|
488
|
-
export async function addInitScript(
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
632
|
+
export async function addInitScript(
|
|
633
|
+
client: CdpClient,
|
|
634
|
+
sessionId: string,
|
|
635
|
+
script: string,
|
|
636
|
+
): Promise<string> {
|
|
637
|
+
const result = await client.send<{ identifier: string }>(
|
|
638
|
+
'Page.addScriptToEvaluateOnNewDocument',
|
|
639
|
+
{
|
|
640
|
+
source: script,
|
|
641
|
+
},
|
|
642
|
+
sessionId,
|
|
643
|
+
);
|
|
492
644
|
return result.identifier;
|
|
493
645
|
}
|
|
494
646
|
|
|
495
|
-
export async function removeInitScript(
|
|
647
|
+
export async function removeInitScript(
|
|
648
|
+
client: CdpClient,
|
|
649
|
+
sessionId: string,
|
|
650
|
+
identifier: string,
|
|
651
|
+
): Promise<void> {
|
|
496
652
|
await client.send('Page.removeScriptToEvaluateOnNewDocument', { identifier }, sessionId);
|
|
497
653
|
}
|
|
498
654
|
|
|
@@ -509,29 +665,38 @@ export async function evaluateJs(
|
|
|
509
665
|
client: CdpClient,
|
|
510
666
|
sessionId: string,
|
|
511
667
|
expression: string,
|
|
512
|
-
timeoutMs: number = DEFAULT_EVAL_TIMEOUT_MS
|
|
668
|
+
timeoutMs: number = DEFAULT_EVAL_TIMEOUT_MS,
|
|
513
669
|
): Promise<unknown> {
|
|
514
670
|
const evalPromise = client.send<{
|
|
515
671
|
result: { value?: unknown; type: string; description?: string };
|
|
516
672
|
exceptionDetails?: { text: string; exception?: { description?: string } };
|
|
517
|
-
}>(
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
673
|
+
}>(
|
|
674
|
+
'Runtime.evaluate',
|
|
675
|
+
{
|
|
676
|
+
expression,
|
|
677
|
+
returnByValue: true,
|
|
678
|
+
awaitPromise: true,
|
|
679
|
+
},
|
|
680
|
+
sessionId,
|
|
681
|
+
);
|
|
522
682
|
|
|
523
683
|
const result = await (timeoutMs > 0
|
|
524
684
|
? Promise.race([
|
|
525
685
|
evalPromise,
|
|
526
686
|
new Promise<never>((_, reject) => {
|
|
527
|
-
const t = setTimeout(
|
|
687
|
+
const t = setTimeout(
|
|
688
|
+
() => reject(new Error(`JS evaluation timed out after ${timeoutMs}ms`)),
|
|
689
|
+
timeoutMs,
|
|
690
|
+
);
|
|
528
691
|
t.unref?.();
|
|
529
692
|
}),
|
|
530
693
|
])
|
|
531
694
|
: evalPromise);
|
|
532
695
|
|
|
533
696
|
if (result.exceptionDetails) {
|
|
534
|
-
throw new Error(
|
|
697
|
+
throw new Error(
|
|
698
|
+
`JS evaluation error: ${result.exceptionDetails.exception?.description ?? result.exceptionDetails.text}`,
|
|
699
|
+
);
|
|
535
700
|
}
|
|
536
701
|
|
|
537
702
|
return result.result?.value;
|