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.
- package/README.md +430 -45
- package/dist/cli/CLIApplication.js +9 -0
- package/dist/cli/CommandRouter.js +12 -1
- 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 +706 -58
- package/dist/handlers/InstallCursorCommandHandler.js +208 -74
- package/dist/handlers/PressKeyHandler.js +337 -0
- package/dist/handlers/TakeSnapshotHandler.js +104 -32
- 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,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;
|