@democratize-quality/mcp-server 1.0.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/LICENSE +15 -0
- package/README.md +423 -0
- package/browserControl.js +113 -0
- package/cli.js +187 -0
- package/docs/api/tool-reference.md +317 -0
- package/docs/api_tools_usage.md +477 -0
- package/docs/development/adding-tools.md +274 -0
- package/docs/development/configuration.md +332 -0
- package/docs/examples/authentication.md +124 -0
- package/docs/examples/basic-automation.md +105 -0
- package/docs/getting-started.md +214 -0
- package/docs/index.md +61 -0
- package/mcpServer.js +280 -0
- package/package.json +83 -0
- package/run-server.js +140 -0
- package/src/config/environments/api-only.js +53 -0
- package/src/config/environments/development.js +54 -0
- package/src/config/environments/production.js +69 -0
- package/src/config/index.js +341 -0
- package/src/config/server.js +41 -0
- package/src/config/tools/api.js +67 -0
- package/src/config/tools/browser.js +90 -0
- package/src/config/tools/default.js +32 -0
- package/src/services/browserService.js +325 -0
- package/src/tools/api/api-request.js +641 -0
- package/src/tools/api/api-session-report.js +1262 -0
- package/src/tools/api/api-session-status.js +395 -0
- package/src/tools/base/ToolBase.js +230 -0
- package/src/tools/base/ToolRegistry.js +269 -0
- package/src/tools/browser/advanced/browser-console.js +384 -0
- package/src/tools/browser/advanced/browser-dialog.js +319 -0
- package/src/tools/browser/advanced/browser-evaluate.js +337 -0
- package/src/tools/browser/advanced/browser-file.js +480 -0
- package/src/tools/browser/advanced/browser-keyboard.js +343 -0
- package/src/tools/browser/advanced/browser-mouse.js +332 -0
- package/src/tools/browser/advanced/browser-network.js +421 -0
- package/src/tools/browser/advanced/browser-pdf.js +407 -0
- package/src/tools/browser/advanced/browser-tabs.js +497 -0
- package/src/tools/browser/advanced/browser-wait.js +378 -0
- package/src/tools/browser/click.js +168 -0
- package/src/tools/browser/close.js +60 -0
- package/src/tools/browser/dom.js +70 -0
- package/src/tools/browser/launch.js +67 -0
- package/src/tools/browser/navigate.js +270 -0
- package/src/tools/browser/screenshot.js +351 -0
- package/src/tools/browser/type.js +174 -0
- package/src/tools/index.js +95 -0
- package/src/utils/browserHelpers.js +83 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
const ToolBase = require('../../base/ToolBase');
|
|
2
|
+
const browserService = require('../../../services/browserService');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Enhanced Keyboard Tool - Provides comprehensive keyboard interactions
|
|
6
|
+
* Inspired by Playwright MCP keyboard capabilities
|
|
7
|
+
*/
|
|
8
|
+
class BrowserKeyboardTool extends ToolBase {
|
|
9
|
+
static definition = {
|
|
10
|
+
name: "browser_keyboard",
|
|
11
|
+
description: "Perform keyboard actions including typing, key presses, and keyboard shortcuts. Supports special keys and modifier combinations.",
|
|
12
|
+
input_schema: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
browserId: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "The ID of the browser instance"
|
|
18
|
+
},
|
|
19
|
+
action: {
|
|
20
|
+
type: "string",
|
|
21
|
+
enum: ["type", "press", "down", "up", "shortcut"],
|
|
22
|
+
description: "The keyboard action to perform"
|
|
23
|
+
},
|
|
24
|
+
text: {
|
|
25
|
+
type: "string",
|
|
26
|
+
description: "Text to type (for 'type' action)"
|
|
27
|
+
},
|
|
28
|
+
key: {
|
|
29
|
+
type: "string",
|
|
30
|
+
description: "Key to press (for 'press', 'down', 'up' actions). Examples: 'Enter', 'Tab', 'ArrowLeft', 'F1'"
|
|
31
|
+
},
|
|
32
|
+
shortcut: {
|
|
33
|
+
type: "string",
|
|
34
|
+
description: "Keyboard shortcut (for 'shortcut' action). Examples: 'Ctrl+C', 'Cmd+V', 'Ctrl+Shift+I'"
|
|
35
|
+
},
|
|
36
|
+
target: {
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {
|
|
39
|
+
selector: {
|
|
40
|
+
type: "string",
|
|
41
|
+
description: "CSS selector of element to focus before typing (optional)"
|
|
42
|
+
}
|
|
43
|
+
},
|
|
44
|
+
description: "Target element to focus before keyboard action"
|
|
45
|
+
},
|
|
46
|
+
delay: {
|
|
47
|
+
type: "number",
|
|
48
|
+
default: 0,
|
|
49
|
+
description: "Delay between keystrokes in milliseconds"
|
|
50
|
+
},
|
|
51
|
+
modifiers: {
|
|
52
|
+
type: "array",
|
|
53
|
+
items: { type: "string", enum: ["ctrl", "shift", "alt", "meta"] },
|
|
54
|
+
description: "Modifier keys to hold during action"
|
|
55
|
+
}
|
|
56
|
+
},
|
|
57
|
+
required: ["browserId", "action"]
|
|
58
|
+
},
|
|
59
|
+
output_schema: {
|
|
60
|
+
type: "object",
|
|
61
|
+
properties: {
|
|
62
|
+
success: { type: "boolean", description: "Whether the keyboard action was successful" },
|
|
63
|
+
action: { type: "string", description: "The action that was performed" },
|
|
64
|
+
text: { type: "string", description: "Text that was typed" },
|
|
65
|
+
key: { type: "string", description: "Key that was pressed" },
|
|
66
|
+
target: { type: "string", description: "CSS selector of targeted element" },
|
|
67
|
+
browserId: { type: "string", description: "Browser instance ID" }
|
|
68
|
+
},
|
|
69
|
+
required: ["success", "action", "browserId"]
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
async execute(parameters) {
|
|
74
|
+
const { browserId, action, text, key, shortcut, target, delay = 0, modifiers = [] } = parameters;
|
|
75
|
+
|
|
76
|
+
const browser = browserService.getBrowserInstance(browserId);
|
|
77
|
+
if (!browser) {
|
|
78
|
+
throw new Error(`Browser instance '${browserId}' not found`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const client = browser.client;
|
|
82
|
+
|
|
83
|
+
// Focus target element if specified
|
|
84
|
+
if (target?.selector) {
|
|
85
|
+
await this.focusElement(client, target.selector);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
let result = {
|
|
89
|
+
success: true,
|
|
90
|
+
action: action,
|
|
91
|
+
browserId: browserId
|
|
92
|
+
};
|
|
93
|
+
|
|
94
|
+
// Convert modifiers to CDP format
|
|
95
|
+
const cdpModifiers = this.convertModifiers(modifiers);
|
|
96
|
+
|
|
97
|
+
switch (action) {
|
|
98
|
+
case 'type':
|
|
99
|
+
if (!text) {
|
|
100
|
+
throw new Error('Text is required for type action');
|
|
101
|
+
}
|
|
102
|
+
await this.typeText(client, text, delay);
|
|
103
|
+
result.text = text;
|
|
104
|
+
break;
|
|
105
|
+
|
|
106
|
+
case 'press':
|
|
107
|
+
if (!key) {
|
|
108
|
+
throw new Error('Key is required for press action');
|
|
109
|
+
}
|
|
110
|
+
await this.pressKey(client, key, cdpModifiers);
|
|
111
|
+
result.key = key;
|
|
112
|
+
break;
|
|
113
|
+
|
|
114
|
+
case 'down':
|
|
115
|
+
if (!key) {
|
|
116
|
+
throw new Error('Key is required for down action');
|
|
117
|
+
}
|
|
118
|
+
await this.keyDown(client, key, cdpModifiers);
|
|
119
|
+
result.key = key;
|
|
120
|
+
break;
|
|
121
|
+
|
|
122
|
+
case 'up':
|
|
123
|
+
if (!key) {
|
|
124
|
+
throw new Error('Key is required for up action');
|
|
125
|
+
}
|
|
126
|
+
await this.keyUp(client, key, cdpModifiers);
|
|
127
|
+
result.key = key;
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case 'shortcut':
|
|
131
|
+
if (!shortcut) {
|
|
132
|
+
throw new Error('Shortcut is required for shortcut action');
|
|
133
|
+
}
|
|
134
|
+
await this.executeShortcut(client, shortcut);
|
|
135
|
+
result.shortcut = shortcut;
|
|
136
|
+
break;
|
|
137
|
+
|
|
138
|
+
default:
|
|
139
|
+
throw new Error(`Unsupported keyboard action: ${action}`);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
if (target?.selector) {
|
|
143
|
+
result.target = target.selector;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
return result;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Focus an element by CSS selector
|
|
151
|
+
*/
|
|
152
|
+
async focusElement(client, selector) {
|
|
153
|
+
const result = await client.Runtime.evaluate({
|
|
154
|
+
expression: `
|
|
155
|
+
(() => {
|
|
156
|
+
const element = document.querySelector('${selector}');
|
|
157
|
+
if (element) {
|
|
158
|
+
element.focus();
|
|
159
|
+
return true;
|
|
160
|
+
}
|
|
161
|
+
return false;
|
|
162
|
+
})()
|
|
163
|
+
`
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
if (!result.result.value) {
|
|
167
|
+
throw new Error(`Could not focus element with selector: ${selector}`);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
/**
|
|
172
|
+
* Type text with optional delay between keystrokes
|
|
173
|
+
*/
|
|
174
|
+
async typeText(client, text, delay) {
|
|
175
|
+
for (const char of text) {
|
|
176
|
+
await client.Input.dispatchKeyEvent({
|
|
177
|
+
type: 'keyDown',
|
|
178
|
+
text: char
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
await client.Input.dispatchKeyEvent({
|
|
182
|
+
type: 'keyUp',
|
|
183
|
+
text: char
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
if (delay > 0) {
|
|
187
|
+
await new Promise(resolve => setTimeout(resolve, delay));
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Press a key (down and up)
|
|
194
|
+
*/
|
|
195
|
+
async pressKey(client, key, modifiers = 0) {
|
|
196
|
+
const keyInfo = this.getKeyInfo(key);
|
|
197
|
+
|
|
198
|
+
await client.Input.dispatchKeyEvent({
|
|
199
|
+
type: 'keyDown',
|
|
200
|
+
key: keyInfo.key,
|
|
201
|
+
code: keyInfo.code,
|
|
202
|
+
modifiers: modifiers
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
await client.Input.dispatchKeyEvent({
|
|
206
|
+
type: 'keyUp',
|
|
207
|
+
key: keyInfo.key,
|
|
208
|
+
code: keyInfo.code,
|
|
209
|
+
modifiers: modifiers
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Key down
|
|
215
|
+
*/
|
|
216
|
+
async keyDown(client, key, modifiers = 0) {
|
|
217
|
+
const keyInfo = this.getKeyInfo(key);
|
|
218
|
+
|
|
219
|
+
await client.Input.dispatchKeyEvent({
|
|
220
|
+
type: 'keyDown',
|
|
221
|
+
key: keyInfo.key,
|
|
222
|
+
code: keyInfo.code,
|
|
223
|
+
modifiers: modifiers
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/**
|
|
228
|
+
* Key up
|
|
229
|
+
*/
|
|
230
|
+
async keyUp(client, key, modifiers = 0) {
|
|
231
|
+
const keyInfo = this.getKeyInfo(key);
|
|
232
|
+
|
|
233
|
+
await client.Input.dispatchKeyEvent({
|
|
234
|
+
type: 'keyUp',
|
|
235
|
+
key: keyInfo.key,
|
|
236
|
+
code: keyInfo.code,
|
|
237
|
+
modifiers: modifiers
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Execute keyboard shortcut
|
|
243
|
+
*/
|
|
244
|
+
async executeShortcut(client, shortcut) {
|
|
245
|
+
const keys = this.parseShortcut(shortcut);
|
|
246
|
+
|
|
247
|
+
// Press modifier keys
|
|
248
|
+
for (const modifier of keys.modifiers) {
|
|
249
|
+
await this.keyDown(client, modifier);
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
// Press main key
|
|
253
|
+
if (keys.key) {
|
|
254
|
+
await this.pressKey(client, keys.key, this.convertModifiers(keys.modifiers));
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Release modifier keys in reverse order
|
|
258
|
+
for (let i = keys.modifiers.length - 1; i >= 0; i--) {
|
|
259
|
+
await this.keyUp(client, keys.modifiers[i]);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
/**
|
|
264
|
+
* Parse keyboard shortcut string
|
|
265
|
+
*/
|
|
266
|
+
parseShortcut(shortcut) {
|
|
267
|
+
const parts = shortcut.split('+');
|
|
268
|
+
const key = parts.pop();
|
|
269
|
+
const modifiers = parts.map(mod => mod.toLowerCase());
|
|
270
|
+
|
|
271
|
+
return { key, modifiers };
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
/**
|
|
275
|
+
* Get key information for CDP
|
|
276
|
+
*/
|
|
277
|
+
getKeyInfo(key) {
|
|
278
|
+
const keyMap = {
|
|
279
|
+
'Enter': { key: 'Enter', code: 'Enter' },
|
|
280
|
+
'Tab': { key: 'Tab', code: 'Tab' },
|
|
281
|
+
'Escape': { key: 'Escape', code: 'Escape' },
|
|
282
|
+
'Space': { key: ' ', code: 'Space' },
|
|
283
|
+
'ArrowLeft': { key: 'ArrowLeft', code: 'ArrowLeft' },
|
|
284
|
+
'ArrowRight': { key: 'ArrowRight', code: 'ArrowRight' },
|
|
285
|
+
'ArrowUp': { key: 'ArrowUp', code: 'ArrowUp' },
|
|
286
|
+
'ArrowDown': { key: 'ArrowDown', code: 'ArrowDown' },
|
|
287
|
+
'Backspace': { key: 'Backspace', code: 'Backspace' },
|
|
288
|
+
'Delete': { key: 'Delete', code: 'Delete' },
|
|
289
|
+
'Home': { key: 'Home', code: 'Home' },
|
|
290
|
+
'End': { key: 'End', code: 'End' },
|
|
291
|
+
'PageUp': { key: 'PageUp', code: 'PageUp' },
|
|
292
|
+
'PageDown': { key: 'PageDown', code: 'PageDown' },
|
|
293
|
+
'F1': { key: 'F1', code: 'F1' },
|
|
294
|
+
'F2': { key: 'F2', code: 'F2' },
|
|
295
|
+
'F3': { key: 'F3', code: 'F3' },
|
|
296
|
+
'F4': { key: 'F4', code: 'F4' },
|
|
297
|
+
'F5': { key: 'F5', code: 'F5' },
|
|
298
|
+
'F6': { key: 'F6', code: 'F6' },
|
|
299
|
+
'F7': { key: 'F7', code: 'F7' },
|
|
300
|
+
'F8': { key: 'F8', code: 'F8' },
|
|
301
|
+
'F9': { key: 'F9', code: 'F9' },
|
|
302
|
+
'F10': { key: 'F10', code: 'F10' },
|
|
303
|
+
'F11': { key: 'F11', code: 'F11' },
|
|
304
|
+
'F12': { key: 'F12', code: 'F12' }
|
|
305
|
+
};
|
|
306
|
+
|
|
307
|
+
if (keyMap[key]) {
|
|
308
|
+
return keyMap[key];
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
// For single characters
|
|
312
|
+
return { key: key, code: `Key${key.toUpperCase()}` };
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
/**
|
|
316
|
+
* Convert modifier keys to CDP format
|
|
317
|
+
*/
|
|
318
|
+
convertModifiers(modifiers) {
|
|
319
|
+
let cdpModifiers = 0;
|
|
320
|
+
|
|
321
|
+
for (const modifier of modifiers) {
|
|
322
|
+
switch (modifier.toLowerCase()) {
|
|
323
|
+
case 'ctrl':
|
|
324
|
+
cdpModifiers |= 2; // Ctrl
|
|
325
|
+
break;
|
|
326
|
+
case 'shift':
|
|
327
|
+
cdpModifiers |= 8; // Shift
|
|
328
|
+
break;
|
|
329
|
+
case 'alt':
|
|
330
|
+
cdpModifiers |= 1; // Alt
|
|
331
|
+
break;
|
|
332
|
+
case 'meta':
|
|
333
|
+
case 'cmd':
|
|
334
|
+
cdpModifiers |= 4; // Meta/Cmd
|
|
335
|
+
break;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
return cdpModifiers;
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
module.exports = BrowserKeyboardTool;
|
|
@@ -0,0 +1,332 @@
|
|
|
1
|
+
const ToolBase = require('../../base/ToolBase');
|
|
2
|
+
const browserService = require('../../../services/browserService');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Enhanced Mouse Tool - Provides precise mouse interactions with coordinate support
|
|
6
|
+
* Inspired by Playwright MCP mouse capabilities
|
|
7
|
+
*/
|
|
8
|
+
class BrowserMouseTool extends ToolBase {
|
|
9
|
+
static definition = {
|
|
10
|
+
name: "browser_mouse",
|
|
11
|
+
description: "Perform mouse actions including clicks, moves, and drags with precise coordinate control. Supports both CSS selectors and direct coordinates.",
|
|
12
|
+
input_schema: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
browserId: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "The ID of the browser instance"
|
|
18
|
+
},
|
|
19
|
+
action: {
|
|
20
|
+
type: "string",
|
|
21
|
+
enum: ["click", "move", "drag", "hover", "rightClick", "doubleClick"],
|
|
22
|
+
description: "The mouse action to perform"
|
|
23
|
+
},
|
|
24
|
+
target: {
|
|
25
|
+
type: "object",
|
|
26
|
+
oneOf: [
|
|
27
|
+
{
|
|
28
|
+
type: "object",
|
|
29
|
+
properties: {
|
|
30
|
+
type: { type: "string", enum: ["coordinates"] },
|
|
31
|
+
x: { type: "number", description: "X coordinate" },
|
|
32
|
+
y: { type: "number", description: "Y coordinate" }
|
|
33
|
+
},
|
|
34
|
+
required: ["type", "x", "y"]
|
|
35
|
+
},
|
|
36
|
+
{
|
|
37
|
+
type: "object",
|
|
38
|
+
properties: {
|
|
39
|
+
type: { type: "string", enum: ["selector"] },
|
|
40
|
+
selector: { type: "string", description: "CSS selector" },
|
|
41
|
+
offset: {
|
|
42
|
+
type: "object",
|
|
43
|
+
properties: {
|
|
44
|
+
x: { type: "number", description: "X offset from element center" },
|
|
45
|
+
y: { type: "number", description: "Y offset from element center" }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
},
|
|
49
|
+
required: ["type", "selector"]
|
|
50
|
+
}
|
|
51
|
+
],
|
|
52
|
+
description: "Target for the mouse action - either coordinates or CSS selector"
|
|
53
|
+
},
|
|
54
|
+
dragTo: {
|
|
55
|
+
type: "object",
|
|
56
|
+
properties: {
|
|
57
|
+
x: { type: "number", description: "End X coordinate for drag" },
|
|
58
|
+
y: { type: "number", description: "End Y coordinate for drag" }
|
|
59
|
+
},
|
|
60
|
+
description: "End coordinates for drag actions"
|
|
61
|
+
},
|
|
62
|
+
button: {
|
|
63
|
+
type: "string",
|
|
64
|
+
enum: ["left", "right", "middle"],
|
|
65
|
+
default: "left",
|
|
66
|
+
description: "Mouse button to use"
|
|
67
|
+
},
|
|
68
|
+
modifiers: {
|
|
69
|
+
type: "array",
|
|
70
|
+
items: { type: "string", enum: ["ctrl", "shift", "alt", "meta"] },
|
|
71
|
+
description: "Keyboard modifiers to hold during action"
|
|
72
|
+
}
|
|
73
|
+
},
|
|
74
|
+
required: ["browserId", "action", "target"]
|
|
75
|
+
},
|
|
76
|
+
output_schema: {
|
|
77
|
+
type: "object",
|
|
78
|
+
properties: {
|
|
79
|
+
success: { type: "boolean", description: "Whether the mouse action was successful" },
|
|
80
|
+
action: { type: "string", description: "The action that was performed" },
|
|
81
|
+
coordinates: {
|
|
82
|
+
type: "object",
|
|
83
|
+
properties: {
|
|
84
|
+
x: { type: "number" },
|
|
85
|
+
y: { type: "number" }
|
|
86
|
+
},
|
|
87
|
+
description: "Final coordinates of the mouse action"
|
|
88
|
+
},
|
|
89
|
+
element: { type: "string", description: "CSS selector if targeting an element" },
|
|
90
|
+
browserId: { type: "string", description: "Browser instance ID" }
|
|
91
|
+
},
|
|
92
|
+
required: ["success", "action", "browserId"]
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
async execute(parameters) {
|
|
97
|
+
const { browserId, action, target, dragTo, button = 'left', modifiers = [] } = parameters;
|
|
98
|
+
|
|
99
|
+
const browser = browserService.getBrowserInstance(browserId);
|
|
100
|
+
if (!browser) {
|
|
101
|
+
throw new Error(`Browser instance '${browserId}' not found`);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const client = browser.client;
|
|
105
|
+
|
|
106
|
+
// Calculate target coordinates
|
|
107
|
+
let targetX, targetY;
|
|
108
|
+
let elementInfo = null;
|
|
109
|
+
|
|
110
|
+
if (target.type === 'coordinates') {
|
|
111
|
+
targetX = target.x;
|
|
112
|
+
targetY = target.y;
|
|
113
|
+
} else if (target.type === 'selector') {
|
|
114
|
+
// Get element bounds using DOM API
|
|
115
|
+
const result = await client.Runtime.evaluate({
|
|
116
|
+
expression: `
|
|
117
|
+
(() => {
|
|
118
|
+
const element = document.querySelector('${target.selector}');
|
|
119
|
+
if (!element) return null;
|
|
120
|
+
const rect = element.getBoundingClientRect();
|
|
121
|
+
return {
|
|
122
|
+
x: rect.left + rect.width / 2,
|
|
123
|
+
y: rect.top + rect.height / 2,
|
|
124
|
+
width: rect.width,
|
|
125
|
+
height: rect.height,
|
|
126
|
+
selector: '${target.selector}'
|
|
127
|
+
};
|
|
128
|
+
})()
|
|
129
|
+
`
|
|
130
|
+
});
|
|
131
|
+
|
|
132
|
+
if (!result.result.value) {
|
|
133
|
+
throw new Error(`Element not found with selector: ${target.selector}`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const elementData = result.result.value;
|
|
137
|
+
targetX = elementData.x + (target.offset?.x || 0);
|
|
138
|
+
targetY = elementData.y + (target.offset?.y || 0);
|
|
139
|
+
elementInfo = {
|
|
140
|
+
selector: target.selector,
|
|
141
|
+
bounds: elementData
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Convert modifiers to CDP format
|
|
146
|
+
const cdpModifiers = this.convertModifiers(modifiers);
|
|
147
|
+
|
|
148
|
+
// Perform the mouse action
|
|
149
|
+
let actionResult;
|
|
150
|
+
|
|
151
|
+
switch (action) {
|
|
152
|
+
case 'move':
|
|
153
|
+
await client.Input.dispatchMouseEvent({
|
|
154
|
+
type: 'mouseMoved',
|
|
155
|
+
x: targetX,
|
|
156
|
+
y: targetY,
|
|
157
|
+
modifiers: cdpModifiers
|
|
158
|
+
});
|
|
159
|
+
actionResult = { type: 'move', x: targetX, y: targetY };
|
|
160
|
+
break;
|
|
161
|
+
|
|
162
|
+
case 'click':
|
|
163
|
+
await client.Input.dispatchMouseEvent({
|
|
164
|
+
type: 'mousePressed',
|
|
165
|
+
x: targetX,
|
|
166
|
+
y: targetY,
|
|
167
|
+
button: button,
|
|
168
|
+
clickCount: 1,
|
|
169
|
+
modifiers: cdpModifiers
|
|
170
|
+
});
|
|
171
|
+
await client.Input.dispatchMouseEvent({
|
|
172
|
+
type: 'mouseReleased',
|
|
173
|
+
x: targetX,
|
|
174
|
+
y: targetY,
|
|
175
|
+
button: button,
|
|
176
|
+
clickCount: 1,
|
|
177
|
+
modifiers: cdpModifiers
|
|
178
|
+
});
|
|
179
|
+
actionResult = { type: 'click', x: targetX, y: targetY };
|
|
180
|
+
break;
|
|
181
|
+
|
|
182
|
+
case 'doubleClick':
|
|
183
|
+
// First click
|
|
184
|
+
await client.Input.dispatchMouseEvent({
|
|
185
|
+
type: 'mousePressed',
|
|
186
|
+
x: targetX,
|
|
187
|
+
y: targetY,
|
|
188
|
+
button: button,
|
|
189
|
+
clickCount: 1,
|
|
190
|
+
modifiers: cdpModifiers
|
|
191
|
+
});
|
|
192
|
+
await client.Input.dispatchMouseEvent({
|
|
193
|
+
type: 'mouseReleased',
|
|
194
|
+
x: targetX,
|
|
195
|
+
y: targetY,
|
|
196
|
+
button: button,
|
|
197
|
+
clickCount: 1,
|
|
198
|
+
modifiers: cdpModifiers
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
// Second click (double click)
|
|
202
|
+
await client.Input.dispatchMouseEvent({
|
|
203
|
+
type: 'mousePressed',
|
|
204
|
+
x: targetX,
|
|
205
|
+
y: targetY,
|
|
206
|
+
button: button,
|
|
207
|
+
clickCount: 2,
|
|
208
|
+
modifiers: cdpModifiers
|
|
209
|
+
});
|
|
210
|
+
await client.Input.dispatchMouseEvent({
|
|
211
|
+
type: 'mouseReleased',
|
|
212
|
+
x: targetX,
|
|
213
|
+
y: targetY,
|
|
214
|
+
button: button,
|
|
215
|
+
clickCount: 2,
|
|
216
|
+
modifiers: cdpModifiers
|
|
217
|
+
});
|
|
218
|
+
actionResult = { type: 'doubleClick', x: targetX, y: targetY };
|
|
219
|
+
break;
|
|
220
|
+
|
|
221
|
+
case 'rightClick':
|
|
222
|
+
await client.Input.dispatchMouseEvent({
|
|
223
|
+
type: 'mousePressed',
|
|
224
|
+
x: targetX,
|
|
225
|
+
y: targetY,
|
|
226
|
+
button: 'right',
|
|
227
|
+
clickCount: 1,
|
|
228
|
+
modifiers: cdpModifiers
|
|
229
|
+
});
|
|
230
|
+
await client.Input.dispatchMouseEvent({
|
|
231
|
+
type: 'mouseReleased',
|
|
232
|
+
x: targetX,
|
|
233
|
+
y: targetY,
|
|
234
|
+
button: 'right',
|
|
235
|
+
clickCount: 1,
|
|
236
|
+
modifiers: cdpModifiers
|
|
237
|
+
});
|
|
238
|
+
actionResult = { type: 'rightClick', x: targetX, y: targetY };
|
|
239
|
+
break;
|
|
240
|
+
|
|
241
|
+
case 'hover':
|
|
242
|
+
await client.Input.dispatchMouseEvent({
|
|
243
|
+
type: 'mouseMoved',
|
|
244
|
+
x: targetX,
|
|
245
|
+
y: targetY,
|
|
246
|
+
modifiers: cdpModifiers
|
|
247
|
+
});
|
|
248
|
+
actionResult = { type: 'hover', x: targetX, y: targetY };
|
|
249
|
+
break;
|
|
250
|
+
|
|
251
|
+
case 'drag':
|
|
252
|
+
if (!dragTo) {
|
|
253
|
+
throw new Error('dragTo coordinates required for drag action');
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Start drag
|
|
257
|
+
await client.Input.dispatchMouseEvent({
|
|
258
|
+
type: 'mousePressed',
|
|
259
|
+
x: targetX,
|
|
260
|
+
y: targetY,
|
|
261
|
+
button: button,
|
|
262
|
+
clickCount: 1,
|
|
263
|
+
modifiers: cdpModifiers
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
// Move to end position
|
|
267
|
+
await client.Input.dispatchMouseEvent({
|
|
268
|
+
type: 'mouseMoved',
|
|
269
|
+
x: dragTo.x,
|
|
270
|
+
y: dragTo.y,
|
|
271
|
+
modifiers: cdpModifiers
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Release
|
|
275
|
+
await client.Input.dispatchMouseEvent({
|
|
276
|
+
type: 'mouseReleased',
|
|
277
|
+
x: dragTo.x,
|
|
278
|
+
y: dragTo.y,
|
|
279
|
+
button: button,
|
|
280
|
+
clickCount: 1,
|
|
281
|
+
modifiers: cdpModifiers
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
actionResult = {
|
|
285
|
+
type: 'drag',
|
|
286
|
+
from: { x: targetX, y: targetY },
|
|
287
|
+
to: { x: dragTo.x, y: dragTo.y }
|
|
288
|
+
};
|
|
289
|
+
break;
|
|
290
|
+
|
|
291
|
+
default:
|
|
292
|
+
throw new Error(`Unsupported mouse action: ${action}`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return {
|
|
296
|
+
success: true,
|
|
297
|
+
action: action,
|
|
298
|
+
coordinates: action === 'drag' ? actionResult : { x: targetX, y: targetY },
|
|
299
|
+
element: elementInfo?.selector,
|
|
300
|
+
browserId: browserId,
|
|
301
|
+
actionDetails: actionResult
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* Convert modifier keys to CDP format
|
|
307
|
+
*/
|
|
308
|
+
convertModifiers(modifiers) {
|
|
309
|
+
let cdpModifiers = 0;
|
|
310
|
+
|
|
311
|
+
for (const modifier of modifiers) {
|
|
312
|
+
switch (modifier) {
|
|
313
|
+
case 'ctrl':
|
|
314
|
+
cdpModifiers |= 2; // Ctrl
|
|
315
|
+
break;
|
|
316
|
+
case 'shift':
|
|
317
|
+
cdpModifiers |= 8; // Shift
|
|
318
|
+
break;
|
|
319
|
+
case 'alt':
|
|
320
|
+
cdpModifiers |= 1; // Alt
|
|
321
|
+
break;
|
|
322
|
+
case 'meta':
|
|
323
|
+
cdpModifiers |= 4; // Meta/Cmd
|
|
324
|
+
break;
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
return cdpModifiers;
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
module.exports = BrowserMouseTool;
|