chrome-cdp-cli 2.0.3 → 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.
|
@@ -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
|
|
@@ -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}`);
|
|
@@ -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
|
}
|
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",
|