chrome-cdp-cli 1.2.2 → 1.3.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.
@@ -0,0 +1,245 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FillFormHandler = void 0;
4
+ const FillHandler_1 = require("./FillHandler");
5
+ class FillFormHandler {
6
+ constructor() {
7
+ this.name = 'fill_form';
8
+ this.fillHandler = new FillHandler_1.FillHandler();
9
+ }
10
+ async execute(client, args) {
11
+ const fillFormArgs = args;
12
+ if (!fillFormArgs.fields) {
13
+ return {
14
+ success: false,
15
+ error: 'Fields array is required for fill_form command'
16
+ };
17
+ }
18
+ if (!Array.isArray(fillFormArgs.fields)) {
19
+ return {
20
+ success: false,
21
+ error: 'Fields must be an array'
22
+ };
23
+ }
24
+ if (fillFormArgs.fields.length === 0) {
25
+ return {
26
+ success: false,
27
+ error: 'At least one field is required'
28
+ };
29
+ }
30
+ for (let i = 0; i < fillFormArgs.fields.length; i++) {
31
+ const field = fillFormArgs.fields[i];
32
+ if (!field.selector || typeof field.selector !== 'string') {
33
+ return {
34
+ success: false,
35
+ error: `Field ${i}: selector is required and must be a string`
36
+ };
37
+ }
38
+ if (field.value === undefined || field.value === null || typeof field.value !== 'string') {
39
+ return {
40
+ success: false,
41
+ error: `Field ${i}: value is required and must be a string`
42
+ };
43
+ }
44
+ }
45
+ try {
46
+ await client.send('DOM.enable');
47
+ await client.send('Runtime.enable');
48
+ const timeout = fillFormArgs.timeout || 5000;
49
+ const waitForElements = fillFormArgs.waitForElements !== false;
50
+ const clearFirst = fillFormArgs.clearFirst !== false;
51
+ const continueOnError = fillFormArgs.continueOnError !== false;
52
+ const results = [];
53
+ let successCount = 0;
54
+ let errorCount = 0;
55
+ for (const field of fillFormArgs.fields) {
56
+ try {
57
+ const fillResult = await this.fillHandler.execute(client, {
58
+ selector: field.selector,
59
+ text: field.value,
60
+ waitForElement: waitForElements,
61
+ timeout: timeout,
62
+ clearFirst: clearFirst
63
+ });
64
+ const fieldResult = {
65
+ selector: field.selector,
66
+ value: field.value,
67
+ success: fillResult.success,
68
+ data: fillResult.data
69
+ };
70
+ if (fillResult.success) {
71
+ successCount++;
72
+ }
73
+ else {
74
+ errorCount++;
75
+ fieldResult.error = fillResult.error;
76
+ if (!continueOnError) {
77
+ results.push(fieldResult);
78
+ return {
79
+ success: false,
80
+ error: `Failed to fill field "${field.selector}": ${fillResult.error}`,
81
+ data: {
82
+ results: results,
83
+ summary: {
84
+ total: fillFormArgs.fields.length,
85
+ successful: successCount,
86
+ failed: errorCount,
87
+ processed: results.length
88
+ }
89
+ }
90
+ };
91
+ }
92
+ }
93
+ results.push(fieldResult);
94
+ }
95
+ catch (error) {
96
+ errorCount++;
97
+ const fieldResult = {
98
+ selector: field.selector,
99
+ value: field.value,
100
+ success: false,
101
+ error: error instanceof Error ? error.message : String(error)
102
+ };
103
+ results.push(fieldResult);
104
+ if (!continueOnError) {
105
+ return {
106
+ success: false,
107
+ error: `Failed to fill field "${field.selector}": ${fieldResult.error}`,
108
+ data: {
109
+ results: results,
110
+ summary: {
111
+ total: fillFormArgs.fields.length,
112
+ successful: successCount,
113
+ failed: errorCount,
114
+ processed: results.length
115
+ }
116
+ }
117
+ };
118
+ }
119
+ }
120
+ }
121
+ const overallSuccess = errorCount === 0;
122
+ return {
123
+ success: overallSuccess,
124
+ error: overallSuccess ? undefined : `${errorCount} out of ${fillFormArgs.fields.length} fields failed to fill`,
125
+ data: {
126
+ results: results,
127
+ summary: {
128
+ total: fillFormArgs.fields.length,
129
+ successful: successCount,
130
+ failed: errorCount,
131
+ processed: results.length
132
+ }
133
+ }
134
+ };
135
+ }
136
+ catch (error) {
137
+ return {
138
+ success: false,
139
+ error: error instanceof Error ? error.message : String(error)
140
+ };
141
+ }
142
+ }
143
+ validateArgs(args) {
144
+ if (typeof args !== 'object' || args === null) {
145
+ return false;
146
+ }
147
+ const fillFormArgs = args;
148
+ if (!fillFormArgs.fields || !Array.isArray(fillFormArgs.fields)) {
149
+ return false;
150
+ }
151
+ if (fillFormArgs.fields.length === 0) {
152
+ return false;
153
+ }
154
+ for (const field of fillFormArgs.fields) {
155
+ if (typeof field !== 'object' || field === null) {
156
+ return false;
157
+ }
158
+ if (!field.selector || typeof field.selector !== 'string') {
159
+ return false;
160
+ }
161
+ if (field.value === undefined || field.value === null || typeof field.value !== 'string') {
162
+ return false;
163
+ }
164
+ }
165
+ if (fillFormArgs.waitForElements !== undefined && typeof fillFormArgs.waitForElements !== 'boolean') {
166
+ return false;
167
+ }
168
+ if (fillFormArgs.timeout !== undefined && typeof fillFormArgs.timeout !== 'number') {
169
+ return false;
170
+ }
171
+ if (fillFormArgs.clearFirst !== undefined && typeof fillFormArgs.clearFirst !== 'boolean') {
172
+ return false;
173
+ }
174
+ if (fillFormArgs.continueOnError !== undefined && typeof fillFormArgs.continueOnError !== 'boolean') {
175
+ return false;
176
+ }
177
+ return true;
178
+ }
179
+ getHelp() {
180
+ return `
181
+ fill_form - Fill multiple form fields in batch
182
+
183
+ Usage:
184
+ fill_form --fields '[{"selector":"#username","value":"john"},{"selector":"#email","value":"john@example.com"}]'
185
+ fill_form --fields-file form-data.json
186
+ fill_form --fields '[{"selector":"#name","value":"John Doe"}]' --timeout 10000
187
+
188
+ Arguments:
189
+ --fields <json> JSON array of field objects with selector and value
190
+ --fields-file <file> JSON file containing array of field objects
191
+
192
+ Options:
193
+ --wait-for-elements Wait for elements to be available (default: true)
194
+ --no-wait Don't wait for elements
195
+ --timeout <ms> Timeout for waiting for elements (default: 5000ms)
196
+ --clear-first Clear fields before filling (default: true)
197
+ --no-clear Don't clear fields before filling
198
+ --continue-on-error Continue filling other fields if one fails (default: true)
199
+ --stop-on-error Stop filling if any field fails
200
+
201
+ Field Object Format:
202
+ {
203
+ "selector": "CSS selector for the form field",
204
+ "value": "Text value to fill into the field"
205
+ }
206
+
207
+ Examples:
208
+ # Fill login form
209
+ fill_form --fields '[
210
+ {"selector":"#username","value":"john@example.com"},
211
+ {"selector":"#password","value":"secret123"}
212
+ ]'
213
+
214
+ # Fill registration form from file
215
+ echo '[
216
+ {"selector":"#firstName","value":"John"},
217
+ {"selector":"#lastName","value":"Doe"},
218
+ {"selector":"#email","value":"john.doe@example.com"},
219
+ {"selector":"#country","value":"United States"}
220
+ ]' > form-data.json
221
+ fill_form --fields-file form-data.json
222
+
223
+ # Fill form with custom options
224
+ fill_form --fields '[
225
+ {"selector":"#notes","value":"Additional information"}
226
+ ]' --no-clear --timeout 10000 --stop-on-error
227
+
228
+ # Fill select dropdowns
229
+ fill_form --fields '[
230
+ {"selector":"#country","value":"US"},
231
+ {"selector":"#state","value":"California"},
232
+ {"selector":"#city","value":"San Francisco"}
233
+ ]'
234
+
235
+ Note:
236
+ - Uses the same filling logic as the 'fill' command for each field
237
+ - Works with input, textarea, and select elements
238
+ - For select elements, matches by option value first, then by text content
239
+ - Automatically triggers 'input' and 'change' events for each field
240
+ - Returns detailed results for each field including success/failure status
241
+ - By default continues filling other fields even if one fails
242
+ `;
243
+ }
244
+ }
245
+ exports.FillFormHandler = FillFormHandler;
@@ -0,0 +1,354 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.FillHandler = void 0;
4
+ class FillHandler {
5
+ constructor() {
6
+ this.name = 'fill';
7
+ }
8
+ async execute(client, args) {
9
+ const fillArgs = args;
10
+ if (!fillArgs.selector) {
11
+ return {
12
+ success: false,
13
+ error: 'CSS selector is required for fill command'
14
+ };
15
+ }
16
+ if (typeof fillArgs.selector !== 'string') {
17
+ return {
18
+ success: false,
19
+ error: 'CSS selector must be a string'
20
+ };
21
+ }
22
+ if (fillArgs.text === undefined || fillArgs.text === null) {
23
+ return {
24
+ success: false,
25
+ error: 'Text value is required for fill command'
26
+ };
27
+ }
28
+ if (typeof fillArgs.text !== 'string') {
29
+ return {
30
+ success: false,
31
+ error: 'Text value must be a string'
32
+ };
33
+ }
34
+ try {
35
+ await client.send('DOM.enable');
36
+ await client.send('Runtime.enable');
37
+ const timeout = fillArgs.timeout || 5000;
38
+ const waitForElement = fillArgs.waitForElement !== false;
39
+ if (waitForElement) {
40
+ const elementFound = await this.waitForElement(client, fillArgs.selector, timeout);
41
+ if (!elementFound) {
42
+ return {
43
+ success: false,
44
+ error: `Element with selector "${fillArgs.selector}" not found within ${timeout}ms`
45
+ };
46
+ }
47
+ }
48
+ const cdpResult = await this.fillViaCDP(client, fillArgs.selector, fillArgs.text, fillArgs.clearFirst);
49
+ if (cdpResult.success) {
50
+ return cdpResult;
51
+ }
52
+ const evalResult = await this.fillViaEval(client, fillArgs.selector, fillArgs.text, fillArgs.clearFirst);
53
+ return evalResult;
54
+ }
55
+ catch (error) {
56
+ return {
57
+ success: false,
58
+ error: error instanceof Error ? error.message : String(error)
59
+ };
60
+ }
61
+ }
62
+ async waitForElement(client, selector, timeout) {
63
+ const startTime = Date.now();
64
+ while (Date.now() - startTime < timeout) {
65
+ try {
66
+ const result = await client.send('Runtime.evaluate', {
67
+ expression: `document.querySelector('${selector.replace(/'/g, "\\'")}') !== null`,
68
+ returnByValue: true
69
+ });
70
+ if (result.result.value) {
71
+ return true;
72
+ }
73
+ await new Promise(resolve => setTimeout(resolve, 100));
74
+ }
75
+ catch (error) {
76
+ await new Promise(resolve => setTimeout(resolve, 100));
77
+ }
78
+ }
79
+ return false;
80
+ }
81
+ async fillViaCDP(client, selector, text, clearFirst = true) {
82
+ try {
83
+ const documentResponse = await client.send('DOM.getDocument');
84
+ const rootNodeId = documentResponse.root.nodeId;
85
+ const queryResponse = await client.send('DOM.querySelector', {
86
+ nodeId: rootNodeId,
87
+ selector: selector
88
+ });
89
+ if (!queryResponse.nodeId || queryResponse.nodeId === 0) {
90
+ return {
91
+ success: false,
92
+ error: `Element with selector "${selector}" not found`
93
+ };
94
+ }
95
+ const objectResponse = await client.send('DOM.resolveNode', {
96
+ nodeId: queryResponse.nodeId
97
+ });
98
+ if (!objectResponse.object?.objectId) {
99
+ return {
100
+ success: false,
101
+ error: 'Failed to resolve element to remote object'
102
+ };
103
+ }
104
+ const fillResponse = await client.send('Runtime.callFunctionOn', {
105
+ objectId: objectResponse.object.objectId,
106
+ functionDeclaration: `
107
+ function(text, clearFirst) {
108
+ // Check if element is a form field
109
+ const tagName = this.tagName.toLowerCase();
110
+ const inputType = this.type ? this.type.toLowerCase() : '';
111
+
112
+ if (!['input', 'textarea', 'select'].includes(tagName)) {
113
+ throw new Error('Element is not a form field (input, textarea, or select)');
114
+ }
115
+
116
+ // For select elements, try to select by value or text
117
+ if (tagName === 'select') {
118
+ // Try to find option by value first
119
+ let option = Array.from(this.options).find(opt => opt.value === text);
120
+
121
+ // If not found by value, try by text content
122
+ if (!option) {
123
+ option = Array.from(this.options).find(opt => opt.textContent.trim() === text);
124
+ }
125
+
126
+ if (option) {
127
+ this.value = option.value;
128
+ this.selectedIndex = option.index;
129
+ } else {
130
+ throw new Error('Option not found in select element: ' + text);
131
+ }
132
+ } else {
133
+ // For input and textarea elements
134
+ if (clearFirst) {
135
+ this.value = '';
136
+ }
137
+
138
+ // Set the value
139
+ this.value = text;
140
+
141
+ // Trigger input events to notify any listeners
142
+ this.dispatchEvent(new Event('input', { bubbles: true }));
143
+ this.dispatchEvent(new Event('change', { bubbles: true }));
144
+ }
145
+
146
+ // Focus the element
147
+ this.focus();
148
+
149
+ return {
150
+ success: true,
151
+ tagName: this.tagName,
152
+ id: this.id,
153
+ className: this.className,
154
+ value: this.value,
155
+ type: this.type || null
156
+ };
157
+ }
158
+ `,
159
+ arguments: [
160
+ { value: text },
161
+ { value: clearFirst }
162
+ ],
163
+ returnByValue: true,
164
+ userGesture: true
165
+ });
166
+ if (fillResponse.exceptionDetails) {
167
+ return {
168
+ success: false,
169
+ error: `Fill execution failed: ${fillResponse.exceptionDetails.exception?.description || fillResponse.exceptionDetails.text}`
170
+ };
171
+ }
172
+ return {
173
+ success: true,
174
+ data: {
175
+ selector: selector,
176
+ text: text,
177
+ element: fillResponse.result.value,
178
+ method: 'CDP'
179
+ }
180
+ };
181
+ }
182
+ catch (error) {
183
+ return {
184
+ success: false,
185
+ error: `CDP fill failed: ${error instanceof Error ? error.message : String(error)}`
186
+ };
187
+ }
188
+ }
189
+ async fillViaEval(client, selector, text, clearFirst = true) {
190
+ try {
191
+ const escapedSelector = selector.replace(/'/g, "\\'").replace(/"/g, '\\"');
192
+ const escapedText = text.replace(/'/g, "\\'").replace(/"/g, '\\"');
193
+ const expression = `
194
+ (function() {
195
+ const element = document.querySelector('${escapedSelector}');
196
+ if (!element) {
197
+ throw new Error('Element with selector "${escapedSelector}" not found');
198
+ }
199
+
200
+ // Check if element is a form field
201
+ const tagName = element.tagName.toLowerCase();
202
+ const inputType = element.type ? element.type.toLowerCase() : '';
203
+
204
+ if (!['input', 'textarea', 'select'].includes(tagName)) {
205
+ throw new Error('Element is not a form field (input, textarea, or select)');
206
+ }
207
+
208
+ const text = '${escapedText}';
209
+ const clearFirst = ${clearFirst};
210
+
211
+ // For select elements, try to select by value or text
212
+ if (tagName === 'select') {
213
+ // Try to find option by value first
214
+ let option = Array.from(element.options).find(opt => opt.value === text);
215
+
216
+ // If not found by value, try by text content
217
+ if (!option) {
218
+ option = Array.from(element.options).find(opt => opt.textContent.trim() === text);
219
+ }
220
+
221
+ if (option) {
222
+ element.value = option.value;
223
+ element.selectedIndex = option.index;
224
+ } else {
225
+ throw new Error('Option not found in select element: ' + text);
226
+ }
227
+ } else {
228
+ // For input and textarea elements
229
+ if (clearFirst) {
230
+ element.value = '';
231
+ }
232
+
233
+ // Set the value
234
+ element.value = text;
235
+
236
+ // Trigger input events to notify any listeners
237
+ element.dispatchEvent(new Event('input', { bubbles: true }));
238
+ element.dispatchEvent(new Event('change', { bubbles: true }));
239
+ }
240
+
241
+ // Focus the element
242
+ element.focus();
243
+
244
+ return {
245
+ success: true,
246
+ tagName: element.tagName,
247
+ id: element.id,
248
+ className: element.className,
249
+ value: element.value,
250
+ type: element.type || null
251
+ };
252
+ })()
253
+ `;
254
+ const response = await client.send('Runtime.evaluate', {
255
+ expression: expression,
256
+ returnByValue: true,
257
+ userGesture: true
258
+ });
259
+ if (response.exceptionDetails) {
260
+ return {
261
+ success: false,
262
+ error: response.exceptionDetails.exception?.description || response.exceptionDetails.text
263
+ };
264
+ }
265
+ return {
266
+ success: true,
267
+ data: {
268
+ selector: selector,
269
+ text: text,
270
+ element: response.result.value,
271
+ method: 'eval'
272
+ }
273
+ };
274
+ }
275
+ catch (error) {
276
+ return {
277
+ success: false,
278
+ error: `Eval fill failed: ${error instanceof Error ? error.message : String(error)}`
279
+ };
280
+ }
281
+ }
282
+ validateArgs(args) {
283
+ if (typeof args !== 'object' || args === null) {
284
+ return false;
285
+ }
286
+ const fillArgs = args;
287
+ if (!fillArgs.selector || typeof fillArgs.selector !== 'string') {
288
+ return false;
289
+ }
290
+ if (fillArgs.text === undefined || fillArgs.text === null || typeof fillArgs.text !== 'string') {
291
+ return false;
292
+ }
293
+ if (fillArgs.waitForElement !== undefined && typeof fillArgs.waitForElement !== 'boolean') {
294
+ return false;
295
+ }
296
+ if (fillArgs.timeout !== undefined && typeof fillArgs.timeout !== 'number') {
297
+ return false;
298
+ }
299
+ if (fillArgs.clearFirst !== undefined && typeof fillArgs.clearFirst !== 'boolean') {
300
+ return false;
301
+ }
302
+ return true;
303
+ }
304
+ getHelp() {
305
+ return `
306
+ fill - Fill a form field with text using CSS selector
307
+
308
+ Usage:
309
+ fill <selector> <text>
310
+ fill "#username" "john@example.com"
311
+ fill "input[name='password']" "secret123" --timeout 10000
312
+ fill "#country" "United States" --no-clear
313
+
314
+ Arguments:
315
+ <selector> CSS selector for the form field to fill
316
+ <text> Text to input into the field
317
+
318
+ Options:
319
+ --wait-for-element Wait for element to be available (default: true)
320
+ --no-wait Don't wait for element (same as --wait-for-element=false)
321
+ --timeout <ms> Timeout for waiting for element (default: 5000ms)
322
+ --clear-first Clear field before filling (default: true)
323
+ --no-clear Don't clear field before filling
324
+
325
+ Examples:
326
+ # Fill a text input by ID
327
+ fill "#username" "john@example.com"
328
+
329
+ # Fill a password field
330
+ fill "input[type='password']" "secret123"
331
+
332
+ # Fill a textarea
333
+ fill "#message" "Hello, this is a test message"
334
+
335
+ # Select an option in a dropdown (by value or text)
336
+ fill "#country" "US"
337
+ fill "#country" "United States"
338
+
339
+ # Fill without clearing existing content
340
+ fill "#notes" " - Additional note" --no-clear
341
+
342
+ # Fill with custom timeout
343
+ fill ".slow-loading-field" "value" --timeout 10000
344
+
345
+ Note:
346
+ - Works with input, textarea, and select elements
347
+ - For select elements, matches by option value first, then by text content
348
+ - Automatically triggers 'input' and 'change' events
349
+ - Uses CDP DOM.querySelector and Runtime.callFunctionOn for precise control
350
+ - Falls back to JavaScript eval if CDP approach fails
351
+ `;
352
+ }
353
+ }
354
+ exports.FillHandler = FillHandler;