chrome-cdp-cli 2.0.2 → 2.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/dist/cli/ArgumentParser.js +4 -2
- package/dist/cli/CommandSchemaRegistry.js +13 -2
- package/dist/cli/OutputFormatter.js +3 -3
- package/dist/client/CDPClient.js +2 -1
- package/dist/connection/ConnectionManager.js +2 -1
- package/dist/handlers/ClickHandler.js +89 -3
- package/dist/handlers/DragHandler.js +137 -4
- package/dist/handlers/TakeSnapshotHandler.js +151 -52
- package/package.json +1 -1
|
@@ -348,13 +348,14 @@ class ArgumentParser {
|
|
|
348
348
|
}
|
|
349
349
|
const stringValue = String(value);
|
|
350
350
|
switch (optionDef.type) {
|
|
351
|
-
case 'number':
|
|
351
|
+
case 'number': {
|
|
352
352
|
const numValue = Number(stringValue);
|
|
353
353
|
if (isNaN(numValue)) {
|
|
354
354
|
throw new Error(`Option --${optionDef.name} must be a number, got: ${stringValue}`);
|
|
355
355
|
}
|
|
356
356
|
return numValue;
|
|
357
|
-
|
|
357
|
+
}
|
|
358
|
+
case 'boolean': {
|
|
358
359
|
if (typeof value === 'boolean') {
|
|
359
360
|
return value;
|
|
360
361
|
}
|
|
@@ -366,6 +367,7 @@ class ArgumentParser {
|
|
|
366
367
|
return false;
|
|
367
368
|
}
|
|
368
369
|
throw new Error(`Option --${optionDef.name} must be a boolean, got: ${stringValue}`);
|
|
370
|
+
}
|
|
369
371
|
case 'array':
|
|
370
372
|
if (Array.isArray(value)) {
|
|
371
373
|
return value;
|
|
@@ -281,13 +281,13 @@ class CommandSchemaRegistry {
|
|
|
281
281
|
options: [],
|
|
282
282
|
arguments: [
|
|
283
283
|
{
|
|
284
|
-
name: '
|
|
284
|
+
name: 'sourceSelector',
|
|
285
285
|
description: 'CSS selector for element to drag from',
|
|
286
286
|
type: 'string',
|
|
287
287
|
required: true
|
|
288
288
|
},
|
|
289
289
|
{
|
|
290
|
-
name: '
|
|
290
|
+
name: 'targetSelector',
|
|
291
291
|
description: 'CSS selector for element to drag to',
|
|
292
292
|
type: 'string',
|
|
293
293
|
required: true
|
|
@@ -459,6 +459,17 @@ class CommandSchemaRegistry {
|
|
|
459
459
|
description: 'Include text index information',
|
|
460
460
|
type: 'boolean',
|
|
461
461
|
default: false
|
|
462
|
+
},
|
|
463
|
+
{
|
|
464
|
+
name: 'color',
|
|
465
|
+
description: 'Enable color output (default: auto-detect)',
|
|
466
|
+
type: 'boolean'
|
|
467
|
+
},
|
|
468
|
+
{
|
|
469
|
+
name: 'no-color',
|
|
470
|
+
description: 'Disable color output',
|
|
471
|
+
type: 'boolean',
|
|
472
|
+
default: false
|
|
462
473
|
}
|
|
463
474
|
],
|
|
464
475
|
arguments: []
|
|
@@ -17,7 +17,7 @@ class OutputFormatter {
|
|
|
17
17
|
return this.formatErrorOutput(result, options);
|
|
18
18
|
}
|
|
19
19
|
if (options.template) {
|
|
20
|
-
return this.applyTemplate(result, options.template
|
|
20
|
+
return this.applyTemplate(result, options.template);
|
|
21
21
|
}
|
|
22
22
|
switch (options.format) {
|
|
23
23
|
case 'json':
|
|
@@ -167,7 +167,7 @@ class OutputFormatter {
|
|
|
167
167
|
if (obj.type && obj.text !== undefined && obj.timestamp) {
|
|
168
168
|
const timestamp = new Date(obj.timestamp).toISOString();
|
|
169
169
|
const typeIcon = this.getConsoleTypeIcon(obj.type);
|
|
170
|
-
|
|
170
|
+
const output = `${timestamp} ${typeIcon} ${obj.text}`;
|
|
171
171
|
return output;
|
|
172
172
|
}
|
|
173
173
|
if (obj.requestId && obj.url && obj.method) {
|
|
@@ -234,7 +234,7 @@ class OutputFormatter {
|
|
|
234
234
|
}
|
|
235
235
|
return output;
|
|
236
236
|
}
|
|
237
|
-
applyTemplate(result, templateName
|
|
237
|
+
applyTemplate(result, templateName) {
|
|
238
238
|
const template = this.templates.get(templateName);
|
|
239
239
|
if (!template) {
|
|
240
240
|
throw new Error(`Unknown template: ${templateName}`);
|
package/dist/client/CDPClient.js
CHANGED
|
@@ -141,7 +141,8 @@ class CDPClient {
|
|
|
141
141
|
}
|
|
142
142
|
}
|
|
143
143
|
async discoverTargets(host, port) {
|
|
144
|
-
const
|
|
144
|
+
const normalizedHost = host === 'localhost' ? '127.0.0.1' : host;
|
|
145
|
+
const response = await (0, node_fetch_1.default)(`http://${normalizedHost}:${port}/json/list`);
|
|
145
146
|
if (!response.ok) {
|
|
146
147
|
throw new Error(`Failed to discover targets: ${response.statusText}`);
|
|
147
148
|
}
|
|
@@ -44,7 +44,8 @@ class ConnectionManager {
|
|
|
44
44
|
this.logger = logger || new logger_1.Logger();
|
|
45
45
|
}
|
|
46
46
|
async discoverTargets(host, port) {
|
|
47
|
-
const
|
|
47
|
+
const normalizedHost = host === 'localhost' ? '127.0.0.1' : host;
|
|
48
|
+
const url = `http://${normalizedHost}:${port}/json/list`;
|
|
48
49
|
try {
|
|
49
50
|
this.logger.debug(`Discovering targets at ${url}`);
|
|
50
51
|
const http = await Promise.resolve().then(() => __importStar(require('http')));
|
|
@@ -33,6 +33,10 @@ class ClickHandler {
|
|
|
33
33
|
};
|
|
34
34
|
}
|
|
35
35
|
}
|
|
36
|
+
const inputResult = await this.clickViaInput(client, clickArgs.selector);
|
|
37
|
+
if (inputResult.success) {
|
|
38
|
+
return inputResult;
|
|
39
|
+
}
|
|
36
40
|
const cdpResult = await this.clickViaCDP(client, clickArgs.selector);
|
|
37
41
|
if (cdpResult.success) {
|
|
38
42
|
return cdpResult;
|
|
@@ -66,6 +70,87 @@ class ClickHandler {
|
|
|
66
70
|
}
|
|
67
71
|
return false;
|
|
68
72
|
}
|
|
73
|
+
async clickViaInput(client, selector) {
|
|
74
|
+
try {
|
|
75
|
+
const escapedSelector = selector.replace(/'/g, "\\'").replace(/"/g, '\\"');
|
|
76
|
+
const getCoordsExpression = `
|
|
77
|
+
(function() {
|
|
78
|
+
const element = document.querySelector('${escapedSelector}');
|
|
79
|
+
if (!element) {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Scroll element into view if needed
|
|
84
|
+
element.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
85
|
+
|
|
86
|
+
// Get bounding box
|
|
87
|
+
const rect = element.getBoundingClientRect();
|
|
88
|
+
const x = rect.left + rect.width / 2;
|
|
89
|
+
const y = rect.top + rect.height / 2;
|
|
90
|
+
|
|
91
|
+
return {
|
|
92
|
+
x: Math.round(x),
|
|
93
|
+
y: Math.round(y),
|
|
94
|
+
tagName: element.tagName,
|
|
95
|
+
id: element.id,
|
|
96
|
+
className: element.className
|
|
97
|
+
};
|
|
98
|
+
})()
|
|
99
|
+
`;
|
|
100
|
+
const coordsResponse = await client.send('Runtime.evaluate', {
|
|
101
|
+
expression: getCoordsExpression,
|
|
102
|
+
returnByValue: true
|
|
103
|
+
});
|
|
104
|
+
if (coordsResponse.exceptionDetails) {
|
|
105
|
+
return {
|
|
106
|
+
success: false,
|
|
107
|
+
error: `Failed to get element coordinates: ${coordsResponse.exceptionDetails.exception?.description || coordsResponse.exceptionDetails.text}`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
const coords = coordsResponse.result.value;
|
|
111
|
+
if (!coords) {
|
|
112
|
+
return {
|
|
113
|
+
success: false,
|
|
114
|
+
error: `Element with selector "${selector}" not found`
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
await client.send('Input.dispatchMouseEvent', {
|
|
118
|
+
type: 'mousePressed',
|
|
119
|
+
x: coords.x,
|
|
120
|
+
y: coords.y,
|
|
121
|
+
button: 'left',
|
|
122
|
+
clickCount: 1
|
|
123
|
+
});
|
|
124
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
125
|
+
await client.send('Input.dispatchMouseEvent', {
|
|
126
|
+
type: 'mouseReleased',
|
|
127
|
+
x: coords.x,
|
|
128
|
+
y: coords.y,
|
|
129
|
+
button: 'left',
|
|
130
|
+
clickCount: 1
|
|
131
|
+
});
|
|
132
|
+
return {
|
|
133
|
+
success: true,
|
|
134
|
+
data: {
|
|
135
|
+
selector: selector,
|
|
136
|
+
element: {
|
|
137
|
+
success: true,
|
|
138
|
+
tagName: coords.tagName,
|
|
139
|
+
id: coords.id,
|
|
140
|
+
className: coords.className
|
|
141
|
+
},
|
|
142
|
+
method: 'Input.dispatchMouseEvent',
|
|
143
|
+
coordinates: { x: coords.x, y: coords.y }
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
error: `Input click failed: ${error instanceof Error ? error.message : String(error)}`
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
69
154
|
async clickViaCDP(client, selector) {
|
|
70
155
|
try {
|
|
71
156
|
const documentResponse = await client.send('DOM.getDocument');
|
|
@@ -231,10 +316,11 @@ Examples:
|
|
|
231
316
|
click "#optional-element" --no-wait
|
|
232
317
|
|
|
233
318
|
Note:
|
|
234
|
-
- Uses CDP
|
|
235
|
-
- Falls back to JavaScript eval if
|
|
319
|
+
- Uses CDP Input.dispatchMouseEvent for most reliable click simulation
|
|
320
|
+
- Falls back to Runtime.callFunctionOn and JavaScript eval if Input fails
|
|
236
321
|
- Automatically scrolls element into view before clicking
|
|
237
|
-
- Triggers
|
|
322
|
+
- Triggers complete mouse event sequence (mousePressed, mouseReleased)
|
|
323
|
+
- Works with all event listeners including React/Vue handlers
|
|
238
324
|
`;
|
|
239
325
|
}
|
|
240
326
|
}
|
|
@@ -33,6 +33,7 @@ class DragHandler {
|
|
|
33
33
|
}
|
|
34
34
|
try {
|
|
35
35
|
await client.send('Runtime.enable');
|
|
36
|
+
await client.send('DOM.enable');
|
|
36
37
|
const timeout = dragArgs.timeout || 5000;
|
|
37
38
|
const waitForElement = dragArgs.waitForElement !== false;
|
|
38
39
|
if (waitForElement) {
|
|
@@ -51,6 +52,10 @@ class DragHandler {
|
|
|
51
52
|
};
|
|
52
53
|
}
|
|
53
54
|
}
|
|
55
|
+
const inputResult = await this.dragViaInput(client, dragArgs.sourceSelector, dragArgs.targetSelector);
|
|
56
|
+
if (inputResult.success) {
|
|
57
|
+
return inputResult;
|
|
58
|
+
}
|
|
54
59
|
const result = await this.dragViaEval(client, dragArgs.sourceSelector, dragArgs.targetSelector);
|
|
55
60
|
return result;
|
|
56
61
|
}
|
|
@@ -80,6 +85,133 @@ class DragHandler {
|
|
|
80
85
|
}
|
|
81
86
|
return false;
|
|
82
87
|
}
|
|
88
|
+
async dragViaInput(client, sourceSelector, targetSelector) {
|
|
89
|
+
try {
|
|
90
|
+
const escapedSource = sourceSelector.replace(/'/g, "\\'").replace(/"/g, '\\"');
|
|
91
|
+
const escapedTarget = targetSelector.replace(/'/g, "\\'").replace(/"/g, '\\"');
|
|
92
|
+
const getCoordsExpression = `
|
|
93
|
+
(function() {
|
|
94
|
+
const sourceElement = document.querySelector('${escapedSource}');
|
|
95
|
+
if (!sourceElement) {
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const targetElement = document.querySelector('${escapedTarget}');
|
|
100
|
+
if (!targetElement) {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Scroll elements into view if needed
|
|
105
|
+
sourceElement.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
106
|
+
targetElement.scrollIntoView({ behavior: 'instant', block: 'center' });
|
|
107
|
+
|
|
108
|
+
// Get bounding boxes
|
|
109
|
+
const sourceRect = sourceElement.getBoundingClientRect();
|
|
110
|
+
const targetRect = targetElement.getBoundingClientRect();
|
|
111
|
+
|
|
112
|
+
// Calculate center positions
|
|
113
|
+
const sourceX = Math.round(sourceRect.left + sourceRect.width / 2);
|
|
114
|
+
const sourceY = Math.round(sourceRect.top + sourceRect.height / 2);
|
|
115
|
+
const targetX = Math.round(targetRect.left + targetRect.width / 2);
|
|
116
|
+
const targetY = Math.round(targetRect.top + targetRect.height / 2);
|
|
117
|
+
|
|
118
|
+
return {
|
|
119
|
+
source: {
|
|
120
|
+
x: sourceX,
|
|
121
|
+
y: sourceY,
|
|
122
|
+
tagName: sourceElement.tagName,
|
|
123
|
+
id: sourceElement.id,
|
|
124
|
+
className: sourceElement.className
|
|
125
|
+
},
|
|
126
|
+
target: {
|
|
127
|
+
x: targetX,
|
|
128
|
+
y: targetY,
|
|
129
|
+
tagName: targetElement.tagName,
|
|
130
|
+
id: targetElement.id,
|
|
131
|
+
className: targetElement.className
|
|
132
|
+
}
|
|
133
|
+
};
|
|
134
|
+
})()
|
|
135
|
+
`;
|
|
136
|
+
const coordsResponse = await client.send('Runtime.evaluate', {
|
|
137
|
+
expression: getCoordsExpression,
|
|
138
|
+
returnByValue: true
|
|
139
|
+
});
|
|
140
|
+
if (coordsResponse.exceptionDetails) {
|
|
141
|
+
return {
|
|
142
|
+
success: false,
|
|
143
|
+
error: `Failed to get element coordinates: ${coordsResponse.exceptionDetails.exception?.description || coordsResponse.exceptionDetails.text}`
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
const coords = coordsResponse.result.value;
|
|
147
|
+
if (!coords) {
|
|
148
|
+
return {
|
|
149
|
+
success: false,
|
|
150
|
+
error: `Source or target element not found`
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
const steps = 10;
|
|
154
|
+
const dx = (coords.target.x - coords.source.x) / steps;
|
|
155
|
+
const dy = (coords.target.y - coords.source.y) / steps;
|
|
156
|
+
await client.send('Input.dispatchMouseEvent', {
|
|
157
|
+
type: 'mousePressed',
|
|
158
|
+
x: coords.source.x,
|
|
159
|
+
y: coords.source.y,
|
|
160
|
+
button: 'left',
|
|
161
|
+
clickCount: 1
|
|
162
|
+
});
|
|
163
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
164
|
+
for (let i = 1; i <= steps; i++) {
|
|
165
|
+
const currentX = Math.round(coords.source.x + dx * i);
|
|
166
|
+
const currentY = Math.round(coords.source.y + dy * i);
|
|
167
|
+
await client.send('Input.dispatchMouseEvent', {
|
|
168
|
+
type: 'mouseMoved',
|
|
169
|
+
x: currentX,
|
|
170
|
+
y: currentY,
|
|
171
|
+
button: 'left',
|
|
172
|
+
buttons: 1
|
|
173
|
+
});
|
|
174
|
+
await new Promise(resolve => setTimeout(resolve, 10));
|
|
175
|
+
}
|
|
176
|
+
await new Promise(resolve => setTimeout(resolve, 50));
|
|
177
|
+
await client.send('Input.dispatchMouseEvent', {
|
|
178
|
+
type: 'mouseReleased',
|
|
179
|
+
x: coords.target.x,
|
|
180
|
+
y: coords.target.y,
|
|
181
|
+
button: 'left',
|
|
182
|
+
clickCount: 1
|
|
183
|
+
});
|
|
184
|
+
return {
|
|
185
|
+
success: true,
|
|
186
|
+
data: {
|
|
187
|
+
sourceSelector: sourceSelector,
|
|
188
|
+
targetSelector: targetSelector,
|
|
189
|
+
result: {
|
|
190
|
+
success: true,
|
|
191
|
+
source: {
|
|
192
|
+
tagName: coords.source.tagName,
|
|
193
|
+
id: coords.source.id,
|
|
194
|
+
className: coords.source.className,
|
|
195
|
+
position: { x: coords.source.x, y: coords.source.y }
|
|
196
|
+
},
|
|
197
|
+
target: {
|
|
198
|
+
tagName: coords.target.tagName,
|
|
199
|
+
id: coords.target.id,
|
|
200
|
+
className: coords.target.className,
|
|
201
|
+
position: { x: coords.target.x, y: coords.target.y }
|
|
202
|
+
}
|
|
203
|
+
},
|
|
204
|
+
method: 'Input.dispatchMouseEvent'
|
|
205
|
+
}
|
|
206
|
+
};
|
|
207
|
+
}
|
|
208
|
+
catch (error) {
|
|
209
|
+
return {
|
|
210
|
+
success: false,
|
|
211
|
+
error: `Input drag failed: ${error instanceof Error ? error.message : String(error)}`
|
|
212
|
+
};
|
|
213
|
+
}
|
|
214
|
+
}
|
|
83
215
|
async dragViaEval(client, sourceSelector, targetSelector) {
|
|
84
216
|
try {
|
|
85
217
|
const escapedSource = sourceSelector.replace(/'/g, "\\'").replace(/"/g, '\\"');
|
|
@@ -257,10 +389,11 @@ Examples:
|
|
|
257
389
|
drag "#source" "#target" --no-wait
|
|
258
390
|
|
|
259
391
|
Note:
|
|
260
|
-
- Uses
|
|
261
|
-
-
|
|
262
|
-
-
|
|
263
|
-
-
|
|
392
|
+
- Uses CDP Input.dispatchMouseEvent for most reliable drag simulation
|
|
393
|
+
- Falls back to JavaScript DragEvent simulation if Input fails
|
|
394
|
+
- Simulates complete mouse sequence: mousePressed → mouseMoved → mouseReleased
|
|
395
|
+
- Automatically calculates element center positions and smooth movement path
|
|
396
|
+
- Works with all drag and drop implementations including React/Vue libraries
|
|
264
397
|
`;
|
|
265
398
|
}
|
|
266
399
|
}
|
|
@@ -6,9 +6,93 @@ const path_1 = require("path");
|
|
|
6
6
|
class TakeSnapshotHandler {
|
|
7
7
|
constructor() {
|
|
8
8
|
this.name = 'snapshot';
|
|
9
|
+
this.colors = {
|
|
10
|
+
reset: '\x1b[0m',
|
|
11
|
+
bright: '\x1b[1m',
|
|
12
|
+
dim: '\x1b[2m',
|
|
13
|
+
red: '\x1b[31m',
|
|
14
|
+
green: '\x1b[32m',
|
|
15
|
+
yellow: '\x1b[33m',
|
|
16
|
+
blue: '\x1b[34m',
|
|
17
|
+
magenta: '\x1b[35m',
|
|
18
|
+
cyan: '\x1b[36m',
|
|
19
|
+
white: '\x1b[37m',
|
|
20
|
+
gray: '\x1b[90m',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
shouldUseColors(args) {
|
|
24
|
+
if (args.color === false) {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
if (args.color === true) {
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
if (process.env.NO_COLOR) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
if (process.env.FORCE_COLOR === '0') {
|
|
34
|
+
return false;
|
|
35
|
+
}
|
|
36
|
+
if (process.env.FORCE_COLOR === '1' || process.env.FORCE_COLOR === 'true') {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
if (args.filename) {
|
|
40
|
+
return false;
|
|
41
|
+
}
|
|
42
|
+
return process.stdout.isTTY === true;
|
|
43
|
+
}
|
|
44
|
+
colorize(text, color, useColors) {
|
|
45
|
+
if (!useColors)
|
|
46
|
+
return text;
|
|
47
|
+
return `${color}${text}${this.colors.reset}`;
|
|
48
|
+
}
|
|
49
|
+
colorTag(tagName, useColors) {
|
|
50
|
+
if (['button', 'a', 'input', 'textarea', 'select'].includes(tagName.toLowerCase())) {
|
|
51
|
+
return this.colorize(tagName.toUpperCase(), this.colors.green + this.colors.bright, useColors);
|
|
52
|
+
}
|
|
53
|
+
if (['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(tagName.toLowerCase())) {
|
|
54
|
+
return this.colorize(tagName.toUpperCase(), this.colors.cyan + this.colors.bright, useColors);
|
|
55
|
+
}
|
|
56
|
+
if (tagName.toLowerCase() === 'img') {
|
|
57
|
+
return this.colorize(tagName.toUpperCase(), this.colors.magenta, useColors);
|
|
58
|
+
}
|
|
59
|
+
return this.colorize(tagName.toUpperCase(), this.colors.cyan, useColors);
|
|
60
|
+
}
|
|
61
|
+
colorId(id, useColors) {
|
|
62
|
+
return this.colorize(`#${id}`, this.colors.green, useColors);
|
|
63
|
+
}
|
|
64
|
+
colorClass(className, useColors) {
|
|
65
|
+
return this.colorize(`.${className}`, this.colors.yellow, useColors);
|
|
66
|
+
}
|
|
67
|
+
colorAttribute(name, value, useColors) {
|
|
68
|
+
if (name === 'type') {
|
|
69
|
+
return this.colorize(`[${value}]`, this.colors.magenta, useColors);
|
|
70
|
+
}
|
|
71
|
+
if (name === 'name') {
|
|
72
|
+
return this.colorize(`name="${value}"`, this.colors.magenta, useColors);
|
|
73
|
+
}
|
|
74
|
+
return `[${name}="${value}"]`;
|
|
75
|
+
}
|
|
76
|
+
colorText(text, useColors) {
|
|
77
|
+
return this.colorize(`"${text}"`, this.colors.white, useColors);
|
|
78
|
+
}
|
|
79
|
+
colorUrl(url, useColors) {
|
|
80
|
+
return this.colorize(`"${url}"`, this.colors.blue, useColors);
|
|
81
|
+
}
|
|
82
|
+
colorSpecial(text, useColors) {
|
|
83
|
+
return this.colorize(text, this.colors.yellow, useColors);
|
|
84
|
+
}
|
|
85
|
+
colorTreeSymbol(symbol, useColors) {
|
|
86
|
+
return this.colorize(symbol, this.colors.gray, useColors);
|
|
87
|
+
}
|
|
88
|
+
colorPageTitle(title, useColors) {
|
|
89
|
+
return this.colorize(`PAGE: ${title}`, this.colors.cyan + this.colors.bright, useColors);
|
|
9
90
|
}
|
|
10
91
|
async execute(client, args) {
|
|
11
92
|
const snapshotArgs = args;
|
|
93
|
+
if (snapshotArgs['no-color']) {
|
|
94
|
+
snapshotArgs.color = false;
|
|
95
|
+
}
|
|
12
96
|
try {
|
|
13
97
|
await client.send('DOM.enable');
|
|
14
98
|
await client.send('CSS.enable');
|
|
@@ -89,7 +173,8 @@ class TakeSnapshotHandler {
|
|
|
89
173
|
processedSnapshot = htmlResponse.outerHTML;
|
|
90
174
|
}
|
|
91
175
|
else {
|
|
92
|
-
const
|
|
176
|
+
const useColors = this.shouldUseColors(snapshotArgs);
|
|
177
|
+
const textSnapshot = this.buildTextFromDOMNode(docResponse.root, url, title, useColors);
|
|
93
178
|
processedSnapshot = {
|
|
94
179
|
url,
|
|
95
180
|
title,
|
|
@@ -168,7 +253,8 @@ class TakeSnapshotHandler {
|
|
|
168
253
|
error: 'No documents found'
|
|
169
254
|
};
|
|
170
255
|
}
|
|
171
|
-
const
|
|
256
|
+
const useColors = this.shouldUseColors(args);
|
|
257
|
+
const textSnapshot = this.createTextSnapshot(doc, response.strings, useColors);
|
|
172
258
|
const result = {
|
|
173
259
|
url: doc.documentURL,
|
|
174
260
|
title: doc.title,
|
|
@@ -176,21 +262,21 @@ class TakeSnapshotHandler {
|
|
|
176
262
|
};
|
|
177
263
|
return result;
|
|
178
264
|
}
|
|
179
|
-
createTextSnapshot(doc, strings) {
|
|
265
|
+
createTextSnapshot(doc, strings, useColors = true) {
|
|
180
266
|
const nodes = doc.nodes;
|
|
181
267
|
if (!nodes.nodeName || !nodes.nodeType) {
|
|
182
268
|
return 'Empty document';
|
|
183
269
|
}
|
|
184
270
|
const nodeTree = this.buildNodeTree(doc, strings);
|
|
185
|
-
let output =
|
|
271
|
+
let output = this.colorPageTitle(doc.title || 'Untitled', useColors) + '\n';
|
|
186
272
|
const bodyNode = this.findBodyNode(nodeTree);
|
|
187
273
|
if (bodyNode) {
|
|
188
|
-
output += this.renderNodeAsText(bodyNode, 0);
|
|
274
|
+
output += this.renderNodeAsText(bodyNode, 0, false, [], useColors);
|
|
189
275
|
}
|
|
190
276
|
else {
|
|
191
277
|
for (const node of nodeTree) {
|
|
192
278
|
if (this.shouldIncludeNode(node)) {
|
|
193
|
-
output += this.renderNodeAsText(node, 0);
|
|
279
|
+
output += this.renderNodeAsText(node, 0, false, [], useColors);
|
|
194
280
|
}
|
|
195
281
|
}
|
|
196
282
|
}
|
|
@@ -288,72 +374,78 @@ class TakeSnapshotHandler {
|
|
|
288
374
|
}
|
|
289
375
|
return true;
|
|
290
376
|
}
|
|
291
|
-
renderNodeAsText(node, depth, isLast = false, parentIsLast = []) {
|
|
377
|
+
renderNodeAsText(node, depth, isLast = false, parentIsLast = [], useColors = true) {
|
|
292
378
|
if (!this.shouldIncludeNode(node)) {
|
|
293
379
|
return '';
|
|
294
380
|
}
|
|
295
381
|
let indent = '';
|
|
296
382
|
for (let i = 0; i < parentIsLast.length; i++) {
|
|
297
|
-
|
|
383
|
+
const symbol = parentIsLast[i] ? ' ' : '│ ';
|
|
384
|
+
indent += this.colorTreeSymbol(symbol, useColors);
|
|
298
385
|
}
|
|
299
|
-
const
|
|
386
|
+
const prefixSymbol = depth > 0 ? (isLast ? '└── ' : '├── ') : '';
|
|
387
|
+
const prefix = this.colorTreeSymbol(prefixSymbol, useColors);
|
|
300
388
|
let output = '';
|
|
301
389
|
if (node.nodeType === 3) {
|
|
302
390
|
if (node.textContent) {
|
|
303
391
|
const truncatedText = this.truncateText(node.textContent.trim(), 40);
|
|
304
|
-
output += `${indent}${prefix}
|
|
392
|
+
output += `${indent}${prefix}${this.colorText(truncatedText, useColors)}\n`;
|
|
305
393
|
}
|
|
306
394
|
}
|
|
307
395
|
else if (node.nodeType === 1) {
|
|
308
|
-
const tag = node.nodeName
|
|
396
|
+
const tag = this.colorTag(node.nodeName, useColors);
|
|
309
397
|
let description = tag;
|
|
310
398
|
const attrs = [];
|
|
311
|
-
if (node.attributes.id)
|
|
312
|
-
attrs.push(
|
|
399
|
+
if (node.attributes.id) {
|
|
400
|
+
attrs.push(this.colorId(node.attributes.id, useColors));
|
|
401
|
+
}
|
|
313
402
|
if (node.attributes.class) {
|
|
314
403
|
const classes = node.attributes.class.split(/\s+/).filter((c) => c.trim().length > 0);
|
|
315
404
|
classes.forEach((cls) => {
|
|
316
|
-
attrs.push(
|
|
405
|
+
attrs.push(this.colorClass(cls.trim(), useColors));
|
|
317
406
|
});
|
|
318
407
|
}
|
|
319
|
-
if (node.attributes.type)
|
|
320
|
-
attrs.push(
|
|
321
|
-
|
|
322
|
-
|
|
408
|
+
if (node.attributes.type) {
|
|
409
|
+
attrs.push(this.colorAttribute('type', node.attributes.type, useColors));
|
|
410
|
+
}
|
|
411
|
+
if (node.attributes.name) {
|
|
412
|
+
attrs.push(this.colorAttribute('name', node.attributes.name, useColors));
|
|
413
|
+
}
|
|
323
414
|
if (attrs.length > 0) {
|
|
324
415
|
description += `(${attrs.join(' ')})`;
|
|
325
416
|
}
|
|
326
417
|
if (node.nodeName === 'img' && node.attributes.alt) {
|
|
327
418
|
const altText = this.truncateText(node.attributes.alt, 40);
|
|
328
|
-
description += `:
|
|
419
|
+
description += `: ${this.colorText(altText, useColors)}`;
|
|
329
420
|
}
|
|
330
421
|
else if (node.nodeName === 'a' && node.attributes.href) {
|
|
331
|
-
description += `:
|
|
422
|
+
description += `: ${this.colorUrl(node.attributes.href, useColors)}`;
|
|
332
423
|
}
|
|
333
424
|
else if (['input', 'textarea'].includes(node.nodeName)) {
|
|
334
425
|
if (node.attributes.placeholder) {
|
|
335
426
|
const placeholderText = this.truncateText(node.attributes.placeholder, 40);
|
|
336
|
-
description += `:
|
|
427
|
+
description += `: ${this.colorText(placeholderText, useColors)}`;
|
|
337
428
|
}
|
|
338
429
|
else if (node.inputValue) {
|
|
339
430
|
const inputText = this.truncateText(node.inputValue, 40);
|
|
340
|
-
description += `:
|
|
431
|
+
description += `: ${this.colorText(inputText, useColors)}`;
|
|
341
432
|
}
|
|
342
433
|
else if (node.nodeName === 'textarea') {
|
|
343
434
|
const textContent = this.extractTextContent(node);
|
|
344
435
|
if (textContent) {
|
|
345
436
|
const truncatedText = this.truncateText(textContent, 40);
|
|
346
|
-
description += `:
|
|
437
|
+
description += `: ${this.colorText(truncatedText, useColors)}`;
|
|
347
438
|
}
|
|
348
439
|
}
|
|
349
|
-
if (node.checked)
|
|
350
|
-
description += '
|
|
440
|
+
if (node.checked) {
|
|
441
|
+
description += ` ${this.colorSpecial('[checked]', useColors)}`;
|
|
442
|
+
}
|
|
351
443
|
}
|
|
352
444
|
else if (node.nodeName === 'button' || ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'].includes(node.nodeName)) {
|
|
353
445
|
const textContent = this.extractTextContent(node);
|
|
354
446
|
if (textContent) {
|
|
355
447
|
const truncatedText = this.truncateText(textContent, 40);
|
|
356
|
-
description += `:
|
|
448
|
+
description += `: ${this.colorText(truncatedText, useColors)}`;
|
|
357
449
|
}
|
|
358
450
|
}
|
|
359
451
|
output += `${indent}${prefix}${description}\n`;
|
|
@@ -365,7 +457,8 @@ class TakeSnapshotHandler {
|
|
|
365
457
|
const textContent = this.extractTextContent(node);
|
|
366
458
|
if (textContent && textContent.trim().length > 0) {
|
|
367
459
|
const truncatedText = this.truncateText(textContent.trim(), 40);
|
|
368
|
-
|
|
460
|
+
const treeSymbol = this.colorTreeSymbol('│ └── ', useColors);
|
|
461
|
+
output += `${indent}${treeSymbol}${this.colorText(truncatedText, useColors)}\n`;
|
|
369
462
|
return output;
|
|
370
463
|
}
|
|
371
464
|
return output;
|
|
@@ -375,7 +468,7 @@ class TakeSnapshotHandler {
|
|
|
375
468
|
for (let i = 0; i < meaningfulChildren.length; i++) {
|
|
376
469
|
const child = meaningfulChildren[i];
|
|
377
470
|
const childIsLast = i === meaningfulChildren.length - 1;
|
|
378
|
-
output += this.renderNodeAsText(child, depth + 1, childIsLast, newParentIsLast);
|
|
471
|
+
output += this.renderNodeAsText(child, depth + 1, childIsLast, newParentIsLast, useColors);
|
|
379
472
|
}
|
|
380
473
|
}
|
|
381
474
|
}
|
|
@@ -401,17 +494,17 @@ class TakeSnapshotHandler {
|
|
|
401
494
|
return text;
|
|
402
495
|
return text.substring(0, maxLength) + '...';
|
|
403
496
|
}
|
|
404
|
-
buildTextFromDOMNode(root, _url, title) {
|
|
405
|
-
let output =
|
|
497
|
+
buildTextFromDOMNode(root, _url, title, useColors = true) {
|
|
498
|
+
let output = this.colorPageTitle(title || 'Untitled', useColors) + '\n';
|
|
406
499
|
const bodyNode = this.findBodyInDOMTree(root);
|
|
407
500
|
if (bodyNode) {
|
|
408
|
-
output += this.renderDOMNodeAsText(bodyNode, 0);
|
|
501
|
+
output += this.renderDOMNodeAsText(bodyNode, 0, false, [], useColors);
|
|
409
502
|
}
|
|
410
503
|
else {
|
|
411
504
|
if (root.children) {
|
|
412
505
|
for (const child of root.children) {
|
|
413
506
|
if (this.shouldIncludeDOMNode(child)) {
|
|
414
|
-
output += this.renderDOMNodeAsText(child, 0);
|
|
507
|
+
output += this.renderDOMNodeAsText(child, 0, false, [], useColors);
|
|
415
508
|
}
|
|
416
509
|
}
|
|
417
510
|
}
|
|
@@ -448,25 +541,28 @@ class TakeSnapshotHandler {
|
|
|
448
541
|
}
|
|
449
542
|
return true;
|
|
450
543
|
}
|
|
451
|
-
renderDOMNodeAsText(node, depth, isLast = false, parentIsLast = []) {
|
|
544
|
+
renderDOMNodeAsText(node, depth, isLast = false, parentIsLast = [], useColors = true) {
|
|
452
545
|
if (!this.shouldIncludeDOMNode(node)) {
|
|
453
546
|
return '';
|
|
454
547
|
}
|
|
455
548
|
let indent = '';
|
|
456
549
|
for (let i = 0; i < parentIsLast.length; i++) {
|
|
457
|
-
|
|
550
|
+
const symbol = parentIsLast[i] ? ' ' : '│ ';
|
|
551
|
+
indent += this.colorTreeSymbol(symbol, useColors);
|
|
458
552
|
}
|
|
459
|
-
const
|
|
553
|
+
const prefixSymbol = depth > 0 ? (isLast ? '└── ' : '├── ') : '';
|
|
554
|
+
const prefix = this.colorTreeSymbol(prefixSymbol, useColors);
|
|
460
555
|
let output = '';
|
|
461
556
|
if (node.nodeType === 3) {
|
|
462
557
|
const text = (node.nodeValue || '').trim();
|
|
463
558
|
if (text) {
|
|
464
559
|
const truncatedText = this.truncateText(text, 40);
|
|
465
|
-
output += `${indent}${prefix}
|
|
560
|
+
output += `${indent}${prefix}${this.colorText(truncatedText, useColors)}\n`;
|
|
466
561
|
}
|
|
467
562
|
}
|
|
468
563
|
else if (node.nodeType === 1) {
|
|
469
|
-
const
|
|
564
|
+
const nodeName = (node.nodeName || '').toLowerCase();
|
|
565
|
+
const tag = this.colorTag(nodeName, useColors);
|
|
470
566
|
let description = tag;
|
|
471
567
|
const attrs = [];
|
|
472
568
|
if (node.attributes) {
|
|
@@ -474,36 +570,38 @@ class TakeSnapshotHandler {
|
|
|
474
570
|
const name = node.attributes[i];
|
|
475
571
|
const value = node.attributes[i + 1];
|
|
476
572
|
if (['id', 'class', 'type', 'name', 'href', 'src', 'alt', 'placeholder', 'value', 'title'].includes(name)) {
|
|
477
|
-
if (name === 'id')
|
|
478
|
-
attrs.push(
|
|
573
|
+
if (name === 'id') {
|
|
574
|
+
attrs.push(this.colorId(value, useColors));
|
|
575
|
+
}
|
|
479
576
|
else if (name === 'class') {
|
|
480
577
|
const classes = value.split(/\s+/).filter((c) => c.trim().length > 0);
|
|
481
578
|
classes.forEach((cls) => {
|
|
482
|
-
attrs.push(
|
|
579
|
+
attrs.push(this.colorClass(cls.trim(), useColors));
|
|
483
580
|
});
|
|
484
581
|
}
|
|
485
|
-
else if (name === 'type')
|
|
486
|
-
attrs.push(
|
|
487
|
-
|
|
488
|
-
|
|
582
|
+
else if (name === 'type') {
|
|
583
|
+
attrs.push(this.colorAttribute('type', value, useColors));
|
|
584
|
+
}
|
|
585
|
+
else {
|
|
586
|
+
attrs.push(this.colorAttribute(name, value, useColors));
|
|
587
|
+
}
|
|
489
588
|
}
|
|
490
589
|
}
|
|
491
590
|
}
|
|
492
591
|
if (attrs.length > 0) {
|
|
493
592
|
description += `(${attrs.join(' ')})`;
|
|
494
593
|
}
|
|
495
|
-
const nodeName = (node.nodeName || '').toLowerCase();
|
|
496
594
|
if (nodeName === 'img' && node.attributes) {
|
|
497
595
|
const altIndex = node.attributes.indexOf('alt');
|
|
498
596
|
if (altIndex >= 0 && altIndex + 1 < node.attributes.length) {
|
|
499
597
|
const altText = this.truncateText(node.attributes[altIndex + 1], 40);
|
|
500
|
-
description += `:
|
|
598
|
+
description += `: ${this.colorText(altText, useColors)}`;
|
|
501
599
|
}
|
|
502
600
|
}
|
|
503
601
|
else if (nodeName === 'a' && node.attributes) {
|
|
504
602
|
const hrefIndex = node.attributes.indexOf('href');
|
|
505
603
|
if (hrefIndex >= 0 && hrefIndex + 1 < node.attributes.length) {
|
|
506
|
-
description += `:
|
|
604
|
+
description += `: ${this.colorUrl(node.attributes[hrefIndex + 1], useColors)}`;
|
|
507
605
|
}
|
|
508
606
|
}
|
|
509
607
|
else if (['input', 'textarea'].includes(nodeName)) {
|
|
@@ -511,14 +609,14 @@ class TakeSnapshotHandler {
|
|
|
511
609
|
const placeholderIndex = node.attributes.indexOf('placeholder');
|
|
512
610
|
if (placeholderIndex >= 0 && placeholderIndex + 1 < node.attributes.length) {
|
|
513
611
|
const placeholderText = this.truncateText(node.attributes[placeholderIndex + 1], 40);
|
|
514
|
-
description += `:
|
|
612
|
+
description += `: ${this.colorText(placeholderText, useColors)}`;
|
|
515
613
|
}
|
|
516
614
|
}
|
|
517
615
|
if (nodeName === 'textarea') {
|
|
518
616
|
const textContent = this.extractTextFromDOMNode(node);
|
|
519
617
|
if (textContent) {
|
|
520
618
|
const truncatedText = this.truncateText(textContent, 40);
|
|
521
|
-
description += `:
|
|
619
|
+
description += `: ${this.colorText(truncatedText, useColors)}`;
|
|
522
620
|
}
|
|
523
621
|
}
|
|
524
622
|
}
|
|
@@ -526,7 +624,7 @@ class TakeSnapshotHandler {
|
|
|
526
624
|
const textContent = this.extractTextFromDOMNode(node);
|
|
527
625
|
if (textContent) {
|
|
528
626
|
const truncatedText = this.truncateText(textContent, 40);
|
|
529
|
-
description += `:
|
|
627
|
+
description += `: ${this.colorText(truncatedText, useColors)}`;
|
|
530
628
|
}
|
|
531
629
|
}
|
|
532
630
|
output += `${indent}${prefix}${description}\n`;
|
|
@@ -539,7 +637,8 @@ class TakeSnapshotHandler {
|
|
|
539
637
|
const textContent = this.extractTextFromDOMNode(node);
|
|
540
638
|
if (textContent && textContent.trim().length > 0) {
|
|
541
639
|
const truncatedText = this.truncateText(textContent.trim(), 40);
|
|
542
|
-
|
|
640
|
+
const treeSymbol = this.colorTreeSymbol('│ └── ', useColors);
|
|
641
|
+
output += `${indent}${treeSymbol}${this.colorText(truncatedText, useColors)}\n`;
|
|
543
642
|
return output;
|
|
544
643
|
}
|
|
545
644
|
return output;
|
|
@@ -549,7 +648,7 @@ class TakeSnapshotHandler {
|
|
|
549
648
|
for (let i = 0; i < meaningfulChildren.length; i++) {
|
|
550
649
|
const child = meaningfulChildren[i];
|
|
551
650
|
const childIsLast = i === meaningfulChildren.length - 1;
|
|
552
|
-
output += this.renderDOMNodeAsText(child, depth + 1, childIsLast, newParentIsLast);
|
|
651
|
+
output += this.renderDOMNodeAsText(child, depth + 1, childIsLast, newParentIsLast, useColors);
|
|
553
652
|
}
|
|
554
653
|
}
|
|
555
654
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-cdp-cli",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.4",
|
|
4
4
|
"description": "Browser automation CLI via Chrome DevTools Protocol. Designed for developers and AI assistants - combines dedicated commands for common tasks with flexible JavaScript execution for complex scenarios. Features: element interaction, screenshots, DOM snapshots, console/network monitoring. Built-in IDE integration for Cursor and Claude.",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|