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
|
@@ -63,7 +63,7 @@ Examples:
|
|
|
63
63
|
};
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
-
await this.
|
|
66
|
+
await this.ensureDirectoryPath(targetDir);
|
|
67
67
|
const commands = this.generateCursorCommands();
|
|
68
68
|
const createdFiles = [];
|
|
69
69
|
for (const command of commands) {
|
|
@@ -109,89 +109,175 @@ Examples:
|
|
|
109
109
|
logger_1.logger.info(`Created directory: ${dirPath}`);
|
|
110
110
|
}
|
|
111
111
|
}
|
|
112
|
+
async ensureDirectoryPath(targetPath) {
|
|
113
|
+
if (targetPath.includes('.cursor/commands') || targetPath.endsWith('.cursor/commands')) {
|
|
114
|
+
const cursorDir = targetPath.includes('.cursor/commands')
|
|
115
|
+
? targetPath.substring(0, targetPath.indexOf('.cursor') + 7)
|
|
116
|
+
: path.dirname(targetPath);
|
|
117
|
+
await this.ensureDirectory(cursorDir);
|
|
118
|
+
const commandsDir = path.join(cursorDir, 'commands');
|
|
119
|
+
await this.ensureDirectory(commandsDir);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
await this.ensureDirectory(targetPath);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
112
125
|
generateCursorCommands() {
|
|
113
126
|
return [
|
|
114
127
|
{
|
|
115
128
|
name: 'cdp-cli',
|
|
116
129
|
description: 'Chrome DevTools Protocol CLI 工具',
|
|
117
|
-
instructions: `通过 Chrome DevTools Protocol 控制 Chrome
|
|
118
|
-
|
|
119
|
-
##
|
|
120
|
-
|
|
121
|
-
### 1. JavaScript 执行
|
|
122
|
-
执行 JavaScript 代码并返回结果,支持异步代码和 Promise
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
-
|
|
128
|
-
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
-
|
|
141
|
-
|
|
142
|
-
-
|
|
143
|
-
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
-
|
|
155
|
-
-
|
|
156
|
-
|
|
157
|
-
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
-
|
|
170
|
-
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
-
|
|
183
|
-
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
130
|
+
instructions: `通过 Chrome DevTools Protocol 控制 Chrome 浏览器,支持完整的自动化操作。
|
|
131
|
+
|
|
132
|
+
## 完整命令列表
|
|
133
|
+
|
|
134
|
+
### 1. JavaScript 执行
|
|
135
|
+
- **eval** - 执行 JavaScript 代码并返回结果,支持异步代码和 Promise
|
|
136
|
+
\`chrome-cdp-cli eval "document.title"\`
|
|
137
|
+
\`chrome-cdp-cli eval "fetch('/api/data').then(r => r.json())"\`
|
|
138
|
+
|
|
139
|
+
### 2. 页面截图和快照
|
|
140
|
+
- **screenshot** - 捕获页面截图并保存到文件
|
|
141
|
+
\`chrome-cdp-cli screenshot --filename page.png\`
|
|
142
|
+
\`chrome-cdp-cli screenshot --filename fullpage.png --full-page\`
|
|
143
|
+
|
|
144
|
+
- **snapshot** - 捕获完整 DOM 快照(包含结构、样式、布局)
|
|
145
|
+
\`chrome-cdp-cli snapshot --filename dom-snapshot.json\`
|
|
146
|
+
|
|
147
|
+
### 3. 元素交互
|
|
148
|
+
- **click** - 点击页面元素
|
|
149
|
+
\`chrome-cdp-cli click "#submit-button"\`
|
|
150
|
+
\`chrome-cdp-cli click ".menu-item" --timeout 10000\`
|
|
151
|
+
|
|
152
|
+
- **hover** - 鼠标悬停在元素上
|
|
153
|
+
\`chrome-cdp-cli hover "#dropdown-trigger"\`
|
|
154
|
+
|
|
155
|
+
- **fill** - 填充表单字段
|
|
156
|
+
\`chrome-cdp-cli fill "#username" "john@example.com"\`
|
|
157
|
+
\`chrome-cdp-cli fill "input[name='password']" "secret123"\`
|
|
158
|
+
|
|
159
|
+
- **fill_form** - 批量填充表单
|
|
160
|
+
\`chrome-cdp-cli fill_form '{"#username": "john", "#password": "secret"}'\`
|
|
161
|
+
|
|
162
|
+
### 4. 高级交互
|
|
163
|
+
- **drag** - 拖拽操作
|
|
164
|
+
\`chrome-cdp-cli drag "#draggable" "#dropzone"\`
|
|
165
|
+
|
|
166
|
+
- **press_key** - 模拟键盘输入
|
|
167
|
+
\`chrome-cdp-cli press_key "Enter"\`
|
|
168
|
+
\`chrome-cdp-cli press_key "a" --modifiers Ctrl --selector "#input"\`
|
|
169
|
+
|
|
170
|
+
- **upload_file** - 文件上传
|
|
171
|
+
\`chrome-cdp-cli upload_file "input[type='file']" "./document.pdf"\`
|
|
172
|
+
|
|
173
|
+
- **wait_for** - 等待元素出现或满足条件
|
|
174
|
+
\`chrome-cdp-cli wait_for "#loading" --condition hidden\`
|
|
175
|
+
\`chrome-cdp-cli wait_for "#submit-btn" --condition enabled\`
|
|
176
|
+
|
|
177
|
+
- **handle_dialog** - 处理浏览器对话框
|
|
178
|
+
\`chrome-cdp-cli handle_dialog accept\`
|
|
179
|
+
\`chrome-cdp-cli handle_dialog accept --text "user input"\`
|
|
180
|
+
|
|
181
|
+
### 5. 监控功能
|
|
182
|
+
- **get_console_message** - 获取最新控制台消息
|
|
183
|
+
\`chrome-cdp-cli get_console_message\`
|
|
184
|
+
|
|
185
|
+
- **list_console_messages** - 列出所有控制台消息
|
|
186
|
+
\`chrome-cdp-cli list_console_messages --type error\`
|
|
187
|
+
|
|
188
|
+
- **get_network_request** - 获取最新网络请求
|
|
189
|
+
\`chrome-cdp-cli get_network_request\`
|
|
190
|
+
|
|
191
|
+
- **list_network_requests** - 列出所有网络请求
|
|
192
|
+
\`chrome-cdp-cli list_network_requests --method POST\`
|
|
193
|
+
|
|
194
|
+
### 6. IDE 集成
|
|
195
|
+
- **install_cursor_command** - 安装 Cursor 命令
|
|
196
|
+
\`chrome-cdp-cli install_cursor_command\`
|
|
197
|
+
|
|
198
|
+
- **install_claude_skill** - 安装 Claude 技能
|
|
199
|
+
\`chrome-cdp-cli install_claude_skill --skill-type personal\`
|
|
200
|
+
|
|
201
|
+
## 常用工作流程
|
|
202
|
+
|
|
203
|
+
### 完整的表单测试流程
|
|
204
|
+
\`\`\`bash
|
|
205
|
+
# 1. 等待页面加载
|
|
206
|
+
chrome-cdp-cli wait_for "#login-form" --condition visible
|
|
207
|
+
|
|
208
|
+
# 2. 填写表单
|
|
209
|
+
chrome-cdp-cli fill "#email" "test@example.com"
|
|
210
|
+
chrome-cdp-cli fill "#password" "password123"
|
|
211
|
+
|
|
212
|
+
# 3. 提交表单
|
|
213
|
+
chrome-cdp-cli click "#submit-button"
|
|
214
|
+
|
|
215
|
+
# 4. 等待结果并截图
|
|
216
|
+
chrome-cdp-cli wait_for "#success-message" --condition visible
|
|
217
|
+
chrome-cdp-cli screenshot --filename login-success.png
|
|
218
|
+
|
|
219
|
+
# 5. 检查控制台错误
|
|
220
|
+
chrome-cdp-cli list_console_messages --type error
|
|
221
|
+
\`\`\`
|
|
222
|
+
|
|
223
|
+
### 文件上传测试
|
|
224
|
+
\`\`\`bash
|
|
225
|
+
# 1. 点击上传按钮
|
|
226
|
+
chrome-cdp-cli click "#upload-trigger"
|
|
227
|
+
|
|
228
|
+
# 2. 上传文件
|
|
229
|
+
chrome-cdp-cli upload_file "input[type='file']" "./test-document.pdf"
|
|
230
|
+
|
|
231
|
+
# 3. 等待上传完成
|
|
232
|
+
chrome-cdp-cli wait_for ".upload-success" --condition visible
|
|
233
|
+
|
|
234
|
+
# 4. 验证结果
|
|
235
|
+
chrome-cdp-cli eval "document.querySelector('.file-name').textContent"
|
|
236
|
+
\`\`\`
|
|
237
|
+
|
|
238
|
+
### 拖拽交互测试
|
|
239
|
+
\`\`\`bash
|
|
240
|
+
# 1. 等待元素可用
|
|
241
|
+
chrome-cdp-cli wait_for "#draggable-item" --condition visible
|
|
242
|
+
chrome-cdp-cli wait_for "#drop-zone" --condition visible
|
|
243
|
+
|
|
244
|
+
# 2. 执行拖拽
|
|
245
|
+
chrome-cdp-cli drag "#draggable-item" "#drop-zone"
|
|
246
|
+
|
|
247
|
+
# 3. 验证拖拽结果
|
|
248
|
+
chrome-cdp-cli eval "document.querySelector('#drop-zone').children.length"
|
|
249
|
+
\`\`\`
|
|
250
|
+
|
|
251
|
+
### 键盘导航测试
|
|
252
|
+
\`\`\`bash
|
|
253
|
+
# 1. 聚焦到输入框
|
|
254
|
+
chrome-cdp-cli click "#search-input"
|
|
255
|
+
|
|
256
|
+
# 2. 输入文本
|
|
257
|
+
chrome-cdp-cli press_key "t"
|
|
258
|
+
chrome-cdp-cli press_key "e"
|
|
259
|
+
chrome-cdp-cli press_key "s"
|
|
260
|
+
chrome-cdp-cli press_key "t"
|
|
261
|
+
|
|
262
|
+
# 3. 使用快捷键
|
|
263
|
+
chrome-cdp-cli press_key "a" --modifiers Ctrl # 全选
|
|
264
|
+
chrome-cdp-cli press_key "Enter" # 提交
|
|
265
|
+
|
|
266
|
+
# 4. 处理可能的确认对话框
|
|
267
|
+
chrome-cdp-cli handle_dialog accept
|
|
268
|
+
\`\`\`
|
|
189
269
|
|
|
190
270
|
命令会自动连接到运行在 localhost:9222 的 Chrome 实例。`,
|
|
191
271
|
examples: [
|
|
192
272
|
'chrome-cdp-cli eval "document.title"',
|
|
193
273
|
'chrome-cdp-cli screenshot --filename page.png',
|
|
194
|
-
'chrome-cdp-cli
|
|
274
|
+
'chrome-cdp-cli click "#submit-button"',
|
|
275
|
+
'chrome-cdp-cli fill "#username" "test@example.com"',
|
|
276
|
+
'chrome-cdp-cli drag "#item" "#target"',
|
|
277
|
+
'chrome-cdp-cli press_key "Enter"',
|
|
278
|
+
'chrome-cdp-cli upload_file "input[type=file]" "./doc.pdf"',
|
|
279
|
+
'chrome-cdp-cli wait_for "#loading" --condition hidden',
|
|
280
|
+
'chrome-cdp-cli handle_dialog accept',
|
|
195
281
|
'chrome-cdp-cli get_console_message',
|
|
196
282
|
'chrome-cdp-cli list_network_requests'
|
|
197
283
|
]
|
|
@@ -274,6 +360,54 @@ chrome-cdp-cli snapshot --filename page-analysis.json
|
|
|
274
360
|
# 3. 检查控制台错误
|
|
275
361
|
chrome-cdp-cli list_console_messages --type error
|
|
276
362
|
\`\`\`
|
|
363
|
+
`;
|
|
364
|
+
}
|
|
365
|
+
getHelp() {
|
|
366
|
+
return `
|
|
367
|
+
install-cursor-command - Install Cursor IDE commands for Chrome browser automation
|
|
368
|
+
|
|
369
|
+
Usage:
|
|
370
|
+
install-cursor-command
|
|
371
|
+
install-cursor-command --target-directory /path/to/.cursor/commands
|
|
372
|
+
install-cursor-command --force
|
|
373
|
+
|
|
374
|
+
Arguments:
|
|
375
|
+
--target-directory <path> Custom installation directory (default: .cursor/commands)
|
|
376
|
+
--include-examples Include example usage (default: true)
|
|
377
|
+
--force Force installation without directory validation
|
|
378
|
+
|
|
379
|
+
Description:
|
|
380
|
+
Installs a unified Cursor command file (cdp-cli.md) that provides Chrome browser
|
|
381
|
+
automation capabilities directly within Cursor IDE. The command includes:
|
|
382
|
+
|
|
383
|
+
• JavaScript execution in browser context
|
|
384
|
+
• Screenshot capture and DOM snapshots
|
|
385
|
+
• Console and network monitoring
|
|
386
|
+
• Complete automation workflows
|
|
387
|
+
• Comprehensive examples and documentation
|
|
388
|
+
|
|
389
|
+
Directory Validation:
|
|
390
|
+
By default, the command checks for a .cursor directory to ensure you're in a
|
|
391
|
+
Cursor project root. Use --force to bypass this validation or --target-directory
|
|
392
|
+
to specify a custom location.
|
|
393
|
+
|
|
394
|
+
Examples:
|
|
395
|
+
# Install in current project (requires .cursor directory)
|
|
396
|
+
install-cursor-command
|
|
397
|
+
|
|
398
|
+
# Install with custom directory
|
|
399
|
+
install-cursor-command --target-directory /path/to/.cursor/commands
|
|
400
|
+
|
|
401
|
+
# Force install without validation
|
|
402
|
+
install-cursor-command --force
|
|
403
|
+
|
|
404
|
+
# After installation, use in Cursor with:
|
|
405
|
+
/cdp-cli
|
|
406
|
+
|
|
407
|
+
Note:
|
|
408
|
+
The installed command provides powerful browser automation through the eval
|
|
409
|
+
approach, which is ideal for LLM-assisted development as it allows writing
|
|
410
|
+
and testing JavaScript automation scripts quickly and flexibly.
|
|
277
411
|
`;
|
|
278
412
|
}
|
|
279
413
|
}
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PressKeyHandler = void 0;
|
|
4
|
+
class PressKeyHandler {
|
|
5
|
+
constructor() {
|
|
6
|
+
this.name = 'press_key';
|
|
7
|
+
}
|
|
8
|
+
async execute(client, args) {
|
|
9
|
+
const keyArgs = args;
|
|
10
|
+
if (!keyArgs.key) {
|
|
11
|
+
return {
|
|
12
|
+
success: false,
|
|
13
|
+
error: 'Key is required for press_key command'
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
if (typeof keyArgs.key !== 'string') {
|
|
17
|
+
return {
|
|
18
|
+
success: false,
|
|
19
|
+
error: 'Key must be a string'
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
await client.send('Runtime.enable');
|
|
24
|
+
const timeout = keyArgs.timeout || 5000;
|
|
25
|
+
const waitForElement = keyArgs.waitForElement !== false;
|
|
26
|
+
if (keyArgs.selector) {
|
|
27
|
+
if (waitForElement) {
|
|
28
|
+
const elementFound = await this.waitForElement(client, keyArgs.selector, timeout);
|
|
29
|
+
if (!elementFound) {
|
|
30
|
+
return {
|
|
31
|
+
success: false,
|
|
32
|
+
error: `Element with selector "${keyArgs.selector}" not found within ${timeout}ms`
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
const focusResult = await this.focusElement(client, keyArgs.selector);
|
|
37
|
+
if (!focusResult.success) {
|
|
38
|
+
return focusResult;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const result = await this.pressKeyViaEval(client, keyArgs.key, keyArgs.modifiers || [], keyArgs.selector);
|
|
42
|
+
return result;
|
|
43
|
+
}
|
|
44
|
+
catch (error) {
|
|
45
|
+
return {
|
|
46
|
+
success: false,
|
|
47
|
+
error: error instanceof Error ? error.message : String(error)
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
async waitForElement(client, selector, timeout) {
|
|
52
|
+
const startTime = Date.now();
|
|
53
|
+
while (Date.now() - startTime < timeout) {
|
|
54
|
+
try {
|
|
55
|
+
const result = await client.send('Runtime.evaluate', {
|
|
56
|
+
expression: `document.querySelector('${selector.replace(/'/g, "\\'")}') !== null`,
|
|
57
|
+
returnByValue: true
|
|
58
|
+
});
|
|
59
|
+
if (result.result.value) {
|
|
60
|
+
return true;
|
|
61
|
+
}
|
|
62
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
async focusElement(client, selector) {
|
|
71
|
+
try {
|
|
72
|
+
const escapedSelector = selector.replace(/'/g, "\\'").replace(/"/g, '\\"');
|
|
73
|
+
const expression = `
|
|
74
|
+
(function() {
|
|
75
|
+
const element = document.querySelector('${escapedSelector}');
|
|
76
|
+
if (!element) {
|
|
77
|
+
throw new Error('Element with selector "${escapedSelector}" not found');
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
element.focus();
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
success: true,
|
|
84
|
+
tagName: element.tagName,
|
|
85
|
+
id: element.id,
|
|
86
|
+
className: element.className
|
|
87
|
+
};
|
|
88
|
+
})()
|
|
89
|
+
`;
|
|
90
|
+
const response = await client.send('Runtime.evaluate', {
|
|
91
|
+
expression: expression,
|
|
92
|
+
returnByValue: true,
|
|
93
|
+
userGesture: true
|
|
94
|
+
});
|
|
95
|
+
if (response.exceptionDetails) {
|
|
96
|
+
return {
|
|
97
|
+
success: false,
|
|
98
|
+
error: response.exceptionDetails.exception?.description || response.exceptionDetails.text
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
return {
|
|
102
|
+
success: true,
|
|
103
|
+
data: response.result.value
|
|
104
|
+
};
|
|
105
|
+
}
|
|
106
|
+
catch (error) {
|
|
107
|
+
return {
|
|
108
|
+
success: false,
|
|
109
|
+
error: `Focus failed: ${error instanceof Error ? error.message : String(error)}`
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
async pressKeyViaEval(client, key, modifiers, selector) {
|
|
114
|
+
try {
|
|
115
|
+
const escapedKey = key.replace(/'/g, "\\'").replace(/"/g, '\\"');
|
|
116
|
+
const modifierFlags = {
|
|
117
|
+
ctrlKey: modifiers.includes('Ctrl') || modifiers.includes('Control'),
|
|
118
|
+
altKey: modifiers.includes('Alt'),
|
|
119
|
+
shiftKey: modifiers.includes('Shift'),
|
|
120
|
+
metaKey: modifiers.includes('Meta') || modifiers.includes('Cmd')
|
|
121
|
+
};
|
|
122
|
+
const expression = `
|
|
123
|
+
(function() {
|
|
124
|
+
const key = '${escapedKey}';
|
|
125
|
+
const modifiers = ${JSON.stringify(modifierFlags)};
|
|
126
|
+
|
|
127
|
+
// Determine target element
|
|
128
|
+
let targetElement = document.activeElement;
|
|
129
|
+
${selector ? `
|
|
130
|
+
const specifiedElement = document.querySelector('${selector.replace(/'/g, "\\'")}');
|
|
131
|
+
if (specifiedElement) {
|
|
132
|
+
targetElement = specifiedElement;
|
|
133
|
+
}
|
|
134
|
+
` : ''}
|
|
135
|
+
|
|
136
|
+
if (!targetElement) {
|
|
137
|
+
targetElement = document.body;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Create keyboard event options
|
|
141
|
+
const eventOptions = {
|
|
142
|
+
key: key,
|
|
143
|
+
code: key.length === 1 ? 'Key' + key.toUpperCase() : key,
|
|
144
|
+
bubbles: true,
|
|
145
|
+
cancelable: true,
|
|
146
|
+
view: window,
|
|
147
|
+
ctrlKey: modifiers.ctrlKey,
|
|
148
|
+
altKey: modifiers.altKey,
|
|
149
|
+
shiftKey: modifiers.shiftKey,
|
|
150
|
+
metaKey: modifiers.metaKey
|
|
151
|
+
};
|
|
152
|
+
|
|
153
|
+
// Handle special keys
|
|
154
|
+
const specialKeys = {
|
|
155
|
+
'Enter': { code: 'Enter', keyCode: 13 },
|
|
156
|
+
'Escape': { code: 'Escape', keyCode: 27 },
|
|
157
|
+
'Tab': { code: 'Tab', keyCode: 9 },
|
|
158
|
+
'Backspace': { code: 'Backspace', keyCode: 8 },
|
|
159
|
+
'Delete': { code: 'Delete', keyCode: 46 },
|
|
160
|
+
'ArrowUp': { code: 'ArrowUp', keyCode: 38 },
|
|
161
|
+
'ArrowDown': { code: 'ArrowDown', keyCode: 40 },
|
|
162
|
+
'ArrowLeft': { code: 'ArrowLeft', keyCode: 37 },
|
|
163
|
+
'ArrowRight': { code: 'ArrowRight', keyCode: 39 },
|
|
164
|
+
'Home': { code: 'Home', keyCode: 36 },
|
|
165
|
+
'End': { code: 'End', keyCode: 35 },
|
|
166
|
+
'PageUp': { code: 'PageUp', keyCode: 33 },
|
|
167
|
+
'PageDown': { code: 'PageDown', keyCode: 34 },
|
|
168
|
+
'Space': { code: 'Space', keyCode: 32, key: ' ' }
|
|
169
|
+
};
|
|
170
|
+
|
|
171
|
+
if (specialKeys[key]) {
|
|
172
|
+
eventOptions.code = specialKeys[key].code;
|
|
173
|
+
eventOptions.keyCode = specialKeys[key].keyCode;
|
|
174
|
+
if (specialKeys[key].key) {
|
|
175
|
+
eventOptions.key = specialKeys[key].key;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Create and dispatch keydown event
|
|
180
|
+
const keydownEvent = new KeyboardEvent('keydown', eventOptions);
|
|
181
|
+
targetElement.dispatchEvent(keydownEvent);
|
|
182
|
+
|
|
183
|
+
// Create and dispatch keypress event (for printable characters)
|
|
184
|
+
if (key.length === 1 || key === 'Enter' || key === 'Space') {
|
|
185
|
+
const keypressEvent = new KeyboardEvent('keypress', eventOptions);
|
|
186
|
+
targetElement.dispatchEvent(keypressEvent);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Create and dispatch keyup event
|
|
190
|
+
const keyupEvent = new KeyboardEvent('keyup', eventOptions);
|
|
191
|
+
targetElement.dispatchEvent(keyupEvent);
|
|
192
|
+
|
|
193
|
+
// For input elements, also trigger input event if it's a printable character
|
|
194
|
+
if ((targetElement.tagName === 'INPUT' || targetElement.tagName === 'TEXTAREA') &&
|
|
195
|
+
key.length === 1 && !modifiers.ctrlKey && !modifiers.altKey && !modifiers.metaKey) {
|
|
196
|
+
// Add character to input value
|
|
197
|
+
const currentValue = targetElement.value || '';
|
|
198
|
+
const cursorPos = targetElement.selectionStart || currentValue.length;
|
|
199
|
+
const newValue = currentValue.slice(0, cursorPos) + key + currentValue.slice(cursorPos);
|
|
200
|
+
targetElement.value = newValue;
|
|
201
|
+
targetElement.selectionStart = targetElement.selectionEnd = cursorPos + 1;
|
|
202
|
+
|
|
203
|
+
// Trigger input and change events
|
|
204
|
+
targetElement.dispatchEvent(new Event('input', { bubbles: true }));
|
|
205
|
+
targetElement.dispatchEvent(new Event('change', { bubbles: true }));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return {
|
|
209
|
+
success: true,
|
|
210
|
+
key: key,
|
|
211
|
+
modifiers: modifiers,
|
|
212
|
+
target: {
|
|
213
|
+
tagName: targetElement.tagName,
|
|
214
|
+
id: targetElement.id,
|
|
215
|
+
className: targetElement.className,
|
|
216
|
+
value: targetElement.value || null
|
|
217
|
+
}
|
|
218
|
+
};
|
|
219
|
+
})()
|
|
220
|
+
`;
|
|
221
|
+
const response = await client.send('Runtime.evaluate', {
|
|
222
|
+
expression: expression,
|
|
223
|
+
returnByValue: true,
|
|
224
|
+
userGesture: true
|
|
225
|
+
});
|
|
226
|
+
if (response.exceptionDetails) {
|
|
227
|
+
return {
|
|
228
|
+
success: false,
|
|
229
|
+
error: response.exceptionDetails.exception?.description || response.exceptionDetails.text
|
|
230
|
+
};
|
|
231
|
+
}
|
|
232
|
+
return {
|
|
233
|
+
success: true,
|
|
234
|
+
data: {
|
|
235
|
+
key: key,
|
|
236
|
+
modifiers: modifiers,
|
|
237
|
+
selector: selector,
|
|
238
|
+
result: response.result.value
|
|
239
|
+
}
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
return {
|
|
244
|
+
success: false,
|
|
245
|
+
error: `Key press failed: ${error instanceof Error ? error.message : String(error)}`
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
validateArgs(args) {
|
|
250
|
+
if (typeof args !== 'object' || args === null) {
|
|
251
|
+
return false;
|
|
252
|
+
}
|
|
253
|
+
const keyArgs = args;
|
|
254
|
+
if (!keyArgs.key || typeof keyArgs.key !== 'string') {
|
|
255
|
+
return false;
|
|
256
|
+
}
|
|
257
|
+
if (keyArgs.selector !== undefined && typeof keyArgs.selector !== 'string') {
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
if (keyArgs.modifiers !== undefined && !Array.isArray(keyArgs.modifiers)) {
|
|
261
|
+
return false;
|
|
262
|
+
}
|
|
263
|
+
if (keyArgs.modifiers && !keyArgs.modifiers.every(mod => typeof mod === 'string')) {
|
|
264
|
+
return false;
|
|
265
|
+
}
|
|
266
|
+
if (keyArgs.waitForElement !== undefined && typeof keyArgs.waitForElement !== 'boolean') {
|
|
267
|
+
return false;
|
|
268
|
+
}
|
|
269
|
+
if (keyArgs.timeout !== undefined && typeof keyArgs.timeout !== 'number') {
|
|
270
|
+
return false;
|
|
271
|
+
}
|
|
272
|
+
return true;
|
|
273
|
+
}
|
|
274
|
+
getHelp() {
|
|
275
|
+
return `
|
|
276
|
+
press_key - Simulate keyboard input
|
|
277
|
+
|
|
278
|
+
Usage:
|
|
279
|
+
press_key <key>
|
|
280
|
+
press_key <key> --selector <selector>
|
|
281
|
+
press_key <key> --modifiers Ctrl,Shift
|
|
282
|
+
press_key "Enter" --selector "#input-field"
|
|
283
|
+
|
|
284
|
+
Arguments:
|
|
285
|
+
<key> Key to press (e.g., 'Enter', 'Escape', 'a', 'ArrowDown')
|
|
286
|
+
|
|
287
|
+
Options:
|
|
288
|
+
--selector <selector> CSS selector to focus element first
|
|
289
|
+
--modifiers <list> Comma-separated modifiers: Ctrl, Alt, Shift, Meta
|
|
290
|
+
--wait-for-element Wait for element to be available if selector provided (default: true)
|
|
291
|
+
--no-wait Don't wait for element
|
|
292
|
+
--timeout <ms> Timeout for waiting for element (default: 5000ms)
|
|
293
|
+
|
|
294
|
+
Examples:
|
|
295
|
+
# Press Enter key
|
|
296
|
+
press_key "Enter"
|
|
297
|
+
|
|
298
|
+
# Press Enter in a specific input field
|
|
299
|
+
press_key "Enter" --selector "#search-input"
|
|
300
|
+
|
|
301
|
+
# Press Ctrl+A to select all
|
|
302
|
+
press_key "a" --modifiers Ctrl
|
|
303
|
+
|
|
304
|
+
# Press Escape key
|
|
305
|
+
press_key "Escape"
|
|
306
|
+
|
|
307
|
+
# Press arrow keys for navigation
|
|
308
|
+
press_key "ArrowDown"
|
|
309
|
+
press_key "ArrowUp"
|
|
310
|
+
|
|
311
|
+
# Type a character in focused element
|
|
312
|
+
press_key "a"
|
|
313
|
+
|
|
314
|
+
# Press Tab to navigate
|
|
315
|
+
press_key "Tab"
|
|
316
|
+
|
|
317
|
+
# Press with multiple modifiers
|
|
318
|
+
press_key "s" --modifiers Ctrl,Shift
|
|
319
|
+
|
|
320
|
+
Supported Keys:
|
|
321
|
+
- Letters: a-z, A-Z
|
|
322
|
+
- Numbers: 0-9
|
|
323
|
+
- Special: Enter, Escape, Tab, Backspace, Delete, Space
|
|
324
|
+
- Arrows: ArrowUp, ArrowDown, ArrowLeft, ArrowRight
|
|
325
|
+
- Navigation: Home, End, PageUp, PageDown
|
|
326
|
+
- Modifiers: Ctrl, Alt, Shift, Meta
|
|
327
|
+
|
|
328
|
+
Note:
|
|
329
|
+
- Uses JavaScript KeyboardEvent simulation
|
|
330
|
+
- Dispatches keydown, keypress (for printable chars), and keyup events
|
|
331
|
+
- For input fields, automatically updates value and triggers input/change events
|
|
332
|
+
- If selector is provided, focuses the element first
|
|
333
|
+
- Works with keyboard event listeners and form validation
|
|
334
|
+
`;
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
exports.PressKeyHandler = PressKeyHandler;
|