cdp-skill 1.0.2 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -0
- package/SKILL.md +34 -5
- package/package.json +2 -1
- package/src/capture/console-capture.js +241 -0
- package/src/capture/debug-capture.js +144 -0
- package/src/capture/error-aggregator.js +151 -0
- package/src/capture/eval-serializer.js +320 -0
- package/src/capture/index.js +40 -0
- package/src/capture/network-capture.js +211 -0
- package/src/capture/pdf-capture.js +256 -0
- package/src/capture/screenshot-capture.js +325 -0
- package/src/cdp/browser.js +569 -0
- package/src/cdp/connection.js +369 -0
- package/src/cdp/discovery.js +138 -0
- package/src/cdp/index.js +29 -0
- package/src/cdp/target-and-session.js +439 -0
- package/src/cdp-skill.js +25 -11
- package/src/constants.js +79 -0
- package/src/dom/actionability.js +638 -0
- package/src/dom/click-executor.js +923 -0
- package/src/dom/element-handle.js +496 -0
- package/src/dom/element-locator.js +475 -0
- package/src/dom/element-validator.js +120 -0
- package/src/dom/fill-executor.js +489 -0
- package/src/dom/index.js +248 -0
- package/src/dom/input-emulator.js +406 -0
- package/src/dom/keyboard-executor.js +202 -0
- package/src/dom/quad-helpers.js +89 -0
- package/src/dom/react-filler.js +94 -0
- package/src/dom/wait-executor.js +423 -0
- package/src/index.js +6 -6
- package/src/page/cookie-manager.js +202 -0
- package/src/page/dom-stability.js +181 -0
- package/src/page/index.js +36 -0
- package/src/{page.js → page/page-controller.js} +109 -839
- package/src/page/wait-utilities.js +302 -0
- package/src/page/web-storage-manager.js +108 -0
- package/src/runner/context-helpers.js +224 -0
- package/src/runner/execute-browser.js +518 -0
- package/src/runner/execute-form.js +315 -0
- package/src/runner/execute-input.js +308 -0
- package/src/runner/execute-interaction.js +672 -0
- package/src/runner/execute-navigation.js +180 -0
- package/src/runner/execute-query.js +771 -0
- package/src/runner/index.js +51 -0
- package/src/runner/step-executors.js +421 -0
- package/src/runner/step-validator.js +641 -0
- package/src/tests/Actionability.test.js +613 -0
- package/src/tests/BrowserClient.test.js +1 -1
- package/src/tests/ChromeDiscovery.test.js +1 -1
- package/src/tests/ClickExecutor.test.js +554 -0
- package/src/tests/ConsoleCapture.test.js +1 -1
- package/src/tests/ContextHelpers.test.js +453 -0
- package/src/tests/CookieManager.test.js +450 -0
- package/src/tests/DebugCapture.test.js +307 -0
- package/src/tests/ElementHandle.test.js +1 -1
- package/src/tests/ElementLocator.test.js +1 -1
- package/src/tests/ErrorAggregator.test.js +1 -1
- package/src/tests/EvalSerializer.test.js +391 -0
- package/src/tests/FillExecutor.test.js +611 -0
- package/src/tests/InputEmulator.test.js +1 -1
- package/src/tests/KeyboardExecutor.test.js +430 -0
- package/src/tests/NetworkErrorCapture.test.js +1 -1
- package/src/tests/PageController.test.js +1 -1
- package/src/tests/PdfCapture.test.js +333 -0
- package/src/tests/ScreenshotCapture.test.js +1 -1
- package/src/tests/SessionRegistry.test.js +1 -1
- package/src/tests/StepValidator.test.js +527 -0
- package/src/tests/TargetManager.test.js +1 -1
- package/src/tests/TestRunner.test.js +1 -1
- package/src/tests/WaitStrategy.test.js +1 -1
- package/src/tests/WaitUtilities.test.js +508 -0
- package/src/tests/WebStorageManager.test.js +333 -0
- package/src/types.js +309 -0
- package/src/capture.js +0 -1400
- package/src/cdp.js +0 -1286
- package/src/dom.js +0 -4379
- package/src/runner.js +0 -3676
|
@@ -0,0 +1,439 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Target and Session Management Module
|
|
3
|
+
* CDP target management and session registry for browser tabs
|
|
4
|
+
*
|
|
5
|
+
* PUBLIC EXPORTS:
|
|
6
|
+
* - createTargetManager(connection) - Factory for target manager
|
|
7
|
+
* - createSessionRegistry(connection) - Factory for session registry
|
|
8
|
+
* - createPageSession(connection, sessionId, targetId) - Factory for page session
|
|
9
|
+
*
|
|
10
|
+
* @module cdp-skill/cdp/target-and-session
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Create a target manager for browser targets
|
|
15
|
+
* @param {import('../types.js').CDPConnection} connection - CDP connection
|
|
16
|
+
* @returns {Object} Target manager interface
|
|
17
|
+
*/
|
|
18
|
+
export function createTargetManager(connection) {
|
|
19
|
+
const targets = new Map();
|
|
20
|
+
let discoveryEnabled = false;
|
|
21
|
+
let boundHandlers = null;
|
|
22
|
+
|
|
23
|
+
function onTargetCreated(params) {
|
|
24
|
+
targets.set(params.targetInfo.targetId, params.targetInfo);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function onTargetDestroyed(params) {
|
|
28
|
+
targets.delete(params.targetId);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function onTargetInfoChanged(params) {
|
|
32
|
+
targets.set(params.targetInfo.targetId, params.targetInfo);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Enable automatic target discovery
|
|
37
|
+
* @returns {Promise<void>}
|
|
38
|
+
*/
|
|
39
|
+
async function enableDiscovery() {
|
|
40
|
+
if (discoveryEnabled) return;
|
|
41
|
+
|
|
42
|
+
boundHandlers = { onTargetCreated, onTargetDestroyed, onTargetInfoChanged };
|
|
43
|
+
connection.on('Target.targetCreated', boundHandlers.onTargetCreated);
|
|
44
|
+
connection.on('Target.targetDestroyed', boundHandlers.onTargetDestroyed);
|
|
45
|
+
connection.on('Target.targetInfoChanged', boundHandlers.onTargetInfoChanged);
|
|
46
|
+
|
|
47
|
+
await connection.send('Target.setDiscoverTargets', { discover: true });
|
|
48
|
+
discoveryEnabled = true;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Disable automatic target discovery
|
|
53
|
+
* @returns {Promise<void>}
|
|
54
|
+
*/
|
|
55
|
+
async function disableDiscovery() {
|
|
56
|
+
if (!discoveryEnabled) return;
|
|
57
|
+
|
|
58
|
+
await connection.send('Target.setDiscoverTargets', { discover: false });
|
|
59
|
+
|
|
60
|
+
if (boundHandlers) {
|
|
61
|
+
connection.off('Target.targetCreated', boundHandlers.onTargetCreated);
|
|
62
|
+
connection.off('Target.targetDestroyed', boundHandlers.onTargetDestroyed);
|
|
63
|
+
connection.off('Target.targetInfoChanged', boundHandlers.onTargetInfoChanged);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
discoveryEnabled = false;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Get all targets, optionally filtered
|
|
71
|
+
* @param {Object} [filter] - Optional target filter
|
|
72
|
+
* @returns {Promise<Array>} Array of target info objects
|
|
73
|
+
*/
|
|
74
|
+
async function getTargets(filter = null) {
|
|
75
|
+
const result = await connection.send('Target.getTargets', {
|
|
76
|
+
filter: filter ? [filter] : undefined
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
for (const info of result.targetInfos) {
|
|
80
|
+
targets.set(info.targetId, info);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return result.targetInfos;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Get page targets only
|
|
88
|
+
* @returns {Promise<Array>} Array of page target info objects
|
|
89
|
+
*/
|
|
90
|
+
async function getPages() {
|
|
91
|
+
const allTargets = await getTargets();
|
|
92
|
+
return allTargets.filter(t => t.type === 'page');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Create a new browser target (tab)
|
|
97
|
+
* @param {string} [url='about:blank'] - Initial URL
|
|
98
|
+
* @param {Object} [options] - Creation options
|
|
99
|
+
* @param {number} [options.width] - Viewport width
|
|
100
|
+
* @param {number} [options.height] - Viewport height
|
|
101
|
+
* @param {boolean} [options.background=false] - Create in background
|
|
102
|
+
* @param {boolean} [options.newWindow=false] - Create in new window
|
|
103
|
+
* @returns {Promise<string>} Target ID
|
|
104
|
+
*/
|
|
105
|
+
async function createTarget(url = 'about:blank', options = {}) {
|
|
106
|
+
const result = await connection.send('Target.createTarget', {
|
|
107
|
+
url,
|
|
108
|
+
width: options.width,
|
|
109
|
+
height: options.height,
|
|
110
|
+
background: options.background ?? false,
|
|
111
|
+
newWindow: options.newWindow ?? false
|
|
112
|
+
});
|
|
113
|
+
return result.targetId;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Close a target
|
|
118
|
+
* @param {string} targetId - Target to close
|
|
119
|
+
* @returns {Promise<boolean>} Success
|
|
120
|
+
*/
|
|
121
|
+
async function closeTarget(targetId) {
|
|
122
|
+
const result = await connection.send('Target.closeTarget', { targetId });
|
|
123
|
+
targets.delete(targetId);
|
|
124
|
+
return result.success ?? true;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Activate (bring to front) a target
|
|
129
|
+
* @param {string} targetId - Target to activate
|
|
130
|
+
* @returns {Promise<void>}
|
|
131
|
+
*/
|
|
132
|
+
async function activateTarget(targetId) {
|
|
133
|
+
await connection.send('Target.activateTarget', { targetId });
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Get detailed info for a target
|
|
138
|
+
* @param {string} targetId - Target ID
|
|
139
|
+
* @returns {Promise<Object>} Target info
|
|
140
|
+
*/
|
|
141
|
+
async function getTargetInfo(targetId) {
|
|
142
|
+
const result = await connection.send('Target.getTargetInfo', { targetId });
|
|
143
|
+
targets.set(targetId, result.targetInfo);
|
|
144
|
+
return result.targetInfo;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get cached target info
|
|
149
|
+
* @param {string} targetId - Target ID
|
|
150
|
+
* @returns {Object|undefined} Cached target info
|
|
151
|
+
*/
|
|
152
|
+
function getCachedTarget(targetId) {
|
|
153
|
+
return targets.get(targetId);
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get all cached targets
|
|
158
|
+
* @returns {Map} Cached targets map
|
|
159
|
+
*/
|
|
160
|
+
function getCachedTargets() {
|
|
161
|
+
return new Map(targets);
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Clean up target manager
|
|
166
|
+
* @returns {Promise<void>}
|
|
167
|
+
*/
|
|
168
|
+
async function cleanup() {
|
|
169
|
+
await disableDiscovery();
|
|
170
|
+
targets.clear();
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
return {
|
|
174
|
+
enableDiscovery,
|
|
175
|
+
disableDiscovery,
|
|
176
|
+
getTargets,
|
|
177
|
+
getPages,
|
|
178
|
+
createTarget,
|
|
179
|
+
closeTarget,
|
|
180
|
+
activateTarget,
|
|
181
|
+
getTargetInfo,
|
|
182
|
+
getCachedTarget,
|
|
183
|
+
getCachedTargets,
|
|
184
|
+
cleanup
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* Create a session registry for managing CDP sessions
|
|
190
|
+
* @param {import('../types.js').CDPConnection} connection - CDP connection
|
|
191
|
+
* @returns {Object} Session registry interface
|
|
192
|
+
*/
|
|
193
|
+
export function createSessionRegistry(connection) {
|
|
194
|
+
const sessions = new Map();
|
|
195
|
+
const targetToSession = new Map();
|
|
196
|
+
const pendingAttach = new Map();
|
|
197
|
+
let boundHandlers = null;
|
|
198
|
+
|
|
199
|
+
function onAttached(params) {
|
|
200
|
+
const { sessionId, targetInfo } = params;
|
|
201
|
+
sessions.set(sessionId, { targetId: targetInfo.targetId, attached: true });
|
|
202
|
+
targetToSession.set(targetInfo.targetId, sessionId);
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
function onDetached(params) {
|
|
206
|
+
const { sessionId } = params;
|
|
207
|
+
const session = sessions.get(sessionId);
|
|
208
|
+
if (session) {
|
|
209
|
+
targetToSession.delete(session.targetId);
|
|
210
|
+
sessions.delete(sessionId);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function onTargetDestroyed(params) {
|
|
215
|
+
const { targetId } = params;
|
|
216
|
+
const sessionId = targetToSession.get(targetId);
|
|
217
|
+
if (sessionId) {
|
|
218
|
+
sessions.delete(sessionId);
|
|
219
|
+
targetToSession.delete(targetId);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// Setup handlers on creation
|
|
224
|
+
boundHandlers = { onAttached, onDetached, onTargetDestroyed };
|
|
225
|
+
connection.on('Target.attachedToTarget', boundHandlers.onAttached);
|
|
226
|
+
connection.on('Target.detachedFromTarget', boundHandlers.onDetached);
|
|
227
|
+
connection.on('Target.targetDestroyed', boundHandlers.onTargetDestroyed);
|
|
228
|
+
|
|
229
|
+
async function doAttach(targetId) {
|
|
230
|
+
const result = await connection.send('Target.attachToTarget', {
|
|
231
|
+
targetId,
|
|
232
|
+
flatten: true
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
const sessionId = result.sessionId;
|
|
236
|
+
sessions.set(sessionId, { targetId, attached: true });
|
|
237
|
+
targetToSession.set(targetId, sessionId);
|
|
238
|
+
|
|
239
|
+
return sessionId;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Attach to a target
|
|
244
|
+
* @param {string} targetId - Target to attach to
|
|
245
|
+
* @returns {Promise<string>} Session ID
|
|
246
|
+
*/
|
|
247
|
+
async function attach(targetId) {
|
|
248
|
+
const existing = targetToSession.get(targetId);
|
|
249
|
+
if (existing) return existing;
|
|
250
|
+
|
|
251
|
+
const pending = pendingAttach.get(targetId);
|
|
252
|
+
if (pending) return pending;
|
|
253
|
+
|
|
254
|
+
const attachPromise = doAttach(targetId);
|
|
255
|
+
pendingAttach.set(targetId, attachPromise);
|
|
256
|
+
|
|
257
|
+
try {
|
|
258
|
+
return await attachPromise;
|
|
259
|
+
} finally {
|
|
260
|
+
pendingAttach.delete(targetId);
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
/**
|
|
265
|
+
* Detach from a session
|
|
266
|
+
* @param {string} sessionId - Session to detach
|
|
267
|
+
* @returns {Promise<void>}
|
|
268
|
+
*/
|
|
269
|
+
async function detach(sessionId) {
|
|
270
|
+
const session = sessions.get(sessionId);
|
|
271
|
+
if (!session) return;
|
|
272
|
+
|
|
273
|
+
await connection.send('Target.detachFromTarget', { sessionId });
|
|
274
|
+
sessions.delete(sessionId);
|
|
275
|
+
targetToSession.delete(session.targetId);
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
/**
|
|
279
|
+
* Detach from a target by target ID
|
|
280
|
+
* @param {string} targetId - Target to detach from
|
|
281
|
+
* @returns {Promise<void>}
|
|
282
|
+
*/
|
|
283
|
+
async function detachByTarget(targetId) {
|
|
284
|
+
const sessionId = targetToSession.get(targetId);
|
|
285
|
+
if (sessionId) {
|
|
286
|
+
await detach(sessionId);
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Get session ID for a target
|
|
292
|
+
* @param {string} targetId - Target ID
|
|
293
|
+
* @returns {string|undefined} Session ID
|
|
294
|
+
*/
|
|
295
|
+
function getSessionForTarget(targetId) {
|
|
296
|
+
return targetToSession.get(targetId);
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/**
|
|
300
|
+
* Get target ID for a session
|
|
301
|
+
* @param {string} sessionId - Session ID
|
|
302
|
+
* @returns {string|undefined} Target ID
|
|
303
|
+
*/
|
|
304
|
+
function getTargetForSession(sessionId) {
|
|
305
|
+
return sessions.get(sessionId)?.targetId;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Check if attached to a target
|
|
310
|
+
* @param {string} targetId - Target ID
|
|
311
|
+
* @returns {boolean}
|
|
312
|
+
*/
|
|
313
|
+
function isAttached(targetId) {
|
|
314
|
+
return targetToSession.has(targetId);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Get all sessions
|
|
319
|
+
* @returns {Array<{sessionId: string, targetId: string}>}
|
|
320
|
+
*/
|
|
321
|
+
function getAllSessions() {
|
|
322
|
+
return Array.from(sessions.entries()).map(([sessionId, data]) => ({
|
|
323
|
+
sessionId,
|
|
324
|
+
targetId: data.targetId
|
|
325
|
+
}));
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
/**
|
|
329
|
+
* Detach from all sessions
|
|
330
|
+
* @returns {Promise<void>}
|
|
331
|
+
*/
|
|
332
|
+
async function detachAll() {
|
|
333
|
+
const sessionIds = Array.from(sessions.keys());
|
|
334
|
+
await Promise.all(sessionIds.map(s => detach(s)));
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* Clean up session registry
|
|
339
|
+
* @returns {Promise<void>}
|
|
340
|
+
*/
|
|
341
|
+
async function cleanup() {
|
|
342
|
+
await detachAll();
|
|
343
|
+
if (boundHandlers) {
|
|
344
|
+
connection.off('Target.attachedToTarget', boundHandlers.onAttached);
|
|
345
|
+
connection.off('Target.detachedFromTarget', boundHandlers.onDetached);
|
|
346
|
+
connection.off('Target.targetDestroyed', boundHandlers.onTargetDestroyed);
|
|
347
|
+
}
|
|
348
|
+
sessions.clear();
|
|
349
|
+
targetToSession.clear();
|
|
350
|
+
pendingAttach.clear();
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
return {
|
|
354
|
+
attach,
|
|
355
|
+
detach,
|
|
356
|
+
detachByTarget,
|
|
357
|
+
getSessionForTarget,
|
|
358
|
+
getTargetForSession,
|
|
359
|
+
isAttached,
|
|
360
|
+
getAllSessions,
|
|
361
|
+
detachAll,
|
|
362
|
+
cleanup
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Create a page session for CDP communication with a specific page
|
|
368
|
+
* @param {import('../types.js').CDPConnection} connection - CDP connection
|
|
369
|
+
* @param {string} sessionId - Session ID
|
|
370
|
+
* @param {string} targetId - Target ID
|
|
371
|
+
* @returns {import('../types.js').CDPSession} Page session interface
|
|
372
|
+
*/
|
|
373
|
+
export function createPageSession(connection, sessionId, targetId) {
|
|
374
|
+
let valid = true;
|
|
375
|
+
let detachHandler = null;
|
|
376
|
+
|
|
377
|
+
function onDetached(params) {
|
|
378
|
+
if (params.sessionId === sessionId) {
|
|
379
|
+
valid = false;
|
|
380
|
+
if (detachHandler) {
|
|
381
|
+
connection.off('Target.detachedFromTarget', detachHandler);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
detachHandler = onDetached;
|
|
387
|
+
connection.on('Target.detachedFromTarget', detachHandler);
|
|
388
|
+
|
|
389
|
+
/**
|
|
390
|
+
* Send CDP command via this session
|
|
391
|
+
* @param {string} method - CDP method name
|
|
392
|
+
* @param {Object} [params={}] - Command parameters
|
|
393
|
+
* @returns {Promise<Object>} Command result
|
|
394
|
+
*/
|
|
395
|
+
async function send(method, params = {}) {
|
|
396
|
+
if (!valid) {
|
|
397
|
+
throw new Error(`Session ${sessionId} is no longer valid (target was closed or detached)`);
|
|
398
|
+
}
|
|
399
|
+
return connection.sendToSession(sessionId, method, params);
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
/**
|
|
403
|
+
* Subscribe to session-scoped event
|
|
404
|
+
* @param {string} event - Event name
|
|
405
|
+
* @param {function} callback - Event handler
|
|
406
|
+
*/
|
|
407
|
+
function on(event, callback) {
|
|
408
|
+
connection.on(`${sessionId}:${event}`, callback);
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
/**
|
|
412
|
+
* Unsubscribe from session-scoped event
|
|
413
|
+
* @param {string} event - Event name
|
|
414
|
+
* @param {function} callback - Event handler
|
|
415
|
+
*/
|
|
416
|
+
function off(event, callback) {
|
|
417
|
+
connection.off(`${sessionId}:${event}`, callback);
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/**
|
|
421
|
+
* Dispose the session
|
|
422
|
+
*/
|
|
423
|
+
function dispose() {
|
|
424
|
+
valid = false;
|
|
425
|
+
if (detachHandler) {
|
|
426
|
+
connection.off('Target.detachedFromTarget', detachHandler);
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
return {
|
|
431
|
+
send,
|
|
432
|
+
on,
|
|
433
|
+
off,
|
|
434
|
+
dispose,
|
|
435
|
+
isValid: () => valid,
|
|
436
|
+
get sessionId() { return sessionId; },
|
|
437
|
+
get targetId() { return targetId; }
|
|
438
|
+
};
|
|
439
|
+
}
|
package/src/cdp-skill.js
CHANGED
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
* node src/cdp-skill.js --debug '{"steps":[...]}' # Enable debug logging
|
|
12
12
|
*/
|
|
13
13
|
|
|
14
|
-
import { createBrowser, getChromeStatus } from './cdp.js';
|
|
15
|
-
import { createPageController } from './page.js';
|
|
16
|
-
import { createElementLocator, createInputEmulator } from './dom.js';
|
|
17
|
-
import { createScreenshotCapture, createConsoleCapture, createPdfCapture } from './capture.js';
|
|
14
|
+
import { createBrowser, getChromeStatus } from './cdp/index.js';
|
|
15
|
+
import { createPageController } from './page/index.js';
|
|
16
|
+
import { createElementLocator, createInputEmulator } from './dom/index.js';
|
|
17
|
+
import { createScreenshotCapture, createConsoleCapture, createPdfCapture } from './capture/index.js';
|
|
18
18
|
import { createAriaSnapshot } from './aria.js';
|
|
19
|
-
import { createCookieManager } from './page.js';
|
|
20
|
-
import { runSteps } from './runner.js';
|
|
19
|
+
import { createCookieManager } from './page/index.js';
|
|
20
|
+
import { runSteps } from './runner/index.js';
|
|
21
21
|
import fs from 'fs';
|
|
22
22
|
import path from 'path';
|
|
23
23
|
import os from 'os';
|
|
@@ -424,6 +424,7 @@ async function main() {
|
|
|
424
424
|
const host = config.host || 'localhost';
|
|
425
425
|
const port = config.port || 9222;
|
|
426
426
|
const timeout = config.timeout || 30000;
|
|
427
|
+
const headless = config.headless || false; // Run Chrome in headless mode (no focus stealing)
|
|
427
428
|
const tab = json.tab || config.tab; // Top-level tab takes precedence
|
|
428
429
|
|
|
429
430
|
// Handle chromeStatus specially - no session needed
|
|
@@ -442,16 +443,29 @@ async function main() {
|
|
|
442
443
|
process.exit(result.status === 'ok' ? 0 : 1);
|
|
443
444
|
}
|
|
444
445
|
|
|
445
|
-
// Connect to browser
|
|
446
|
+
// Connect to browser, auto-launch if needed
|
|
446
447
|
browser = createBrowser({ host, port, connectTimeout: timeout });
|
|
447
448
|
|
|
448
449
|
try {
|
|
449
450
|
await browser.connect();
|
|
450
451
|
} catch (err) {
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
452
|
+
// Chrome not running - try to auto-launch
|
|
453
|
+
const status = await getChromeStatus({ host, port, autoLaunch: true, headless });
|
|
454
|
+
if (!status.running) {
|
|
455
|
+
throw {
|
|
456
|
+
type: ErrorType.CONNECTION,
|
|
457
|
+
message: `Chrome not running and failed to launch: ${status.error || 'unknown error'}`
|
|
458
|
+
};
|
|
459
|
+
}
|
|
460
|
+
// Retry connection after launch
|
|
461
|
+
try {
|
|
462
|
+
await browser.connect();
|
|
463
|
+
} catch (retryErr) {
|
|
464
|
+
throw {
|
|
465
|
+
type: ErrorType.CONNECTION,
|
|
466
|
+
message: `Chrome launched but connection failed: ${retryErr.message}`
|
|
467
|
+
};
|
|
468
|
+
}
|
|
455
469
|
}
|
|
456
470
|
|
|
457
471
|
// Get page session - requires explicit targetId or openTab step
|
package/src/constants.js
ADDED
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Constants
|
|
3
|
+
* Centralized configuration values used across the codebase
|
|
4
|
+
*
|
|
5
|
+
* EXPORTS:
|
|
6
|
+
* - TIMEOUTS - Timeout values in milliseconds
|
|
7
|
+
* - POLL_INTERVALS - Polling interval values
|
|
8
|
+
* - LIMITS - Various size and count limits
|
|
9
|
+
* - VALID_FORMATS - Allowed screenshot formats
|
|
10
|
+
* - KEY_DEFINITIONS - Special key code mappings
|
|
11
|
+
* - NON_EDITABLE_INPUT_TYPES - Input types that cannot be typed into
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
// Timeouts (milliseconds)
|
|
15
|
+
export const TIMEOUTS = {
|
|
16
|
+
MAX: 300000, // 5 minutes - absolute maximum
|
|
17
|
+
DEFAULT: 10000, // 10 seconds - default for actionability
|
|
18
|
+
NETWORK_IDLE: 500, // network idle threshold
|
|
19
|
+
STABILITY: 50, // DOM stability check
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export const POLL_INTERVALS = {
|
|
23
|
+
DEFAULT: 100, // standard polling
|
|
24
|
+
FAST: 50, // actionability retries
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export const LIMITS = {
|
|
28
|
+
MAX_CONSOLE_MESSAGES: 10000,
|
|
29
|
+
MAX_SCREENSHOT_DIMENSION: 16384,
|
|
30
|
+
MAX_PENDING_REQUESTS: 10000,
|
|
31
|
+
MAX_DOM_NODES: 500,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export const VALID_FORMATS = ['png', 'jpeg', 'webp'];
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Special key definitions for keyboard input
|
|
38
|
+
*/
|
|
39
|
+
export const KEY_DEFINITIONS = {
|
|
40
|
+
Enter: { key: 'Enter', code: 'Enter', keyCode: 13, text: '\r' },
|
|
41
|
+
Tab: { key: 'Tab', code: 'Tab', keyCode: 9 },
|
|
42
|
+
Escape: { key: 'Escape', code: 'Escape', keyCode: 27 },
|
|
43
|
+
Backspace: { key: 'Backspace', code: 'Backspace', keyCode: 8 },
|
|
44
|
+
Delete: { key: 'Delete', code: 'Delete', keyCode: 46 },
|
|
45
|
+
Space: { key: ' ', code: 'Space', keyCode: 32, text: ' ' },
|
|
46
|
+
ArrowUp: { key: 'ArrowUp', code: 'ArrowUp', keyCode: 38 },
|
|
47
|
+
ArrowDown: { key: 'ArrowDown', code: 'ArrowDown', keyCode: 40 },
|
|
48
|
+
ArrowLeft: { key: 'ArrowLeft', code: 'ArrowLeft', keyCode: 37 },
|
|
49
|
+
ArrowRight: { key: 'ArrowRight', code: 'ArrowRight', keyCode: 39 },
|
|
50
|
+
Shift: { key: 'Shift', code: 'ShiftLeft', keyCode: 16 },
|
|
51
|
+
Control: { key: 'Control', code: 'ControlLeft', keyCode: 17 },
|
|
52
|
+
Alt: { key: 'Alt', code: 'AltLeft', keyCode: 18 },
|
|
53
|
+
Meta: { key: 'Meta', code: 'MetaLeft', keyCode: 91 },
|
|
54
|
+
F1: { key: 'F1', code: 'F1', keyCode: 112 },
|
|
55
|
+
F2: { key: 'F2', code: 'F2', keyCode: 113 },
|
|
56
|
+
F3: { key: 'F3', code: 'F3', keyCode: 114 },
|
|
57
|
+
F4: { key: 'F4', code: 'F4', keyCode: 115 },
|
|
58
|
+
F5: { key: 'F5', code: 'F5', keyCode: 116 },
|
|
59
|
+
F6: { key: 'F6', code: 'F6', keyCode: 117 },
|
|
60
|
+
F7: { key: 'F7', code: 'F7', keyCode: 118 },
|
|
61
|
+
F8: { key: 'F8', code: 'F8', keyCode: 119 },
|
|
62
|
+
F9: { key: 'F9', code: 'F9', keyCode: 120 },
|
|
63
|
+
F10: { key: 'F10', code: 'F10', keyCode: 121 },
|
|
64
|
+
F11: { key: 'F11', code: 'F11', keyCode: 122 },
|
|
65
|
+
F12: { key: 'F12', code: 'F12', keyCode: 123 },
|
|
66
|
+
Home: { key: 'Home', code: 'Home', keyCode: 36 },
|
|
67
|
+
End: { key: 'End', code: 'End', keyCode: 35 },
|
|
68
|
+
PageUp: { key: 'PageUp', code: 'PageUp', keyCode: 33 },
|
|
69
|
+
PageDown: { key: 'PageDown', code: 'PageDown', keyCode: 34 },
|
|
70
|
+
Insert: { key: 'Insert', code: 'Insert', keyCode: 45 }
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Non-editable input types that cannot receive text input
|
|
75
|
+
*/
|
|
76
|
+
export const NON_EDITABLE_INPUT_TYPES = [
|
|
77
|
+
'button', 'checkbox', 'color', 'file', 'hidden',
|
|
78
|
+
'image', 'radio', 'range', 'reset', 'submit'
|
|
79
|
+
];
|