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
- case 'boolean':
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: 'fromSelector',
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: 'toSelector',
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, options);
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
- let output = `${timestamp} ${typeIcon} ${obj.text}`;
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, _options) {
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 DOM.querySelector and Runtime.callFunctionOn for precise control
235
- - Falls back to JavaScript eval if CDP approach fails
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 actual click events that work with event listeners
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 JavaScript DragEvent simulation for drag and drop
261
- - Dispatches all standard drag events: dragstart, dragenter, dragover, drop, dragend
262
- - Automatically calculates element center positions for drag coordinates
263
- - Works with HTML5 drag and drop API event listeners
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",
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",