monomind 1.10.39 → 1.10.41
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/package.json +1 -1
- package/packages/@monomind/cli/dist/src/browser/actions.js +114 -22
- package/packages/@monomind/cli/dist/src/browser/batch.d.ts +0 -2
- package/packages/@monomind/cli/dist/src/browser/batch.js +1 -10
- package/packages/@monomind/cli/dist/src/browser/browser.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/browser/browser.js +51 -24
- package/packages/@monomind/cli/dist/src/browser/cdp.js +21 -4
- package/packages/@monomind/cli/dist/src/browser/console-log.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/browser/console-log.js +19 -3
- package/packages/@monomind/cli/dist/src/browser/dialog.js +5 -2
- package/packages/@monomind/cli/dist/src/browser/find.js +28 -8
- package/packages/@monomind/cli/dist/src/browser/har.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/browser/har.js +7 -5
- package/packages/@monomind/cli/dist/src/browser/network.d.ts +7 -4
- package/packages/@monomind/cli/dist/src/browser/network.js +60 -23
- package/packages/@monomind/cli/dist/src/browser/profiler.js +13 -2
- package/packages/@monomind/cli/dist/src/browser/screenshot.js +4 -2
- package/packages/@monomind/cli/dist/src/browser/session.js +49 -12
- package/packages/@monomind/cli/dist/src/browser/snapshot.js +26 -14
- package/packages/@monomind/cli/dist/src/browser/storage.d.ts +1 -0
- package/packages/@monomind/cli/dist/src/browser/storage.js +3 -0
- package/packages/@monomind/cli/dist/src/browser/tabs.d.ts +3 -3
- package/packages/@monomind/cli/dist/src/browser/tabs.js +13 -13
- package/packages/@monomind/cli/dist/src/browser/trace.js +10 -4
- package/packages/@monomind/cli/dist/src/browser/wait.js +24 -14
- package/packages/@monomind/cli/dist/src/commands/browse.js +300 -34
- package/packages/@monomind/cli/package.json +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "monomind",
|
|
3
|
-
"version": "1.10.
|
|
3
|
+
"version": "1.10.41",
|
|
4
4
|
"description": "Monomind - Enterprise AI agent orchestration for Claude Code. Deploy 60+ specialized agents in coordinated swarms with self-learning, fault-tolerant consensus, vector memory, and MCP integration",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { resolve } from 'path';
|
|
1
2
|
import { getObjectIdForRef, getElementBox } from './snapshot.js';
|
|
2
3
|
export async function clickElement(client, sessionId, ref, options = {}) {
|
|
3
4
|
const box = await getElementBox(client, sessionId, ref);
|
|
@@ -11,6 +12,7 @@ export async function clickElement(client, sessionId, ref, options = {}) {
|
|
|
11
12
|
await client.send('Runtime.callFunctionOn', {
|
|
12
13
|
functionDeclaration: 'function() { this.click(); }',
|
|
13
14
|
objectId,
|
|
15
|
+
returnByValue: true,
|
|
14
16
|
}, sessionId);
|
|
15
17
|
return;
|
|
16
18
|
}
|
|
@@ -21,10 +23,11 @@ export async function clickPoint(client, sessionId, x, y, options = {}) {
|
|
|
21
23
|
const clickCount = options.clickCount ?? 1;
|
|
22
24
|
const modifiers = options.modifiers ?? 0;
|
|
23
25
|
const shared = { x, y, button, modifiers };
|
|
26
|
+
const buttonsMask = button === 'right' ? 2 : button === 'middle' ? 4 : 1;
|
|
24
27
|
for (let i = 0; i < clickCount; i++) {
|
|
25
28
|
const count = i + 1;
|
|
26
|
-
await client.send('Input.dispatchMouseEvent', { ...shared, type: 'mousePressed', buttons:
|
|
27
|
-
await client.send('Input.dispatchMouseEvent', { ...shared, type: 'mouseReleased', buttons: 0, clickCount: count }, sessionId);
|
|
29
|
+
await client.send('Input.dispatchMouseEvent', { ...shared, type: 'mousePressed', buttons: buttonsMask, clickCount: count }, sessionId);
|
|
30
|
+
await client.send('Input.dispatchMouseEvent', { ...shared, type: 'mouseReleased', buttons: i < clickCount - 1 ? buttonsMask : 0, clickCount: count }, sessionId);
|
|
28
31
|
}
|
|
29
32
|
}
|
|
30
33
|
export async function fillElement(client, sessionId, ref, value) {
|
|
@@ -36,7 +39,7 @@ export async function fillElement(client, sessionId, ref, value) {
|
|
|
36
39
|
// Select all and replace
|
|
37
40
|
const objectId = await getObjectIdForRef(client, sessionId, ref);
|
|
38
41
|
if (objectId) {
|
|
39
|
-
await client.send('Runtime.callFunctionOn', {
|
|
42
|
+
const fillSelectResult = await client.send('Runtime.callFunctionOn', {
|
|
40
43
|
functionDeclaration: `function() {
|
|
41
44
|
this.focus();
|
|
42
45
|
if (this.tagName === 'INPUT' || this.tagName === 'TEXTAREA') {
|
|
@@ -49,8 +52,19 @@ export async function fillElement(client, sessionId, ref, value) {
|
|
|
49
52
|
}
|
|
50
53
|
}`,
|
|
51
54
|
objectId,
|
|
55
|
+
returnByValue: true,
|
|
52
56
|
}, sessionId);
|
|
57
|
+
if (fillSelectResult.exceptionDetails) {
|
|
58
|
+
throw new Error(`fillElement select-all failed: ${fillSelectResult.exceptionDetails.exception?.description ?? fillSelectResult.exceptionDetails.text}`);
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
else if (box) {
|
|
62
|
+
// Fallback for elements without a resolvable objectId: keyboard select-all clears existing content
|
|
63
|
+
const mod = process.platform === 'darwin' ? 4 : 2;
|
|
64
|
+
await pressKeyCombo(client, sessionId, 'a', mod);
|
|
53
65
|
}
|
|
66
|
+
if (!objectId && !box)
|
|
67
|
+
throw new Error(`Cannot fill ref @${ref.ref}: element not found in DOM`);
|
|
54
68
|
// Type the value character by character for natural input
|
|
55
69
|
await typeText(client, sessionId, value);
|
|
56
70
|
}
|
|
@@ -64,15 +78,16 @@ export async function pressKeyCombo(client, sessionId, key, modifiers) {
|
|
|
64
78
|
await client.send('Input.dispatchKeyEvent', { type: 'keyUp', ...keyInfo, modifiers }, sessionId);
|
|
65
79
|
}
|
|
66
80
|
export async function pressKey(client, sessionId, key) {
|
|
67
|
-
const keyInfo = resolveKey(key);
|
|
81
|
+
const { text, ...keyInfo } = resolveKey(key);
|
|
82
|
+
// rawKeyDown does not insert text; the explicit char event handles insertion
|
|
68
83
|
await client.send('Input.dispatchKeyEvent', {
|
|
69
|
-
type: '
|
|
84
|
+
type: 'rawKeyDown',
|
|
70
85
|
...keyInfo,
|
|
71
86
|
}, sessionId);
|
|
72
|
-
if (
|
|
87
|
+
if (text) {
|
|
73
88
|
await client.send('Input.dispatchKeyEvent', {
|
|
74
89
|
type: 'char',
|
|
75
|
-
text
|
|
90
|
+
text,
|
|
76
91
|
}, sessionId);
|
|
77
92
|
}
|
|
78
93
|
await client.send('Input.dispatchKeyEvent', {
|
|
@@ -97,12 +112,74 @@ function resolveKey(key) {
|
|
|
97
112
|
PageDown: { key: 'PageDown', code: 'PageDown', windowsVirtualKeyCode: 34 },
|
|
98
113
|
Space: { key: ' ', code: 'Space', text: ' ', windowsVirtualKeyCode: 32 },
|
|
99
114
|
' ': { key: ' ', code: 'Space', text: ' ', windowsVirtualKeyCode: 32 },
|
|
115
|
+
Shift: { key: 'Shift', code: 'ShiftLeft', windowsVirtualKeyCode: 16 },
|
|
116
|
+
Control: { key: 'Control', code: 'ControlLeft', windowsVirtualKeyCode: 17 },
|
|
117
|
+
Alt: { key: 'Alt', code: 'AltLeft', windowsVirtualKeyCode: 18 },
|
|
118
|
+
Meta: { key: 'Meta', code: 'MetaLeft', windowsVirtualKeyCode: 91 },
|
|
119
|
+
F1: { key: 'F1', code: 'F1', windowsVirtualKeyCode: 112 },
|
|
120
|
+
F2: { key: 'F2', code: 'F2', windowsVirtualKeyCode: 113 },
|
|
121
|
+
F3: { key: 'F3', code: 'F3', windowsVirtualKeyCode: 114 },
|
|
122
|
+
F4: { key: 'F4', code: 'F4', windowsVirtualKeyCode: 115 },
|
|
123
|
+
F5: { key: 'F5', code: 'F5', windowsVirtualKeyCode: 116 },
|
|
124
|
+
F6: { key: 'F6', code: 'F6', windowsVirtualKeyCode: 117 },
|
|
125
|
+
F7: { key: 'F7', code: 'F7', windowsVirtualKeyCode: 118 },
|
|
126
|
+
F8: { key: 'F8', code: 'F8', windowsVirtualKeyCode: 119 },
|
|
127
|
+
F9: { key: 'F9', code: 'F9', windowsVirtualKeyCode: 120 },
|
|
128
|
+
F10: { key: 'F10', code: 'F10', windowsVirtualKeyCode: 121 },
|
|
129
|
+
F11: { key: 'F11', code: 'F11', windowsVirtualKeyCode: 122 },
|
|
130
|
+
F12: { key: 'F12', code: 'F12', windowsVirtualKeyCode: 123 },
|
|
100
131
|
};
|
|
101
132
|
if (keyMap[key])
|
|
102
133
|
return keyMap[key];
|
|
103
134
|
// Single printable character — windowsVirtualKeyCode required for rawKeyDown shortcut dispatch
|
|
104
135
|
if (key.length === 1) {
|
|
105
|
-
|
|
136
|
+
const charCode = key.charCodeAt(0);
|
|
137
|
+
if (charCode >= 48 && charCode <= 57) {
|
|
138
|
+
return { key, code: `Digit${key}`, text: key, windowsVirtualKeyCode: charCode };
|
|
139
|
+
}
|
|
140
|
+
if ((charCode >= 65 && charCode <= 90) || (charCode >= 97 && charCode <= 122)) {
|
|
141
|
+
return { key, code: `Key${key.toUpperCase()}`, text: key, windowsVirtualKeyCode: key.toUpperCase().charCodeAt(0) };
|
|
142
|
+
}
|
|
143
|
+
// Symbol keys: map to the physical key's code and Windows virtual key code
|
|
144
|
+
const symbolMap = {
|
|
145
|
+
'!': { code: 'Digit1', windowsVirtualKeyCode: 49 },
|
|
146
|
+
'@': { code: 'Digit2', windowsVirtualKeyCode: 50 },
|
|
147
|
+
'#': { code: 'Digit3', windowsVirtualKeyCode: 51 },
|
|
148
|
+
'$': { code: 'Digit4', windowsVirtualKeyCode: 52 },
|
|
149
|
+
'%': { code: 'Digit5', windowsVirtualKeyCode: 53 },
|
|
150
|
+
'^': { code: 'Digit6', windowsVirtualKeyCode: 54 },
|
|
151
|
+
'&': { code: 'Digit7', windowsVirtualKeyCode: 55 },
|
|
152
|
+
'*': { code: 'Digit8', windowsVirtualKeyCode: 56 },
|
|
153
|
+
'(': { code: 'Digit9', windowsVirtualKeyCode: 57 },
|
|
154
|
+
')': { code: 'Digit0', windowsVirtualKeyCode: 48 },
|
|
155
|
+
'-': { code: 'Minus', windowsVirtualKeyCode: 189 },
|
|
156
|
+
'_': { code: 'Minus', windowsVirtualKeyCode: 189 },
|
|
157
|
+
'=': { code: 'Equal', windowsVirtualKeyCode: 187 },
|
|
158
|
+
'+': { code: 'Equal', windowsVirtualKeyCode: 187 },
|
|
159
|
+
'[': { code: 'BracketLeft', windowsVirtualKeyCode: 219 },
|
|
160
|
+
'{': { code: 'BracketLeft', windowsVirtualKeyCode: 219 },
|
|
161
|
+
']': { code: 'BracketRight', windowsVirtualKeyCode: 221 },
|
|
162
|
+
'}': { code: 'BracketRight', windowsVirtualKeyCode: 221 },
|
|
163
|
+
'\\': { code: 'Backslash', windowsVirtualKeyCode: 220 },
|
|
164
|
+
'|': { code: 'Backslash', windowsVirtualKeyCode: 220 },
|
|
165
|
+
';': { code: 'Semicolon', windowsVirtualKeyCode: 186 },
|
|
166
|
+
':': { code: 'Semicolon', windowsVirtualKeyCode: 186 },
|
|
167
|
+
"'": { code: 'Quote', windowsVirtualKeyCode: 222 },
|
|
168
|
+
'"': { code: 'Quote', windowsVirtualKeyCode: 222 },
|
|
169
|
+
'`': { code: 'Backquote', windowsVirtualKeyCode: 192 },
|
|
170
|
+
'~': { code: 'Backquote', windowsVirtualKeyCode: 192 },
|
|
171
|
+
',': { code: 'Comma', windowsVirtualKeyCode: 188 },
|
|
172
|
+
'<': { code: 'Comma', windowsVirtualKeyCode: 188 },
|
|
173
|
+
'.': { code: 'Period', windowsVirtualKeyCode: 190 },
|
|
174
|
+
'>': { code: 'Period', windowsVirtualKeyCode: 190 },
|
|
175
|
+
'/': { code: 'Slash', windowsVirtualKeyCode: 191 },
|
|
176
|
+
'?': { code: 'Slash', windowsVirtualKeyCode: 191 },
|
|
177
|
+
};
|
|
178
|
+
const sym = symbolMap[key];
|
|
179
|
+
if (sym)
|
|
180
|
+
return { key, code: sym.code, text: key, windowsVirtualKeyCode: sym.windowsVirtualKeyCode };
|
|
181
|
+
// Unknown/non-ASCII character — omit code since no valid DOM KeyboardEvent.code exists
|
|
182
|
+
return { key, code: '', text: key };
|
|
106
183
|
}
|
|
107
184
|
return { key, code: key };
|
|
108
185
|
}
|
|
@@ -113,10 +190,10 @@ export async function scrollElement(client, sessionId, direction, amount = 300,
|
|
|
113
190
|
let deltaY = 0;
|
|
114
191
|
if (ref) {
|
|
115
192
|
const box = await getElementBox(client, sessionId, ref);
|
|
116
|
-
if (box)
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
193
|
+
if (!box)
|
|
194
|
+
throw new Error(`Cannot scroll: ref @${ref.ref} not found in DOM`);
|
|
195
|
+
x = box.x;
|
|
196
|
+
y = box.y;
|
|
120
197
|
}
|
|
121
198
|
else {
|
|
122
199
|
// Center of viewport
|
|
@@ -164,7 +241,7 @@ export async function selectOption(client, sessionId, ref, value) {
|
|
|
164
241
|
const objectId = await getObjectIdForRef(client, sessionId, ref);
|
|
165
242
|
if (!objectId)
|
|
166
243
|
throw new Error(`Cannot select: ref @${ref.ref} not found in DOM`);
|
|
167
|
-
await client.send('Runtime.callFunctionOn', {
|
|
244
|
+
const selectResult = await client.send('Runtime.callFunctionOn', {
|
|
168
245
|
functionDeclaration: `function(value) {
|
|
169
246
|
if (this.tagName !== 'SELECT') throw new Error('Not a select element');
|
|
170
247
|
for (const opt of this.options) {
|
|
@@ -178,13 +255,17 @@ export async function selectOption(client, sessionId, ref, value) {
|
|
|
178
255
|
}`,
|
|
179
256
|
objectId,
|
|
180
257
|
arguments: [{ value }],
|
|
258
|
+
returnByValue: true,
|
|
181
259
|
}, sessionId);
|
|
260
|
+
if (selectResult.exceptionDetails) {
|
|
261
|
+
throw new Error(`selectOption failed: ${selectResult.exceptionDetails.exception?.description ?? selectResult.exceptionDetails.text}`);
|
|
262
|
+
}
|
|
182
263
|
}
|
|
183
264
|
export async function checkElement(client, sessionId, ref, checked = true) {
|
|
184
265
|
const objectId = await getObjectIdForRef(client, sessionId, ref);
|
|
185
266
|
if (!objectId)
|
|
186
267
|
throw new Error(`Cannot check: ref @${ref.ref} not found in DOM`);
|
|
187
|
-
await client.send('Runtime.callFunctionOn', {
|
|
268
|
+
const checkResult = await client.send('Runtime.callFunctionOn', {
|
|
188
269
|
functionDeclaration: `function(checked) {
|
|
189
270
|
if (this.checked !== checked) {
|
|
190
271
|
this.click();
|
|
@@ -192,7 +273,11 @@ export async function checkElement(client, sessionId, ref, checked = true) {
|
|
|
192
273
|
}`,
|
|
193
274
|
objectId,
|
|
194
275
|
arguments: [{ value: checked }],
|
|
276
|
+
returnByValue: true,
|
|
195
277
|
}, sessionId);
|
|
278
|
+
if (checkResult.exceptionDetails) {
|
|
279
|
+
throw new Error(`checkElement failed: ${checkResult.exceptionDetails.exception?.description ?? checkResult.exceptionDetails.text}`);
|
|
280
|
+
}
|
|
196
281
|
}
|
|
197
282
|
export async function focusElement(client, sessionId, ref) {
|
|
198
283
|
const objectId = await getObjectIdForRef(client, sessionId, ref);
|
|
@@ -200,30 +285,36 @@ export async function focusElement(client, sessionId, ref) {
|
|
|
200
285
|
await client.send('Runtime.callFunctionOn', {
|
|
201
286
|
functionDeclaration: 'function() { this.focus(); }',
|
|
202
287
|
objectId,
|
|
288
|
+
returnByValue: true,
|
|
203
289
|
}, sessionId);
|
|
204
290
|
return;
|
|
205
291
|
}
|
|
206
292
|
const box = await getElementBox(client, sessionId, ref);
|
|
207
|
-
if (box)
|
|
293
|
+
if (box) {
|
|
208
294
|
await clickPoint(client, sessionId, box.x, box.y);
|
|
295
|
+
}
|
|
296
|
+
else {
|
|
297
|
+
throw new Error(`Cannot focus ref @${ref.ref}: element not found in DOM`);
|
|
298
|
+
}
|
|
209
299
|
}
|
|
210
300
|
export async function typeIntoElement(client, sessionId, ref, text) {
|
|
211
301
|
await focusElement(client, sessionId, ref);
|
|
212
302
|
await typeText(client, sessionId, text);
|
|
213
303
|
}
|
|
214
304
|
export async function keyDown(client, sessionId, key) {
|
|
215
|
-
const keyInfo = resolveKey(key);
|
|
216
|
-
await client.send('Input.dispatchKeyEvent', { type: '
|
|
305
|
+
const { text: _text, ...keyInfo } = resolveKey(key);
|
|
306
|
+
await client.send('Input.dispatchKeyEvent', { type: 'rawKeyDown', ...keyInfo }, sessionId);
|
|
217
307
|
}
|
|
218
308
|
export async function keyUp(client, sessionId, key) {
|
|
219
|
-
const keyInfo = resolveKey(key);
|
|
309
|
+
const { text: _text, ...keyInfo } = resolveKey(key);
|
|
220
310
|
await client.send('Input.dispatchKeyEvent', { type: 'keyUp', ...keyInfo }, sessionId);
|
|
221
311
|
}
|
|
222
312
|
export async function mouseMove(client, sessionId, x, y) {
|
|
223
313
|
await client.send('Input.dispatchMouseEvent', { type: 'mouseMoved', x, y }, sessionId);
|
|
224
314
|
}
|
|
225
315
|
export async function mouseDown(client, sessionId, x, y, button = 'left') {
|
|
226
|
-
|
|
316
|
+
const buttonsMask = button === 'right' ? 2 : button === 'middle' ? 4 : 1;
|
|
317
|
+
await client.send('Input.dispatchMouseEvent', { type: 'mousePressed', x, y, button, buttons: buttonsMask, clickCount: 1 }, sessionId);
|
|
227
318
|
}
|
|
228
319
|
export async function mouseUp(client, sessionId, x, y, button = 'left') {
|
|
229
320
|
await client.send('Input.dispatchMouseEvent', { type: 'mouseReleased', x, y, button, buttons: 0, clickCount: 1 }, sessionId);
|
|
@@ -251,7 +342,7 @@ export async function uploadFile(client, sessionId, ref, filePaths) {
|
|
|
251
342
|
if (!ref.backendDOMNodeId)
|
|
252
343
|
throw new Error(`Cannot upload: ref @${ref.ref} has no DOM node`);
|
|
253
344
|
await client.send('DOM.setFileInputFiles', {
|
|
254
|
-
files: filePaths,
|
|
345
|
+
files: filePaths.map((f) => resolve(f)),
|
|
255
346
|
backendNodeId: ref.backendDOMNodeId,
|
|
256
347
|
}, sessionId);
|
|
257
348
|
}
|
|
@@ -268,7 +359,8 @@ export async function pushState(client, sessionId, url) {
|
|
|
268
359
|
(function() {
|
|
269
360
|
const url = ${JSON.stringify(url)};
|
|
270
361
|
if (window.next && window.next.router) {
|
|
271
|
-
window.next.router.push(url);
|
|
362
|
+
window.next.router.push(url).catch(function() {});
|
|
363
|
+
return;
|
|
272
364
|
} else {
|
|
273
365
|
history.pushState({}, '', url);
|
|
274
366
|
window.dispatchEvent(new PopStateEvent('popstate'));
|
|
@@ -292,7 +384,7 @@ export async function evaluateJs(client, sessionId, expression) {
|
|
|
292
384
|
awaitPromise: true,
|
|
293
385
|
}, sessionId);
|
|
294
386
|
if (result.exceptionDetails) {
|
|
295
|
-
throw new Error(`JS evaluation error: ${result.exceptionDetails.text}`);
|
|
387
|
+
throw new Error(`JS evaluation error: ${result.exceptionDetails.exception?.description ?? result.exceptionDetails.text}`);
|
|
296
388
|
}
|
|
297
389
|
return result.result?.value;
|
|
298
390
|
}
|
|
@@ -2,6 +2,4 @@ export interface BatchCommand {
|
|
|
2
2
|
command: string;
|
|
3
3
|
args: string[];
|
|
4
4
|
}
|
|
5
|
-
export declare function parseBatchCommands(input: string[]): Promise<BatchCommand[]>;
|
|
6
|
-
export declare function parseBatchJson(json: string): Promise<BatchCommand[]>;
|
|
7
5
|
//# sourceMappingURL=batch.d.ts.map
|
|
@@ -1,11 +1,2 @@
|
|
|
1
|
-
export
|
|
2
|
-
return input.map((raw) => {
|
|
3
|
-
const parts = raw.trim().split(/\s+/);
|
|
4
|
-
return { command: parts[0], args: parts.slice(1) };
|
|
5
|
-
});
|
|
6
|
-
}
|
|
7
|
-
export async function parseBatchJson(json) {
|
|
8
|
-
const parsed = JSON.parse(json);
|
|
9
|
-
return parsed.map(([command, ...args]) => ({ command, args }));
|
|
10
|
-
}
|
|
1
|
+
export {};
|
|
11
2
|
//# sourceMappingURL=batch.js.map
|
|
@@ -2,6 +2,7 @@ import { CdpClient } from './cdp.js';
|
|
|
2
2
|
import type { BrowserConfig, CdpTarget } from './types.js';
|
|
3
3
|
export declare function isPortOpen(port: number): Promise<boolean>;
|
|
4
4
|
export declare function launchBrowser(config?: BrowserConfig): Promise<number>;
|
|
5
|
+
export declare function enableSessionDomains(client: CdpClient, sessionId: string): Promise<void>;
|
|
5
6
|
export declare function connectToTarget(port: number, targetId?: string): Promise<{
|
|
6
7
|
client: CdpClient;
|
|
7
8
|
target: CdpTarget;
|
|
@@ -83,6 +83,18 @@ export async function launchBrowser(config = {}) {
|
|
|
83
83
|
}
|
|
84
84
|
throw new Error(`Chrome failed to start on port ${port} within ${LAUNCH_TIMEOUT}ms`);
|
|
85
85
|
}
|
|
86
|
+
export async function enableSessionDomains(client, sessionId) {
|
|
87
|
+
await Promise.all([
|
|
88
|
+
client.send('Page.enable', {}, sessionId),
|
|
89
|
+
client.send('Runtime.enable', {}, sessionId),
|
|
90
|
+
client.send('Network.enable', {}, sessionId),
|
|
91
|
+
client.send('DOM.enable', {}, sessionId),
|
|
92
|
+
client.send('Accessibility.enable', {}, sessionId),
|
|
93
|
+
]);
|
|
94
|
+
setupConsoleCapture(client, sessionId);
|
|
95
|
+
await enableConsoleCapture(client, sessionId);
|
|
96
|
+
setupDialogAutoHandling(client, sessionId);
|
|
97
|
+
}
|
|
86
98
|
export async function connectToTarget(port, targetId) {
|
|
87
99
|
const targets = await fetchTargets(port);
|
|
88
100
|
const pageTargets = targets.filter((t) => t.type === 'page');
|
|
@@ -102,34 +114,33 @@ export async function connectToTarget(port, targetId) {
|
|
|
102
114
|
const wsUrl = target.webSocketDebuggerUrl ?? `ws://127.0.0.1:${port}/devtools/page/${target.id}`;
|
|
103
115
|
const client = new CdpClient();
|
|
104
116
|
await client.connect(wsUrl);
|
|
105
|
-
// Enable required domains
|
|
106
117
|
const { sessionId } = await client.send('Target.attachToTarget', {
|
|
107
118
|
targetId: target.id,
|
|
108
119
|
flatten: true,
|
|
109
120
|
});
|
|
110
|
-
await
|
|
111
|
-
client.send('Page.enable', {}, sessionId),
|
|
112
|
-
client.send('Runtime.enable', {}, sessionId),
|
|
113
|
-
client.send('Network.enable', {}, sessionId),
|
|
114
|
-
client.send('DOM.enable', {}, sessionId),
|
|
115
|
-
client.send('Accessibility.enable', {}, sessionId),
|
|
116
|
-
]);
|
|
117
|
-
// Wire up auto-capture listeners so console/errors/dialogs work immediately
|
|
118
|
-
setupConsoleCapture(client, sessionId);
|
|
119
|
-
await enableConsoleCapture(client, sessionId);
|
|
120
|
-
setupDialogAutoHandling(client, sessionId);
|
|
121
|
+
await enableSessionDomains(client, sessionId);
|
|
121
122
|
return { client, target, sessionId };
|
|
122
123
|
}
|
|
123
124
|
export async function openUrl(client, sessionId, url) {
|
|
124
125
|
await client.send('Page.navigate', { url }, sessionId);
|
|
125
|
-
await
|
|
126
|
+
await waitForNetworkIdle(client, sessionId, 500, 30_000);
|
|
126
127
|
}
|
|
127
128
|
export async function waitForLoad(client, sessionId, condition = 'load', timeout = 30_000) {
|
|
128
129
|
if (condition === 'load' || condition === 'domcontentloaded') {
|
|
130
|
+
// Guard against race where the page loads before the listener is registered
|
|
131
|
+
const readyExpr = condition === 'load' ? 'document.readyState === "complete"' : 'document.readyState !== "loading"';
|
|
132
|
+
const readyCheck = await client.send('Runtime.evaluate', {
|
|
133
|
+
expression: readyExpr, returnByValue: true,
|
|
134
|
+
}, sessionId).catch(() => ({ result: { value: false } }));
|
|
135
|
+
if (readyCheck.result?.value)
|
|
136
|
+
return;
|
|
129
137
|
const event = condition === 'load' ? 'Page.loadEventFired' : 'Page.domContentEventFired';
|
|
130
138
|
const [eventPromise, cancelOnce] = client.onceWithOff(event, sessionId);
|
|
131
139
|
let timedOut = false;
|
|
132
|
-
|
|
140
|
+
let timeoutHandle;
|
|
141
|
+
const timeoutPromise = new Promise((resolve) => {
|
|
142
|
+
timeoutHandle = setTimeout(() => { timedOut = true; resolve(); }, timeout);
|
|
143
|
+
});
|
|
133
144
|
try {
|
|
134
145
|
await Promise.race([eventPromise, timeoutPromise]);
|
|
135
146
|
if (timedOut)
|
|
@@ -137,6 +148,7 @@ export async function waitForLoad(client, sessionId, condition = 'load', timeout
|
|
|
137
148
|
}
|
|
138
149
|
finally {
|
|
139
150
|
cancelOnce();
|
|
151
|
+
clearTimeout(timeoutHandle);
|
|
140
152
|
}
|
|
141
153
|
return;
|
|
142
154
|
}
|
|
@@ -146,6 +158,7 @@ export async function waitForLoad(client, sessionId, condition = 'load', timeout
|
|
|
146
158
|
async function waitForNetworkIdle(client, sessionId, idleMs, timeout) {
|
|
147
159
|
return new Promise((resolve, reject) => {
|
|
148
160
|
let pending = 0;
|
|
161
|
+
const inflight = new Set();
|
|
149
162
|
let idleTimer = null;
|
|
150
163
|
const killTimer = setTimeout(() => {
|
|
151
164
|
cleanup();
|
|
@@ -160,6 +173,8 @@ async function waitForNetworkIdle(client, sessionId, idleMs, timeout) {
|
|
|
160
173
|
offReq();
|
|
161
174
|
offResp();
|
|
162
175
|
offFail();
|
|
176
|
+
offCache();
|
|
177
|
+
offResp2();
|
|
163
178
|
};
|
|
164
179
|
const settle = () => {
|
|
165
180
|
cleanup();
|
|
@@ -178,23 +193,35 @@ async function waitForNetworkIdle(client, sessionId, idleMs, timeout) {
|
|
|
178
193
|
}
|
|
179
194
|
}
|
|
180
195
|
};
|
|
181
|
-
const offReq = client.on('Network.requestWillBeSent', (
|
|
182
|
-
if (sid
|
|
196
|
+
const offReq = client.on('Network.requestWillBeSent', (params, sid) => {
|
|
197
|
+
if (sid !== sessionId)
|
|
198
|
+
return;
|
|
199
|
+
const id = params.requestId;
|
|
200
|
+
if (!inflight.has(id)) {
|
|
201
|
+
inflight.add(id);
|
|
183
202
|
pending++;
|
|
184
203
|
check();
|
|
185
204
|
}
|
|
186
205
|
});
|
|
187
|
-
const
|
|
188
|
-
if (sid
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
});
|
|
193
|
-
const offFail = client.on('Network.loadingFailed', (_, sid) => {
|
|
194
|
-
if (sid === sessionId) {
|
|
206
|
+
const decrement = (params, sid) => {
|
|
207
|
+
if (sid !== sessionId)
|
|
208
|
+
return;
|
|
209
|
+
const id = params.requestId;
|
|
210
|
+
if (inflight.delete(id)) {
|
|
195
211
|
pending = Math.max(0, pending - 1);
|
|
196
212
|
check();
|
|
197
213
|
}
|
|
214
|
+
};
|
|
215
|
+
const offResp = client.on('Network.loadingFinished', decrement);
|
|
216
|
+
const offFail = client.on('Network.loadingFailed', decrement);
|
|
217
|
+
const offCache = client.on('Network.requestServedFromCache', decrement);
|
|
218
|
+
// Guard against requests that never fire loadingFinished/loadingFailed (e.g. data: URLs)
|
|
219
|
+
// Skip 3xx redirect responses — the request continues under the same requestId
|
|
220
|
+
const offResp2 = client.on('Network.responseReceived', (params, sid) => {
|
|
221
|
+
const p = params;
|
|
222
|
+
if (p.response.status >= 300 && p.response.status < 400)
|
|
223
|
+
return;
|
|
224
|
+
decrement(params, sid);
|
|
198
225
|
});
|
|
199
226
|
check();
|
|
200
227
|
});
|
|
@@ -13,8 +13,16 @@ export class CdpClient {
|
|
|
13
13
|
resolve();
|
|
14
14
|
});
|
|
15
15
|
this.ws.on('error', (err) => {
|
|
16
|
-
if (!this.connected)
|
|
16
|
+
if (!this.connected) {
|
|
17
17
|
reject(err);
|
|
18
|
+
}
|
|
19
|
+
else {
|
|
20
|
+
// Post-connect: flush all pending commands — 'close' may not fire on all platforms
|
|
21
|
+
this.connected = false;
|
|
22
|
+
for (const { reject: r } of this.pendingCommands.values())
|
|
23
|
+
r(err);
|
|
24
|
+
this.pendingCommands.clear();
|
|
25
|
+
}
|
|
18
26
|
});
|
|
19
27
|
this.ws.on('close', () => {
|
|
20
28
|
this.connected = false;
|
|
@@ -68,7 +76,12 @@ export class CdpClient {
|
|
|
68
76
|
resolve: (r) => resolve((r.result ?? {})),
|
|
69
77
|
reject,
|
|
70
78
|
});
|
|
71
|
-
this.ws.send(JSON.stringify(cmd))
|
|
79
|
+
this.ws.send(JSON.stringify(cmd), (err) => {
|
|
80
|
+
if (err) {
|
|
81
|
+
this.pendingCommands.delete(id);
|
|
82
|
+
reject(err);
|
|
83
|
+
}
|
|
84
|
+
});
|
|
72
85
|
});
|
|
73
86
|
}
|
|
74
87
|
on(event, fn) {
|
|
@@ -95,9 +108,13 @@ export class CdpClient {
|
|
|
95
108
|
return [promise, () => off()];
|
|
96
109
|
}
|
|
97
110
|
close() {
|
|
111
|
+
this.connected = false;
|
|
112
|
+
for (const { reject: r } of this.pendingCommands.values())
|
|
113
|
+
r(new Error('CDP connection closed'));
|
|
114
|
+
this.pendingCommands.clear();
|
|
98
115
|
this.ws?.close();
|
|
99
116
|
this.ws = null;
|
|
100
|
-
this.
|
|
117
|
+
this.eventListeners.clear();
|
|
101
118
|
}
|
|
102
119
|
isConnected() {
|
|
103
120
|
return this.connected;
|
|
@@ -110,7 +127,7 @@ export async function fetchTargets(port) {
|
|
|
110
127
|
return res.json();
|
|
111
128
|
}
|
|
112
129
|
export async function fetchNewTarget(port, url) {
|
|
113
|
-
const res = await fetch(`http://127.0.0.1:${port}/json/new?${
|
|
130
|
+
const res = await fetch(`http://127.0.0.1:${port}/json/new?${url}`);
|
|
114
131
|
if (!res.ok)
|
|
115
132
|
throw new Error(`Failed to create target: ${res.statusText}`);
|
|
116
133
|
return res.json();
|
|
@@ -19,4 +19,5 @@ export declare function getConsoleMessages(sessionId?: string): ConsoleMessage[]
|
|
|
19
19
|
export declare function clearConsoleMessages(sessionId?: string): void;
|
|
20
20
|
export declare function getPageErrors(sessionId?: string): PageError[];
|
|
21
21
|
export declare function clearPageErrors(sessionId?: string): void;
|
|
22
|
+
export declare function teardownConsoleCapture(sessionId: string): void;
|
|
22
23
|
//# sourceMappingURL=console-log.d.ts.map
|
|
@@ -17,6 +17,7 @@ export function setupConsoleCapture(client, sessionId) {
|
|
|
17
17
|
if (prevOffs) {
|
|
18
18
|
for (const off of prevOffs)
|
|
19
19
|
off();
|
|
20
|
+
_consoleListeners.delete(sessionId);
|
|
20
21
|
}
|
|
21
22
|
_consoleMessages.set(sessionId, []);
|
|
22
23
|
_pageErrors.set(sessionId, []);
|
|
@@ -25,8 +26,9 @@ export function setupConsoleCapture(client, sessionId) {
|
|
|
25
26
|
return;
|
|
26
27
|
const args = params.args ?? [];
|
|
27
28
|
const text = args.map((a) => a.description ?? String(a.value ?? '')).join(' ');
|
|
29
|
+
const rawType = params.type === 'warning' ? 'warn' : params.type;
|
|
28
30
|
messagesFor(sessionId).push({
|
|
29
|
-
type:
|
|
31
|
+
type: rawType ?? 'log',
|
|
30
32
|
text,
|
|
31
33
|
timestamp: Date.now(),
|
|
32
34
|
});
|
|
@@ -35,8 +37,10 @@ export function setupConsoleCapture(client, sessionId) {
|
|
|
35
37
|
if (sid !== sessionId)
|
|
36
38
|
return;
|
|
37
39
|
const entry = params.entry;
|
|
40
|
+
// CDP uses 'warning' but ConsoleMessage type uses 'warn'
|
|
41
|
+
const rawLevel = entry.level === 'warning' ? 'warn' : entry.level;
|
|
38
42
|
messagesFor(sessionId).push({
|
|
39
|
-
type:
|
|
43
|
+
type: rawLevel ?? 'log',
|
|
40
44
|
text: entry.text ?? '',
|
|
41
45
|
timestamp: Date.now(),
|
|
42
46
|
url: entry.url,
|
|
@@ -47,8 +51,9 @@ export function setupConsoleCapture(client, sessionId) {
|
|
|
47
51
|
if (sid !== sessionId)
|
|
48
52
|
return;
|
|
49
53
|
const detail = params.exceptionDetails;
|
|
54
|
+
const message = detail.exception?.description ?? detail.text ?? 'Unknown error';
|
|
50
55
|
errorsFor(sessionId).push({
|
|
51
|
-
text:
|
|
56
|
+
text: message,
|
|
52
57
|
url: detail.url,
|
|
53
58
|
lineNumber: detail.lineNumber,
|
|
54
59
|
columnNumber: detail.columnNumber,
|
|
@@ -58,6 +63,7 @@ export function setupConsoleCapture(client, sessionId) {
|
|
|
58
63
|
_consoleListeners.set(sessionId, [off1, off2, off3]);
|
|
59
64
|
}
|
|
60
65
|
export async function enableConsoleCapture(client, sessionId) {
|
|
66
|
+
await client.send('Runtime.enable', {}, sessionId);
|
|
61
67
|
await client.send('Log.enable', {}, sessionId);
|
|
62
68
|
}
|
|
63
69
|
export function getConsoleMessages(sessionId) {
|
|
@@ -85,4 +91,14 @@ export function clearPageErrors(sessionId) {
|
|
|
85
91
|
}
|
|
86
92
|
_pageErrors.clear();
|
|
87
93
|
}
|
|
94
|
+
export function teardownConsoleCapture(sessionId) {
|
|
95
|
+
const offs = _consoleListeners.get(sessionId);
|
|
96
|
+
if (offs) {
|
|
97
|
+
for (const off of offs)
|
|
98
|
+
off();
|
|
99
|
+
_consoleListeners.delete(sessionId);
|
|
100
|
+
}
|
|
101
|
+
_consoleMessages.delete(sessionId);
|
|
102
|
+
_pageErrors.delete(sessionId);
|
|
103
|
+
}
|
|
88
104
|
//# sourceMappingURL=console-log.js.map
|
|
@@ -13,8 +13,11 @@ export function setupDialogAutoHandling(client, sessionId, autoAccept = true) {
|
|
|
13
13
|
defaultPrompt: params.defaultPrompt,
|
|
14
14
|
};
|
|
15
15
|
_pendingDialogs.set(sessionId, info);
|
|
16
|
-
if (autoAccept
|
|
17
|
-
|
|
16
|
+
if (autoAccept) {
|
|
17
|
+
try {
|
|
18
|
+
await client.send('Page.handleJavaScriptDialog', { accept: true }, sessionId);
|
|
19
|
+
}
|
|
20
|
+
catch { /* dialog may have already been dismissed */ }
|
|
18
21
|
_pendingDialogs.set(sessionId, null);
|
|
19
22
|
}
|
|
20
23
|
});
|