chrome-cdp-cli 1.2.4 → 1.5.0
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 +347 -41
- package/dist/cli/CLIApplication.js +9 -0
- package/dist/cli/CommandRouter.js +6 -0
- package/dist/handlers/ClickHandler.js +241 -0
- package/dist/handlers/DragHandler.js +267 -0
- package/dist/handlers/FillFormHandler.js +245 -0
- package/dist/handlers/FillHandler.js +354 -0
- package/dist/handlers/HandleDialogHandler.js +197 -0
- package/dist/handlers/HoverHandler.js +268 -0
- package/dist/handlers/InstallClaudeSkillHandler.js +632 -57
- package/dist/handlers/InstallCursorCommandHandler.js +146 -73
- package/dist/handlers/PressKeyHandler.js +337 -0
- package/dist/handlers/UploadFileHandler.js +325 -0
- package/dist/handlers/WaitForHandler.js +331 -0
- package/dist/handlers/index.js +9 -0
- package/package.json +2 -2
|
@@ -0,0 +1,325 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.UploadFileHandler = void 0;
|
|
37
|
+
const fs = __importStar(require("fs"));
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
class UploadFileHandler {
|
|
40
|
+
constructor() {
|
|
41
|
+
this.name = 'upload_file';
|
|
42
|
+
}
|
|
43
|
+
async execute(client, args) {
|
|
44
|
+
const uploadArgs = args;
|
|
45
|
+
if (!uploadArgs.selector) {
|
|
46
|
+
return {
|
|
47
|
+
success: false,
|
|
48
|
+
error: 'CSS selector is required for upload_file command'
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
if (typeof uploadArgs.selector !== 'string') {
|
|
52
|
+
return {
|
|
53
|
+
success: false,
|
|
54
|
+
error: 'CSS selector must be a string'
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
if (!uploadArgs.filePath) {
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
error: 'File path is required for upload_file command'
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (typeof uploadArgs.filePath !== 'string') {
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
error: 'File path must be a string'
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
const absoluteFilePath = path.resolve(uploadArgs.filePath);
|
|
70
|
+
if (!fs.existsSync(absoluteFilePath)) {
|
|
71
|
+
return {
|
|
72
|
+
success: false,
|
|
73
|
+
error: `File not found: ${absoluteFilePath}`
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
const stats = fs.statSync(absoluteFilePath);
|
|
77
|
+
if (!stats.isFile()) {
|
|
78
|
+
return {
|
|
79
|
+
success: false,
|
|
80
|
+
error: `Path is not a file: ${absoluteFilePath}`
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
try {
|
|
84
|
+
await client.send('DOM.enable');
|
|
85
|
+
await client.send('Runtime.enable');
|
|
86
|
+
const timeout = uploadArgs.timeout || 5000;
|
|
87
|
+
const waitForElement = uploadArgs.waitForElement !== false;
|
|
88
|
+
if (waitForElement) {
|
|
89
|
+
const elementFound = await this.waitForElement(client, uploadArgs.selector, timeout);
|
|
90
|
+
if (!elementFound) {
|
|
91
|
+
return {
|
|
92
|
+
success: false,
|
|
93
|
+
error: `Element with selector "${uploadArgs.selector}" not found within ${timeout}ms`
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
const isFileInput = await this.verifyFileInput(client, uploadArgs.selector);
|
|
98
|
+
if (!isFileInput.success) {
|
|
99
|
+
return isFileInput;
|
|
100
|
+
}
|
|
101
|
+
const cdpResult = await this.uploadViaCDP(client, uploadArgs.selector, absoluteFilePath);
|
|
102
|
+
if (cdpResult.success) {
|
|
103
|
+
return cdpResult;
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
success: false,
|
|
107
|
+
error: `File upload failed: ${cdpResult.error}. File uploads require CDP support.`
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
return {
|
|
112
|
+
success: false,
|
|
113
|
+
error: error instanceof Error ? error.message : String(error)
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
async waitForElement(client, selector, timeout) {
|
|
118
|
+
const startTime = Date.now();
|
|
119
|
+
while (Date.now() - startTime < timeout) {
|
|
120
|
+
try {
|
|
121
|
+
const result = await client.send('Runtime.evaluate', {
|
|
122
|
+
expression: `document.querySelector('${selector.replace(/'/g, "\\'")}') !== null`,
|
|
123
|
+
returnByValue: true
|
|
124
|
+
});
|
|
125
|
+
if (result.result.value) {
|
|
126
|
+
return true;
|
|
127
|
+
}
|
|
128
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
129
|
+
}
|
|
130
|
+
catch (error) {
|
|
131
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
return false;
|
|
135
|
+
}
|
|
136
|
+
async verifyFileInput(client, selector) {
|
|
137
|
+
try {
|
|
138
|
+
const escapedSelector = selector.replace(/'/g, "\\'").replace(/"/g, '\\"');
|
|
139
|
+
const expression = `
|
|
140
|
+
(function() {
|
|
141
|
+
const element = document.querySelector('${escapedSelector}');
|
|
142
|
+
if (!element) {
|
|
143
|
+
throw new Error('Element with selector "${escapedSelector}" not found');
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (element.tagName.toLowerCase() !== 'input') {
|
|
147
|
+
throw new Error('Element is not an input element');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (element.type.toLowerCase() !== 'file') {
|
|
151
|
+
throw new Error('Element is not a file input (type="file")');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
success: true,
|
|
156
|
+
tagName: element.tagName,
|
|
157
|
+
type: element.type,
|
|
158
|
+
id: element.id,
|
|
159
|
+
className: element.className,
|
|
160
|
+
multiple: element.multiple,
|
|
161
|
+
accept: element.accept
|
|
162
|
+
};
|
|
163
|
+
})()
|
|
164
|
+
`;
|
|
165
|
+
const response = await client.send('Runtime.evaluate', {
|
|
166
|
+
expression: expression,
|
|
167
|
+
returnByValue: true
|
|
168
|
+
});
|
|
169
|
+
if (response.exceptionDetails) {
|
|
170
|
+
return {
|
|
171
|
+
success: false,
|
|
172
|
+
error: response.exceptionDetails.exception?.description || response.exceptionDetails.text
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
return {
|
|
176
|
+
success: true,
|
|
177
|
+
data: response.result.value
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
return {
|
|
182
|
+
success: false,
|
|
183
|
+
error: `File input verification failed: ${error instanceof Error ? error.message : String(error)}`
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
async uploadViaCDP(client, selector, filePath) {
|
|
188
|
+
try {
|
|
189
|
+
const documentResponse = await client.send('DOM.getDocument');
|
|
190
|
+
const rootNodeId = documentResponse.root.nodeId;
|
|
191
|
+
const queryResponse = await client.send('DOM.querySelector', {
|
|
192
|
+
nodeId: rootNodeId,
|
|
193
|
+
selector: selector
|
|
194
|
+
});
|
|
195
|
+
if (!queryResponse.nodeId || queryResponse.nodeId === 0) {
|
|
196
|
+
return {
|
|
197
|
+
success: false,
|
|
198
|
+
error: `File input element with selector "${selector}" not found`
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
await client.send('DOM.setFileInputFiles', {
|
|
202
|
+
nodeId: queryResponse.nodeId,
|
|
203
|
+
files: [filePath]
|
|
204
|
+
});
|
|
205
|
+
const changeEventResult = await this.triggerChangeEvent(client, selector, filePath);
|
|
206
|
+
return {
|
|
207
|
+
success: true,
|
|
208
|
+
data: {
|
|
209
|
+
selector: selector,
|
|
210
|
+
filePath: filePath,
|
|
211
|
+
fileName: path.basename(filePath),
|
|
212
|
+
fileSize: fs.statSync(filePath).size,
|
|
213
|
+
method: 'CDP',
|
|
214
|
+
changeEvent: changeEventResult
|
|
215
|
+
}
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
return {
|
|
220
|
+
success: false,
|
|
221
|
+
error: `CDP file upload failed: ${error instanceof Error ? error.message : String(error)}`
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
async triggerChangeEvent(client, selector, filePath) {
|
|
226
|
+
try {
|
|
227
|
+
const escapedSelector = selector.replace(/'/g, "\\'").replace(/"/g, '\\"');
|
|
228
|
+
const fileName = path.basename(filePath);
|
|
229
|
+
const expression = `
|
|
230
|
+
(function() {
|
|
231
|
+
const element = document.querySelector('${escapedSelector}');
|
|
232
|
+
if (element) {
|
|
233
|
+
// Trigger change event
|
|
234
|
+
const changeEvent = new Event('change', { bubbles: true });
|
|
235
|
+
element.dispatchEvent(changeEvent);
|
|
236
|
+
|
|
237
|
+
// Also trigger input event
|
|
238
|
+
const inputEvent = new Event('input', { bubbles: true });
|
|
239
|
+
element.dispatchEvent(inputEvent);
|
|
240
|
+
|
|
241
|
+
return {
|
|
242
|
+
success: true,
|
|
243
|
+
fileName: '${fileName}',
|
|
244
|
+
filesLength: element.files ? element.files.length : 0
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
return { success: false, error: 'Element not found' };
|
|
248
|
+
})()
|
|
249
|
+
`;
|
|
250
|
+
const response = await client.send('Runtime.evaluate', {
|
|
251
|
+
expression: expression,
|
|
252
|
+
returnByValue: true,
|
|
253
|
+
userGesture: true
|
|
254
|
+
});
|
|
255
|
+
return response.result.value;
|
|
256
|
+
}
|
|
257
|
+
catch (error) {
|
|
258
|
+
return { success: false, error: error instanceof Error ? error.message : String(error) };
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
validateArgs(args) {
|
|
262
|
+
if (typeof args !== 'object' || args === null) {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
const uploadArgs = args;
|
|
266
|
+
if (!uploadArgs.selector || typeof uploadArgs.selector !== 'string') {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
if (!uploadArgs.filePath || typeof uploadArgs.filePath !== 'string') {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
if (uploadArgs.waitForElement !== undefined && typeof uploadArgs.waitForElement !== 'boolean') {
|
|
273
|
+
return false;
|
|
274
|
+
}
|
|
275
|
+
if (uploadArgs.timeout !== undefined && typeof uploadArgs.timeout !== 'number') {
|
|
276
|
+
return false;
|
|
277
|
+
}
|
|
278
|
+
return true;
|
|
279
|
+
}
|
|
280
|
+
getHelp() {
|
|
281
|
+
return `
|
|
282
|
+
upload_file - Upload a file to a file input element
|
|
283
|
+
|
|
284
|
+
Usage:
|
|
285
|
+
upload_file <selector> <filePath>
|
|
286
|
+
upload_file "#file-input" "./document.pdf"
|
|
287
|
+
upload_file "input[type='file']" "/path/to/image.jpg" --timeout 10000
|
|
288
|
+
|
|
289
|
+
Arguments:
|
|
290
|
+
<selector> CSS selector for the file input element
|
|
291
|
+
<filePath> Path to the file to upload (relative or absolute)
|
|
292
|
+
|
|
293
|
+
Options:
|
|
294
|
+
--wait-for-element Wait for element to be available (default: true)
|
|
295
|
+
--no-wait Don't wait for element (same as --wait-for-element=false)
|
|
296
|
+
--timeout <ms> Timeout for waiting for element (default: 5000ms)
|
|
297
|
+
|
|
298
|
+
Examples:
|
|
299
|
+
# Upload a document
|
|
300
|
+
upload_file "#document-upload" "./contract.pdf"
|
|
301
|
+
|
|
302
|
+
# Upload an image
|
|
303
|
+
upload_file "input[name='avatar']" "/home/user/photo.jpg"
|
|
304
|
+
|
|
305
|
+
# Upload with custom timeout
|
|
306
|
+
upload_file ".file-input" "./large-file.zip" --timeout 15000
|
|
307
|
+
|
|
308
|
+
# Upload without waiting (fail immediately if not found)
|
|
309
|
+
upload_file "#upload" "./file.txt" --no-wait
|
|
310
|
+
|
|
311
|
+
Requirements:
|
|
312
|
+
- Target element must be an <input type="file"> element
|
|
313
|
+
- File must exist and be readable
|
|
314
|
+
- File path can be relative (to current directory) or absolute
|
|
315
|
+
|
|
316
|
+
Note:
|
|
317
|
+
- Uses CDP DOM.setFileInputFiles for reliable file upload
|
|
318
|
+
- Automatically triggers 'change' and 'input' events after upload
|
|
319
|
+
- Verifies that target element is a valid file input before upload
|
|
320
|
+
- Supports both single and multiple file inputs
|
|
321
|
+
- File path is resolved to absolute path before upload
|
|
322
|
+
`;
|
|
323
|
+
}
|
|
324
|
+
}
|
|
325
|
+
exports.UploadFileHandler = UploadFileHandler;
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.WaitForHandler = void 0;
|
|
4
|
+
class WaitForHandler {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'wait_for';
|
|
7
|
+
}
|
|
8
|
+
async execute(client, args) {
|
|
9
|
+
const waitArgs = args;
|
|
10
|
+
if (!waitArgs.selector) {
|
|
11
|
+
return {
|
|
12
|
+
success: false,
|
|
13
|
+
error: 'CSS selector is required for wait_for command'
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (typeof waitArgs.selector !== 'string') {
|
|
17
|
+
return {
|
|
18
|
+
success: false,
|
|
19
|
+
error: 'CSS selector must be a string'
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
await client.send('Runtime.enable');
|
|
24
|
+
const timeout = waitArgs.timeout || 10000;
|
|
25
|
+
const condition = waitArgs.condition || (waitArgs.visible ? 'visible' : 'exists');
|
|
26
|
+
const pollInterval = waitArgs.pollInterval || 100;
|
|
27
|
+
const result = await this.waitForCondition(client, waitArgs.selector, condition, timeout, pollInterval);
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
catch (error) {
|
|
31
|
+
return {
|
|
32
|
+
success: false,
|
|
33
|
+
error: error instanceof Error ? error.message : String(error)
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
async waitForCondition(client, selector, condition, timeout, pollInterval) {
|
|
38
|
+
const startTime = Date.now();
|
|
39
|
+
let lastError = null;
|
|
40
|
+
while (Date.now() - startTime < timeout) {
|
|
41
|
+
try {
|
|
42
|
+
const checkResult = await this.checkCondition(client, selector, condition);
|
|
43
|
+
if (checkResult.success && checkResult.data) {
|
|
44
|
+
return {
|
|
45
|
+
success: true,
|
|
46
|
+
data: {
|
|
47
|
+
selector: selector,
|
|
48
|
+
condition: condition,
|
|
49
|
+
waitTime: Date.now() - startTime,
|
|
50
|
+
element: checkResult.data
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
lastError = checkResult.error || 'Condition not met';
|
|
55
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
56
|
+
}
|
|
57
|
+
catch (error) {
|
|
58
|
+
lastError = error instanceof Error ? error.message : String(error);
|
|
59
|
+
await new Promise(resolve => setTimeout(resolve, pollInterval));
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return {
|
|
63
|
+
success: false,
|
|
64
|
+
error: `Timeout after ${timeout}ms waiting for element "${selector}" to be ${condition}. Last error: ${lastError}`
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
async checkCondition(client, selector, condition) {
|
|
68
|
+
try {
|
|
69
|
+
const escapedSelector = selector.replace(/'/g, "\\'").replace(/"/g, '\\"');
|
|
70
|
+
let expression;
|
|
71
|
+
switch (condition) {
|
|
72
|
+
case 'exists':
|
|
73
|
+
expression = `
|
|
74
|
+
(function() {
|
|
75
|
+
const element = document.querySelector('${escapedSelector}');
|
|
76
|
+
if (element) {
|
|
77
|
+
return {
|
|
78
|
+
success: true,
|
|
79
|
+
tagName: element.tagName,
|
|
80
|
+
id: element.id,
|
|
81
|
+
className: element.className,
|
|
82
|
+
condition: 'exists'
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
return { success: false, error: 'Element does not exist' };
|
|
86
|
+
})()
|
|
87
|
+
`;
|
|
88
|
+
break;
|
|
89
|
+
case 'visible':
|
|
90
|
+
expression = `
|
|
91
|
+
(function() {
|
|
92
|
+
const element = document.querySelector('${escapedSelector}');
|
|
93
|
+
if (!element) {
|
|
94
|
+
return { success: false, error: 'Element does not exist' };
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Check if element is visible
|
|
98
|
+
const rect = element.getBoundingClientRect();
|
|
99
|
+
const style = window.getComputedStyle(element);
|
|
100
|
+
|
|
101
|
+
const isVisible = (
|
|
102
|
+
rect.width > 0 &&
|
|
103
|
+
rect.height > 0 &&
|
|
104
|
+
style.visibility !== 'hidden' &&
|
|
105
|
+
style.display !== 'none' &&
|
|
106
|
+
style.opacity !== '0'
|
|
107
|
+
);
|
|
108
|
+
|
|
109
|
+
if (isVisible) {
|
|
110
|
+
return {
|
|
111
|
+
success: true,
|
|
112
|
+
tagName: element.tagName,
|
|
113
|
+
id: element.id,
|
|
114
|
+
className: element.className,
|
|
115
|
+
condition: 'visible',
|
|
116
|
+
rect: { width: rect.width, height: rect.height, x: rect.x, y: rect.y }
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return { success: false, error: 'Element exists but is not visible' };
|
|
121
|
+
})()
|
|
122
|
+
`;
|
|
123
|
+
break;
|
|
124
|
+
case 'hidden':
|
|
125
|
+
expression = `
|
|
126
|
+
(function() {
|
|
127
|
+
const element = document.querySelector('${escapedSelector}');
|
|
128
|
+
if (!element) {
|
|
129
|
+
return {
|
|
130
|
+
success: true,
|
|
131
|
+
condition: 'hidden',
|
|
132
|
+
reason: 'Element does not exist (considered hidden)'
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// Check if element is hidden
|
|
137
|
+
const rect = element.getBoundingClientRect();
|
|
138
|
+
const style = window.getComputedStyle(element);
|
|
139
|
+
|
|
140
|
+
const isHidden = (
|
|
141
|
+
rect.width === 0 ||
|
|
142
|
+
rect.height === 0 ||
|
|
143
|
+
style.visibility === 'hidden' ||
|
|
144
|
+
style.display === 'none' ||
|
|
145
|
+
style.opacity === '0'
|
|
146
|
+
);
|
|
147
|
+
|
|
148
|
+
if (isHidden) {
|
|
149
|
+
return {
|
|
150
|
+
success: true,
|
|
151
|
+
tagName: element.tagName,
|
|
152
|
+
id: element.id,
|
|
153
|
+
className: element.className,
|
|
154
|
+
condition: 'hidden'
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
return { success: false, error: 'Element exists and is visible' };
|
|
159
|
+
})()
|
|
160
|
+
`;
|
|
161
|
+
break;
|
|
162
|
+
case 'enabled':
|
|
163
|
+
expression = `
|
|
164
|
+
(function() {
|
|
165
|
+
const element = document.querySelector('${escapedSelector}');
|
|
166
|
+
if (!element) {
|
|
167
|
+
return { success: false, error: 'Element does not exist' };
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
// Check if element is enabled (not disabled)
|
|
171
|
+
const isEnabled = !element.disabled && !element.hasAttribute('disabled');
|
|
172
|
+
|
|
173
|
+
if (isEnabled) {
|
|
174
|
+
return {
|
|
175
|
+
success: true,
|
|
176
|
+
tagName: element.tagName,
|
|
177
|
+
id: element.id,
|
|
178
|
+
className: element.className,
|
|
179
|
+
condition: 'enabled',
|
|
180
|
+
disabled: element.disabled
|
|
181
|
+
};
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return { success: false, error: 'Element exists but is disabled' };
|
|
185
|
+
})()
|
|
186
|
+
`;
|
|
187
|
+
break;
|
|
188
|
+
case 'disabled':
|
|
189
|
+
expression = `
|
|
190
|
+
(function() {
|
|
191
|
+
const element = document.querySelector('${escapedSelector}');
|
|
192
|
+
if (!element) {
|
|
193
|
+
return { success: false, error: 'Element does not exist' };
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Check if element is disabled
|
|
197
|
+
const isDisabled = element.disabled || element.hasAttribute('disabled');
|
|
198
|
+
|
|
199
|
+
if (isDisabled) {
|
|
200
|
+
return {
|
|
201
|
+
success: true,
|
|
202
|
+
tagName: element.tagName,
|
|
203
|
+
id: element.id,
|
|
204
|
+
className: element.className,
|
|
205
|
+
condition: 'disabled',
|
|
206
|
+
disabled: element.disabled
|
|
207
|
+
};
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return { success: false, error: 'Element exists but is enabled' };
|
|
211
|
+
})()
|
|
212
|
+
`;
|
|
213
|
+
break;
|
|
214
|
+
default:
|
|
215
|
+
return {
|
|
216
|
+
success: false,
|
|
217
|
+
error: `Unknown condition: ${condition}. Supported conditions: exists, visible, hidden, enabled, disabled`
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
const response = await client.send('Runtime.evaluate', {
|
|
221
|
+
expression: expression,
|
|
222
|
+
returnByValue: true
|
|
223
|
+
});
|
|
224
|
+
if (response.exceptionDetails) {
|
|
225
|
+
return {
|
|
226
|
+
success: false,
|
|
227
|
+
error: response.exceptionDetails.exception?.description || response.exceptionDetails.text
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
const result = response.result.value;
|
|
231
|
+
if (result.success) {
|
|
232
|
+
return {
|
|
233
|
+
success: true,
|
|
234
|
+
data: result
|
|
235
|
+
};
|
|
236
|
+
}
|
|
237
|
+
else {
|
|
238
|
+
return {
|
|
239
|
+
success: false,
|
|
240
|
+
error: result.error || 'Condition check failed'
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
catch (error) {
|
|
245
|
+
return {
|
|
246
|
+
success: false,
|
|
247
|
+
error: `Condition check failed: ${error instanceof Error ? error.message : String(error)}`
|
|
248
|
+
};
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
validateArgs(args) {
|
|
252
|
+
if (typeof args !== 'object' || args === null) {
|
|
253
|
+
return false;
|
|
254
|
+
}
|
|
255
|
+
const waitArgs = args;
|
|
256
|
+
if (!waitArgs.selector || typeof waitArgs.selector !== 'string') {
|
|
257
|
+
return false;
|
|
258
|
+
}
|
|
259
|
+
if (waitArgs.timeout !== undefined && typeof waitArgs.timeout !== 'number') {
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
if (waitArgs.visible !== undefined && typeof waitArgs.visible !== 'boolean') {
|
|
263
|
+
return false;
|
|
264
|
+
}
|
|
265
|
+
if (waitArgs.condition !== undefined &&
|
|
266
|
+
!['exists', 'visible', 'hidden', 'enabled', 'disabled'].includes(waitArgs.condition)) {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
if (waitArgs.pollInterval !== undefined && typeof waitArgs.pollInterval !== 'number') {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
getHelp() {
|
|
275
|
+
return `
|
|
276
|
+
wait_for - Wait for an element to appear or meet specific conditions
|
|
277
|
+
|
|
278
|
+
Usage:
|
|
279
|
+
wait_for <selector>
|
|
280
|
+
wait_for <selector> --condition visible
|
|
281
|
+
wait_for <selector> --timeout 15000
|
|
282
|
+
wait_for "#button" --condition enabled --timeout 5000
|
|
283
|
+
|
|
284
|
+
Arguments:
|
|
285
|
+
<selector> CSS selector for the element to wait for
|
|
286
|
+
|
|
287
|
+
Options:
|
|
288
|
+
--timeout <ms> Maximum time to wait (default: 10000ms)
|
|
289
|
+
--condition <type> Condition to wait for (default: exists)
|
|
290
|
+
--visible Wait for element to be visible (same as --condition visible)
|
|
291
|
+
--poll-interval <ms> Polling interval (default: 100ms)
|
|
292
|
+
|
|
293
|
+
Conditions:
|
|
294
|
+
exists Element exists in DOM (default)
|
|
295
|
+
visible Element exists and is visible (not hidden, has dimensions)
|
|
296
|
+
hidden Element is hidden or does not exist
|
|
297
|
+
enabled Element exists and is not disabled
|
|
298
|
+
disabled Element exists and is disabled
|
|
299
|
+
|
|
300
|
+
Examples:
|
|
301
|
+
# Wait for element to exist
|
|
302
|
+
wait_for "#loading-spinner"
|
|
303
|
+
|
|
304
|
+
# Wait for element to be visible
|
|
305
|
+
wait_for "#modal" --condition visible
|
|
306
|
+
|
|
307
|
+
# Wait for button to be enabled
|
|
308
|
+
wait_for "#submit-btn" --condition enabled
|
|
309
|
+
|
|
310
|
+
# Wait for element to be hidden
|
|
311
|
+
wait_for "#loading" --condition hidden
|
|
312
|
+
|
|
313
|
+
# Wait with custom timeout
|
|
314
|
+
wait_for ".slow-element" --timeout 30000
|
|
315
|
+
|
|
316
|
+
# Wait with custom polling interval
|
|
317
|
+
wait_for "#element" --poll-interval 500
|
|
318
|
+
|
|
319
|
+
# Legacy syntax (still supported)
|
|
320
|
+
wait_for "#modal" --visible
|
|
321
|
+
|
|
322
|
+
Note:
|
|
323
|
+
- Polls the condition at regular intervals until met or timeout
|
|
324
|
+
- Returns immediately when condition is satisfied
|
|
325
|
+
- For 'visible' condition, checks dimensions, display, visibility, and opacity
|
|
326
|
+
- For 'enabled/disabled' conditions, works with form elements
|
|
327
|
+
- Useful for waiting for dynamic content, AJAX responses, or animations
|
|
328
|
+
`;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
exports.WaitForHandler = WaitForHandler;
|
package/dist/handlers/index.js
CHANGED
|
@@ -23,3 +23,12 @@ __exportStar(require("./GetNetworkRequestHandler"), exports);
|
|
|
23
23
|
__exportStar(require("./ListNetworkRequestsHandler"), exports);
|
|
24
24
|
__exportStar(require("./InstallCursorCommandHandler"), exports);
|
|
25
25
|
__exportStar(require("./InstallClaudeSkillHandler"), exports);
|
|
26
|
+
__exportStar(require("./ClickHandler"), exports);
|
|
27
|
+
__exportStar(require("./HoverHandler"), exports);
|
|
28
|
+
__exportStar(require("./FillHandler"), exports);
|
|
29
|
+
__exportStar(require("./FillFormHandler"), exports);
|
|
30
|
+
__exportStar(require("./DragHandler"), exports);
|
|
31
|
+
__exportStar(require("./PressKeyHandler"), exports);
|
|
32
|
+
__exportStar(require("./UploadFileHandler"), exports);
|
|
33
|
+
__exportStar(require("./WaitForHandler"), exports);
|
|
34
|
+
__exportStar(require("./HandleDialogHandler"), exports);
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "chrome-cdp-cli",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "LLM-first browser automation CLI via Chrome DevTools Protocol.
|
|
3
|
+
"version": "1.5.0",
|
|
4
|
+
"description": "LLM-first browser automation CLI via Chrome DevTools Protocol. Eval-first design optimized for AI assistants - LLMs write JavaScript scripts for rapid validation. Features: JavaScript execution, 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",
|
|
7
7
|
"bin": {
|