chrome-cdp-cli 2.0.3 → 2.0.5
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 +6 -3
- package/dist/cli/CLIApplication.js +57 -6
- package/dist/cli/CommandSchemaRegistry.js +2 -2
- package/dist/cli/EnhancedCLIInterface.js +2 -1
- package/dist/cli/OutputFormatter.js +3 -3
- package/dist/handlers/ClickHandler.js +89 -3
- package/dist/handlers/DragHandler.js +137 -4
- package/package.json +1 -1
|
@@ -161,7 +161,8 @@ class ArgumentParser {
|
|
|
161
161
|
{ name: 'quiet', short: 'q', type: 'boolean', description: 'Enable quiet mode', default: false },
|
|
162
162
|
{ name: 'timeout', short: 't', type: 'number', description: 'Command timeout in milliseconds', default: 30000 },
|
|
163
163
|
{ name: 'debug', short: 'd', type: 'boolean', description: 'Enable debug logging', default: false },
|
|
164
|
-
{ name: 'config', short: 'c', type: 'string', description: 'Configuration file path' }
|
|
164
|
+
{ name: 'config', short: 'c', type: 'string', description: 'Configuration file path' },
|
|
165
|
+
{ name: 'target-index', type: 'number', description: 'Target page index (1-based, excludes DevTools windows)' }
|
|
165
166
|
];
|
|
166
167
|
while (i < args.length) {
|
|
167
168
|
const arg = args[i];
|
|
@@ -348,13 +349,14 @@ class ArgumentParser {
|
|
|
348
349
|
}
|
|
349
350
|
const stringValue = String(value);
|
|
350
351
|
switch (optionDef.type) {
|
|
351
|
-
case 'number':
|
|
352
|
+
case 'number': {
|
|
352
353
|
const numValue = Number(stringValue);
|
|
353
354
|
if (isNaN(numValue)) {
|
|
354
355
|
throw new Error(`Option --${optionDef.name} must be a number, got: ${stringValue}`);
|
|
355
356
|
}
|
|
356
357
|
return numValue;
|
|
357
|
-
|
|
358
|
+
}
|
|
359
|
+
case 'boolean': {
|
|
358
360
|
if (typeof value === 'boolean') {
|
|
359
361
|
return value;
|
|
360
362
|
}
|
|
@@ -366,6 +368,7 @@ class ArgumentParser {
|
|
|
366
368
|
return false;
|
|
367
369
|
}
|
|
368
370
|
throw new Error(`Option --${optionDef.name} must be a boolean, got: ${stringValue}`);
|
|
371
|
+
}
|
|
369
372
|
case 'array':
|
|
370
373
|
if (Array.isArray(value)) {
|
|
371
374
|
return value;
|
|
@@ -94,7 +94,12 @@ class CLIApplication {
|
|
|
94
94
|
await this.ensureProxyReady();
|
|
95
95
|
if (this.needsConnection(command.name)) {
|
|
96
96
|
this.logger.debug('Command needs connection, ensuring connection...');
|
|
97
|
-
|
|
97
|
+
try {
|
|
98
|
+
await this.ensureConnection(command);
|
|
99
|
+
}
|
|
100
|
+
catch (connectionError) {
|
|
101
|
+
this.logger.debug('Connection failed, will be handled by command router:', connectionError);
|
|
102
|
+
}
|
|
98
103
|
}
|
|
99
104
|
this.logger.debug('Executing command via CLI interface...');
|
|
100
105
|
const result = await this.cli.execute(command);
|
|
@@ -128,6 +133,26 @@ class CLIApplication {
|
|
|
128
133
|
];
|
|
129
134
|
return !noConnectionCommands.includes(commandName);
|
|
130
135
|
}
|
|
136
|
+
isDevToolsWindow(target) {
|
|
137
|
+
const url = target.url.toLowerCase();
|
|
138
|
+
const title = target.title.toLowerCase();
|
|
139
|
+
return url.startsWith('chrome-devtools://') ||
|
|
140
|
+
url.startsWith('devtools://') ||
|
|
141
|
+
title.includes('devtools') ||
|
|
142
|
+
title.includes('chrome devtools');
|
|
143
|
+
}
|
|
144
|
+
displayAvailableTargets(targets) {
|
|
145
|
+
console.log('\nAvailable Chrome pages (excluding DevTools windows):');
|
|
146
|
+
targets.forEach((target, index) => {
|
|
147
|
+
const displayUrl = target.url.length > 60 ? target.url.substring(0, 57) + '...' : target.url;
|
|
148
|
+
console.log(` [${index + 1}] ${target.title || '(Untitled)'}`);
|
|
149
|
+
console.log(` ${displayUrl}`);
|
|
150
|
+
});
|
|
151
|
+
console.log('\nOptions:');
|
|
152
|
+
console.log(' 1. Use --target-index <number> to select a specific page');
|
|
153
|
+
console.log(' Example: chrome-cdp-cli --target-index 1 eval "document.title"');
|
|
154
|
+
console.log(' 2. Close other pages until only one page remains\n');
|
|
155
|
+
}
|
|
131
156
|
async ensureConnection(command) {
|
|
132
157
|
if (this.client) {
|
|
133
158
|
return;
|
|
@@ -138,14 +163,35 @@ class CLIApplication {
|
|
|
138
163
|
throw new Error(`No Chrome targets found at ${command.config.host}:${command.config.port}. ` +
|
|
139
164
|
'Make sure Chrome is running with --remote-debugging-port=9222');
|
|
140
165
|
}
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
166
|
+
const pageTargets = targets.filter(target => target.type === 'page');
|
|
167
|
+
const nonDevToolsTargets = pageTargets.filter(target => !this.isDevToolsWindow(target));
|
|
168
|
+
if (nonDevToolsTargets.length === 0) {
|
|
169
|
+
throw new Error('No page targets available (excluding DevTools windows). Open a tab in Chrome.');
|
|
144
170
|
}
|
|
145
|
-
|
|
171
|
+
let selectedTarget;
|
|
172
|
+
if (command.config.targetIndex !== undefined) {
|
|
173
|
+
const index = command.config.targetIndex - 1;
|
|
174
|
+
if (index < 0 || index >= nonDevToolsTargets.length) {
|
|
175
|
+
this.displayAvailableTargets(nonDevToolsTargets);
|
|
176
|
+
throw new Error(`Invalid target index: ${command.config.targetIndex}. ` +
|
|
177
|
+
`Please choose a number between 1 and ${nonDevToolsTargets.length}.`);
|
|
178
|
+
}
|
|
179
|
+
selectedTarget = nonDevToolsTargets[index];
|
|
180
|
+
}
|
|
181
|
+
else {
|
|
182
|
+
if (nonDevToolsTargets.length === 1) {
|
|
183
|
+
selectedTarget = nonDevToolsTargets[0];
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
this.displayAvailableTargets(nonDevToolsTargets);
|
|
187
|
+
throw new Error(`Multiple Chrome pages found (${nonDevToolsTargets.length}). ` +
|
|
188
|
+
'Please specify --target-index <number> to select a page, or close other pages until only one remains.');
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
this.client = await this.connectionManager.connectToTarget(selectedTarget);
|
|
146
192
|
this.cli.setClient(this.client);
|
|
147
193
|
if (command.config.verbose) {
|
|
148
|
-
this.logger.info(`Connected to Chrome target: ${
|
|
194
|
+
this.logger.info(`Connected to Chrome target: ${selectedTarget.title} (${selectedTarget.url})`);
|
|
149
195
|
}
|
|
150
196
|
}
|
|
151
197
|
catch (error) {
|
|
@@ -208,6 +254,11 @@ class CLIApplication {
|
|
|
208
254
|
else if (arg === '--debug' || arg === '-d') {
|
|
209
255
|
options.debug = true;
|
|
210
256
|
}
|
|
257
|
+
else if (arg === '--target-index') {
|
|
258
|
+
if (i + 1 < args.length) {
|
|
259
|
+
options.targetIndex = parseInt(args[i + 1], 10);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
211
262
|
}
|
|
212
263
|
return options;
|
|
213
264
|
}
|
|
@@ -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
|
|
@@ -72,7 +72,8 @@ class EnhancedCLIInterface {
|
|
|
72
72
|
verbose: globalOptions.verbose || CLIInterface_1.DEFAULT_CLI_CONFIG.verbose,
|
|
73
73
|
quiet: globalOptions.quiet || CLIInterface_1.DEFAULT_CLI_CONFIG.quiet,
|
|
74
74
|
timeout: globalOptions.timeout || CLIInterface_1.DEFAULT_CLI_CONFIG.timeout,
|
|
75
|
-
debug: globalOptions.debug || CLIInterface_1.DEFAULT_CLI_CONFIG.debug
|
|
75
|
+
debug: globalOptions.debug || CLIInterface_1.DEFAULT_CLI_CONFIG.debug,
|
|
76
|
+
targetIndex: globalOptions['target-index'] !== undefined ? globalOptions['target-index'] : undefined
|
|
76
77
|
};
|
|
77
78
|
}
|
|
78
79
|
buildCommandArguments(parseResult) {
|
|
@@ -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.5",
|
|
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",
|