@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,319 @@
|
|
|
1
|
+
const ToolBase = require('../../base/ToolBase');
|
|
2
|
+
const browserService = require('../../../services/browserService');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Dialog Tool - Handle browser dialogs (alert, confirm, prompt)
|
|
6
|
+
* Inspired by Playwright MCP dialog capabilities
|
|
7
|
+
*/
|
|
8
|
+
class BrowserDialogTool extends ToolBase {
|
|
9
|
+
static definition = {
|
|
10
|
+
name: "browser_dialog",
|
|
11
|
+
description: "Handle browser dialogs including alert, confirm, and prompt dialogs with automatic detection and response.",
|
|
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: ["handle", "dismiss", "accept", "getInfo", "setupHandler"],
|
|
22
|
+
description: "Dialog action to perform"
|
|
23
|
+
},
|
|
24
|
+
accept: {
|
|
25
|
+
type: "boolean",
|
|
26
|
+
default: true,
|
|
27
|
+
description: "Whether to accept or dismiss the dialog (for handle action)"
|
|
28
|
+
},
|
|
29
|
+
promptText: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "Text to enter in prompt dialogs"
|
|
32
|
+
},
|
|
33
|
+
autoHandle: {
|
|
34
|
+
type: "boolean",
|
|
35
|
+
default: false,
|
|
36
|
+
description: "Whether to automatically handle future dialogs (for setupHandler action)"
|
|
37
|
+
},
|
|
38
|
+
defaultResponse: {
|
|
39
|
+
type: "object",
|
|
40
|
+
properties: {
|
|
41
|
+
accept: { type: "boolean", description: "Default accept/dismiss behavior" },
|
|
42
|
+
promptText: { type: "string", description: "Default text for prompts" }
|
|
43
|
+
},
|
|
44
|
+
description: "Default responses for auto-handling dialogs"
|
|
45
|
+
}
|
|
46
|
+
},
|
|
47
|
+
required: ["browserId", "action"]
|
|
48
|
+
},
|
|
49
|
+
output_schema: {
|
|
50
|
+
type: "object",
|
|
51
|
+
properties: {
|
|
52
|
+
success: { type: "boolean", description: "Whether the operation was successful" },
|
|
53
|
+
action: { type: "string", description: "The action that was performed" },
|
|
54
|
+
dialog: {
|
|
55
|
+
type: "object",
|
|
56
|
+
properties: {
|
|
57
|
+
type: { type: "string" },
|
|
58
|
+
message: { type: "string" },
|
|
59
|
+
defaultValue: { type: "string" },
|
|
60
|
+
handled: { type: "boolean" },
|
|
61
|
+
response: { type: "string" }
|
|
62
|
+
},
|
|
63
|
+
description: "Dialog information"
|
|
64
|
+
},
|
|
65
|
+
autoHandling: { type: "boolean", description: "Whether auto-handling is enabled" },
|
|
66
|
+
browserId: { type: "string", description: "Browser instance ID" }
|
|
67
|
+
},
|
|
68
|
+
required: ["success", "action", "browserId"]
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
constructor() {
|
|
73
|
+
super();
|
|
74
|
+
this.dialogStates = new Map(); // browserId -> dialog state
|
|
75
|
+
this.autoHandlers = new Map(); // browserId -> auto-handler config
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async execute(parameters) {
|
|
79
|
+
const {
|
|
80
|
+
browserId,
|
|
81
|
+
action,
|
|
82
|
+
accept = true,
|
|
83
|
+
promptText,
|
|
84
|
+
autoHandle = false,
|
|
85
|
+
defaultResponse = {}
|
|
86
|
+
} = parameters;
|
|
87
|
+
|
|
88
|
+
const browser = browserService.getBrowserInstance(browserId);
|
|
89
|
+
if (!browser) {
|
|
90
|
+
throw new Error(`Browser instance '${browserId}' not found`);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
const client = browser.client;
|
|
94
|
+
|
|
95
|
+
let result = {
|
|
96
|
+
success: false,
|
|
97
|
+
action: action,
|
|
98
|
+
browserId: browserId
|
|
99
|
+
};
|
|
100
|
+
|
|
101
|
+
switch (action) {
|
|
102
|
+
case 'setupHandler':
|
|
103
|
+
await this.setupDialogHandler(client, browserId, autoHandle, defaultResponse);
|
|
104
|
+
result.success = true;
|
|
105
|
+
result.autoHandling = autoHandle;
|
|
106
|
+
result.message = autoHandle ? 'Auto dialog handling enabled' : 'Dialog monitoring enabled';
|
|
107
|
+
break;
|
|
108
|
+
|
|
109
|
+
case 'handle':
|
|
110
|
+
const handleResult = await this.handleDialog(browserId, accept, promptText);
|
|
111
|
+
result.success = true;
|
|
112
|
+
result.dialog = handleResult;
|
|
113
|
+
result.message = 'Dialog handled';
|
|
114
|
+
break;
|
|
115
|
+
|
|
116
|
+
case 'accept':
|
|
117
|
+
const acceptResult = await this.handleDialog(browserId, true, promptText);
|
|
118
|
+
result.success = true;
|
|
119
|
+
result.dialog = acceptResult;
|
|
120
|
+
result.message = 'Dialog accepted';
|
|
121
|
+
break;
|
|
122
|
+
|
|
123
|
+
case 'dismiss':
|
|
124
|
+
const dismissResult = await this.handleDialog(browserId, false);
|
|
125
|
+
result.success = true;
|
|
126
|
+
result.dialog = dismissResult;
|
|
127
|
+
result.message = 'Dialog dismissed';
|
|
128
|
+
break;
|
|
129
|
+
|
|
130
|
+
case 'getInfo':
|
|
131
|
+
const dialogInfo = this.getDialogInfo(browserId);
|
|
132
|
+
result.success = true;
|
|
133
|
+
result.dialog = dialogInfo.dialog;
|
|
134
|
+
result.autoHandling = dialogInfo.autoHandling;
|
|
135
|
+
break;
|
|
136
|
+
|
|
137
|
+
default:
|
|
138
|
+
throw new Error(`Unsupported dialog action: ${action}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return result;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Setup dialog handler with auto-handling capability
|
|
146
|
+
*/
|
|
147
|
+
async setupDialogHandler(client, browserId, autoHandle, defaultResponse) {
|
|
148
|
+
// Enable Page domain for dialog events
|
|
149
|
+
await client.Page.enable();
|
|
150
|
+
|
|
151
|
+
// Store auto-handler configuration
|
|
152
|
+
if (autoHandle) {
|
|
153
|
+
this.autoHandlers.set(browserId, {
|
|
154
|
+
enabled: true,
|
|
155
|
+
accept: defaultResponse.accept !== undefined ? defaultResponse.accept : true,
|
|
156
|
+
promptText: defaultResponse.promptText || ''
|
|
157
|
+
});
|
|
158
|
+
} else {
|
|
159
|
+
this.autoHandlers.delete(browserId);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// Set up dialog event listener
|
|
163
|
+
client.Page.javascriptDialogOpening((params) => {
|
|
164
|
+
const dialogInfo = {
|
|
165
|
+
type: params.type,
|
|
166
|
+
message: params.message,
|
|
167
|
+
defaultValue: params.defaultPrompt || '',
|
|
168
|
+
timestamp: new Date().toISOString(),
|
|
169
|
+
handled: false
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
this.dialogStates.set(browserId, dialogInfo);
|
|
173
|
+
|
|
174
|
+
// Auto-handle if configured
|
|
175
|
+
const autoHandler = this.autoHandlers.get(browserId);
|
|
176
|
+
if (autoHandler && autoHandler.enabled) {
|
|
177
|
+
setTimeout(async () => {
|
|
178
|
+
try {
|
|
179
|
+
await this.handleDialog(
|
|
180
|
+
browserId,
|
|
181
|
+
autoHandler.accept,
|
|
182
|
+
params.type === 'prompt' ? autoHandler.promptText : undefined
|
|
183
|
+
);
|
|
184
|
+
} catch (error) {
|
|
185
|
+
console.error(`[Dialog] Auto-handle failed for ${browserId}:`, error.message);
|
|
186
|
+
}
|
|
187
|
+
}, 100); // Small delay to ensure dialog is fully loaded
|
|
188
|
+
}
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
client.Page.javascriptDialogClosed(() => {
|
|
192
|
+
const dialogState = this.dialogStates.get(browserId);
|
|
193
|
+
if (dialogState) {
|
|
194
|
+
dialogState.handled = true;
|
|
195
|
+
dialogState.closedAt = new Date().toISOString();
|
|
196
|
+
}
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Handle an active dialog
|
|
202
|
+
*/
|
|
203
|
+
async handleDialog(browserId, accept, promptText) {
|
|
204
|
+
const browser = browserService.getBrowserInstance(browserId);
|
|
205
|
+
if (!browser) {
|
|
206
|
+
throw new Error(`Browser instance '${browserId}' not found`);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
const dialogState = this.dialogStates.get(browserId);
|
|
210
|
+
if (!dialogState) {
|
|
211
|
+
throw new Error('No active dialog found');
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
if (dialogState.handled) {
|
|
215
|
+
throw new Error('Dialog has already been handled');
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
const client = browser.client;
|
|
219
|
+
|
|
220
|
+
try {
|
|
221
|
+
// Handle the dialog
|
|
222
|
+
await client.Page.handleJavaScriptDialog({
|
|
223
|
+
accept: accept,
|
|
224
|
+
promptText: dialogState.type === 'prompt' ? (promptText || '') : undefined
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// Update dialog state
|
|
228
|
+
dialogState.handled = true;
|
|
229
|
+
dialogState.response = accept ? 'accepted' : 'dismissed';
|
|
230
|
+
dialogState.promptText = promptText;
|
|
231
|
+
dialogState.handledAt = new Date().toISOString();
|
|
232
|
+
|
|
233
|
+
return dialogState;
|
|
234
|
+
} catch (error) {
|
|
235
|
+
throw new Error(`Failed to handle dialog: ${error.message}`);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Get information about current dialog state
|
|
241
|
+
*/
|
|
242
|
+
getDialogInfo(browserId) {
|
|
243
|
+
const dialogState = this.dialogStates.get(browserId);
|
|
244
|
+
const autoHandler = this.autoHandlers.get(browserId);
|
|
245
|
+
|
|
246
|
+
return {
|
|
247
|
+
dialog: dialogState || null,
|
|
248
|
+
autoHandling: autoHandler?.enabled || false,
|
|
249
|
+
autoConfig: autoHandler || null
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
/**
|
|
254
|
+
* Wait for a dialog to appear
|
|
255
|
+
*/
|
|
256
|
+
async waitForDialog(browserId, timeout = 10000) {
|
|
257
|
+
return new Promise((resolve, reject) => {
|
|
258
|
+
const timeoutId = setTimeout(() => {
|
|
259
|
+
reject(new Error(`Dialog wait timeout after ${timeout}ms`));
|
|
260
|
+
}, timeout);
|
|
261
|
+
|
|
262
|
+
const checkDialog = () => {
|
|
263
|
+
const dialogState = this.dialogStates.get(browserId);
|
|
264
|
+
if (dialogState && !dialogState.handled) {
|
|
265
|
+
clearTimeout(timeoutId);
|
|
266
|
+
resolve(dialogState);
|
|
267
|
+
} else {
|
|
268
|
+
setTimeout(checkDialog, 100);
|
|
269
|
+
}
|
|
270
|
+
};
|
|
271
|
+
|
|
272
|
+
checkDialog();
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Clear dialog state
|
|
278
|
+
*/
|
|
279
|
+
clearDialogState(browserId) {
|
|
280
|
+
this.dialogStates.delete(browserId);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Get dialog history
|
|
285
|
+
*/
|
|
286
|
+
getDialogHistory(browserId) {
|
|
287
|
+
// In a real implementation, you might store a history of dialogs
|
|
288
|
+
const currentDialog = this.dialogStates.get(browserId);
|
|
289
|
+
return currentDialog ? [currentDialog] : [];
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
/**
|
|
293
|
+
* Setup custom dialog responses for testing
|
|
294
|
+
*/
|
|
295
|
+
setupTestDialogResponses(browserId, responses) {
|
|
296
|
+
// Store custom responses for different dialog types
|
|
297
|
+
this.autoHandlers.set(browserId, {
|
|
298
|
+
enabled: true,
|
|
299
|
+
customResponses: responses
|
|
300
|
+
});
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Inject dialog trigger for testing
|
|
305
|
+
*/
|
|
306
|
+
async triggerTestDialog(client, type = 'alert', message = 'Test dialog') {
|
|
307
|
+
const expressions = {
|
|
308
|
+
alert: `alert('${message}')`,
|
|
309
|
+
confirm: `confirm('${message}')`,
|
|
310
|
+
prompt: `prompt('${message}', 'default value')`
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
return await client.Runtime.evaluate({
|
|
314
|
+
expression: expressions[type] || expressions.alert
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
module.exports = BrowserDialogTool;
|
|
@@ -0,0 +1,337 @@
|
|
|
1
|
+
const ToolBase = require('../../base/ToolBase');
|
|
2
|
+
const browserService = require('../../../services/browserService');
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Enhanced Evaluate Tool - Execute JavaScript in browser context
|
|
6
|
+
* Inspired by Playwright MCP evaluate capabilities
|
|
7
|
+
*/
|
|
8
|
+
class BrowserEvaluateTool extends ToolBase {
|
|
9
|
+
static definition = {
|
|
10
|
+
name: "browser_evaluate",
|
|
11
|
+
description: "Execute JavaScript code in the browser context. Can execute on page or specific elements. Returns execution results and handles errors gracefully.",
|
|
12
|
+
input_schema: {
|
|
13
|
+
type: "object",
|
|
14
|
+
properties: {
|
|
15
|
+
browserId: {
|
|
16
|
+
type: "string",
|
|
17
|
+
description: "The ID of the browser instance"
|
|
18
|
+
},
|
|
19
|
+
expression: {
|
|
20
|
+
type: "string",
|
|
21
|
+
description: "JavaScript expression or function to execute"
|
|
22
|
+
},
|
|
23
|
+
target: {
|
|
24
|
+
type: "object",
|
|
25
|
+
properties: {
|
|
26
|
+
selector: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "CSS selector of element to execute on (optional)"
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
description: "Target element for execution (if not provided, executes in page context)"
|
|
32
|
+
},
|
|
33
|
+
args: {
|
|
34
|
+
type: "array",
|
|
35
|
+
items: {},
|
|
36
|
+
description: "Arguments to pass to the function (for function expressions)"
|
|
37
|
+
},
|
|
38
|
+
returnByValue: {
|
|
39
|
+
type: "boolean",
|
|
40
|
+
default: true,
|
|
41
|
+
description: "Whether to return result by value or as object reference"
|
|
42
|
+
},
|
|
43
|
+
awaitPromise: {
|
|
44
|
+
type: "boolean",
|
|
45
|
+
default: true,
|
|
46
|
+
description: "Whether to await promises in the result"
|
|
47
|
+
},
|
|
48
|
+
timeout: {
|
|
49
|
+
type: "number",
|
|
50
|
+
default: 30000,
|
|
51
|
+
description: "Execution timeout in milliseconds"
|
|
52
|
+
}
|
|
53
|
+
},
|
|
54
|
+
required: ["browserId", "expression"]
|
|
55
|
+
},
|
|
56
|
+
output_schema: {
|
|
57
|
+
type: "object",
|
|
58
|
+
properties: {
|
|
59
|
+
success: { type: "boolean", description: "Whether the execution was successful" },
|
|
60
|
+
result: { description: "The result of the JavaScript execution" },
|
|
61
|
+
type: { type: "string", description: "Type of the returned result" },
|
|
62
|
+
error: { type: "string", description: "Error message if execution failed" },
|
|
63
|
+
executionTime: { type: "number", description: "Execution time in milliseconds" },
|
|
64
|
+
target: { type: "string", description: "CSS selector if targeting an element" },
|
|
65
|
+
browserId: { type: "string", description: "Browser instance ID" }
|
|
66
|
+
},
|
|
67
|
+
required: ["success", "browserId"]
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
|
|
71
|
+
async execute(parameters) {
|
|
72
|
+
const {
|
|
73
|
+
browserId,
|
|
74
|
+
expression,
|
|
75
|
+
target,
|
|
76
|
+
args = [],
|
|
77
|
+
returnByValue = true,
|
|
78
|
+
awaitPromise = true,
|
|
79
|
+
timeout = 30000
|
|
80
|
+
} = parameters;
|
|
81
|
+
|
|
82
|
+
const browser = browserService.getBrowserInstance(browserId);
|
|
83
|
+
if (!browser) {
|
|
84
|
+
throw new Error(`Browser instance '${browserId}' not found`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const client = browser.client;
|
|
88
|
+
const startTime = Date.now();
|
|
89
|
+
|
|
90
|
+
let result = {
|
|
91
|
+
success: false,
|
|
92
|
+
browserId: browserId,
|
|
93
|
+
executionTime: 0
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
try {
|
|
97
|
+
if (target?.selector) {
|
|
98
|
+
// Execute on specific element
|
|
99
|
+
result = await this.executeOnElement(client, expression, target.selector, args, returnByValue, awaitPromise, timeout);
|
|
100
|
+
result.target = target.selector;
|
|
101
|
+
} else {
|
|
102
|
+
// Execute in page context
|
|
103
|
+
result = await this.executeInPage(client, expression, args, returnByValue, awaitPromise, timeout);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
result.success = true;
|
|
107
|
+
|
|
108
|
+
} catch (error) {
|
|
109
|
+
result.success = false;
|
|
110
|
+
result.error = error.message;
|
|
111
|
+
|
|
112
|
+
// Try to extract more meaningful error information
|
|
113
|
+
if (error.exceptionDetails) {
|
|
114
|
+
result.error = error.exceptionDetails.text || error.exceptionDetails.exception?.description || result.error;
|
|
115
|
+
result.lineNumber = error.exceptionDetails.lineNumber;
|
|
116
|
+
result.columnNumber = error.exceptionDetails.columnNumber;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
result.executionTime = Date.now() - startTime;
|
|
121
|
+
result.browserId = browserId;
|
|
122
|
+
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* Execute JavaScript in page context
|
|
128
|
+
*/
|
|
129
|
+
async executeInPage(client, expression, args, returnByValue, awaitPromise, timeout) {
|
|
130
|
+
// Wrap expression in timeout handling
|
|
131
|
+
const wrappedExpression = this.wrapWithTimeout(expression, timeout);
|
|
132
|
+
|
|
133
|
+
const evalResult = await client.Runtime.evaluate({
|
|
134
|
+
expression: wrappedExpression,
|
|
135
|
+
returnByValue: returnByValue,
|
|
136
|
+
awaitPromise: awaitPromise,
|
|
137
|
+
timeout: timeout
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (evalResult.exceptionDetails) {
|
|
141
|
+
throw evalResult.exceptionDetails;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return this.formatResult(evalResult.result);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Execute JavaScript on a specific element
|
|
149
|
+
*/
|
|
150
|
+
async executeOnElement(client, expression, selector, args, returnByValue, awaitPromise, timeout) {
|
|
151
|
+
// First, get the element
|
|
152
|
+
const elementExpression = `
|
|
153
|
+
(() => {
|
|
154
|
+
const element = document.querySelector('${selector}');
|
|
155
|
+
if (!element) {
|
|
156
|
+
throw new Error('Element not found with selector: ${selector}');
|
|
157
|
+
}
|
|
158
|
+
return element;
|
|
159
|
+
})()
|
|
160
|
+
`;
|
|
161
|
+
|
|
162
|
+
const elementResult = await client.Runtime.evaluate({
|
|
163
|
+
expression: elementExpression,
|
|
164
|
+
returnByValue: false
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
if (elementResult.exceptionDetails) {
|
|
168
|
+
throw new Error(`Failed to find element: ${elementResult.exceptionDetails.text}`);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Now execute the expression on the element
|
|
172
|
+
const executeExpression = this.createElementExpression(expression, args);
|
|
173
|
+
const wrappedExpression = this.wrapWithTimeout(executeExpression, timeout);
|
|
174
|
+
|
|
175
|
+
const evalResult = await client.Runtime.callFunctionOn({
|
|
176
|
+
functionDeclaration: wrappedExpression,
|
|
177
|
+
objectId: elementResult.result.objectId,
|
|
178
|
+
arguments: args.map(arg => ({ value: arg })),
|
|
179
|
+
returnByValue: returnByValue,
|
|
180
|
+
awaitPromise: awaitPromise
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
if (evalResult.exceptionDetails) {
|
|
184
|
+
throw evalResult.exceptionDetails;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return this.formatResult(evalResult.result);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Create expression for element execution
|
|
192
|
+
*/
|
|
193
|
+
createElementExpression(expression, args) {
|
|
194
|
+
// If expression is a function, use it directly
|
|
195
|
+
if (expression.trim().startsWith('function') || expression.includes('=>')) {
|
|
196
|
+
return `(${expression}).apply(this, arguments)`;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
// If it's a simple expression, wrap it in a function
|
|
200
|
+
return `function() { return ${expression}; }`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Wrap expression with timeout handling
|
|
205
|
+
*/
|
|
206
|
+
wrapWithTimeout(expression, timeout) {
|
|
207
|
+
return `
|
|
208
|
+
(async () => {
|
|
209
|
+
const timeoutPromise = new Promise((_, reject) =>
|
|
210
|
+
setTimeout(() => reject(new Error('Execution timeout')), ${timeout})
|
|
211
|
+
);
|
|
212
|
+
|
|
213
|
+
const executionPromise = (async () => {
|
|
214
|
+
${expression.includes('return') ? expression : `return (${expression})`}
|
|
215
|
+
})();
|
|
216
|
+
|
|
217
|
+
return Promise.race([executionPromise, timeoutPromise]);
|
|
218
|
+
})()
|
|
219
|
+
`;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Format execution result
|
|
224
|
+
*/
|
|
225
|
+
formatResult(result) {
|
|
226
|
+
if (!result) {
|
|
227
|
+
return { result: undefined, type: 'undefined' };
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
let formattedResult = {
|
|
231
|
+
type: result.type
|
|
232
|
+
};
|
|
233
|
+
|
|
234
|
+
switch (result.type) {
|
|
235
|
+
case 'object':
|
|
236
|
+
if (result.subtype === 'null') {
|
|
237
|
+
formattedResult.result = null;
|
|
238
|
+
} else if (result.subtype === 'array') {
|
|
239
|
+
formattedResult.result = result.value || result.description;
|
|
240
|
+
formattedResult.subtype = 'array';
|
|
241
|
+
} else if (result.subtype === 'error') {
|
|
242
|
+
formattedResult.result = result.description;
|
|
243
|
+
formattedResult.subtype = 'error';
|
|
244
|
+
} else {
|
|
245
|
+
formattedResult.result = result.value || result.description;
|
|
246
|
+
}
|
|
247
|
+
break;
|
|
248
|
+
|
|
249
|
+
case 'function':
|
|
250
|
+
formattedResult.result = result.description;
|
|
251
|
+
break;
|
|
252
|
+
|
|
253
|
+
case 'undefined':
|
|
254
|
+
formattedResult.result = undefined;
|
|
255
|
+
break;
|
|
256
|
+
|
|
257
|
+
case 'string':
|
|
258
|
+
case 'number':
|
|
259
|
+
case 'boolean':
|
|
260
|
+
formattedResult.result = result.value;
|
|
261
|
+
break;
|
|
262
|
+
|
|
263
|
+
default:
|
|
264
|
+
formattedResult.result = result.value !== undefined ? result.value : result.description;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
return formattedResult;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Common JavaScript snippets for evaluation
|
|
272
|
+
*/
|
|
273
|
+
static getCommonSnippets() {
|
|
274
|
+
return {
|
|
275
|
+
getPageInfo: `
|
|
276
|
+
({
|
|
277
|
+
title: document.title,
|
|
278
|
+
url: window.location.href,
|
|
279
|
+
readyState: document.readyState,
|
|
280
|
+
elementCount: document.querySelectorAll('*').length,
|
|
281
|
+
viewport: {
|
|
282
|
+
width: window.innerWidth,
|
|
283
|
+
height: window.innerHeight
|
|
284
|
+
}
|
|
285
|
+
})
|
|
286
|
+
`,
|
|
287
|
+
|
|
288
|
+
getElementInfo: `
|
|
289
|
+
function(element) {
|
|
290
|
+
const rect = element.getBoundingClientRect();
|
|
291
|
+
return {
|
|
292
|
+
tagName: element.tagName,
|
|
293
|
+
id: element.id,
|
|
294
|
+
className: element.className,
|
|
295
|
+
textContent: element.textContent?.substring(0, 100),
|
|
296
|
+
bounds: {
|
|
297
|
+
x: rect.x,
|
|
298
|
+
y: rect.y,
|
|
299
|
+
width: rect.width,
|
|
300
|
+
height: rect.height
|
|
301
|
+
},
|
|
302
|
+
visible: rect.width > 0 && rect.height > 0,
|
|
303
|
+
attributes: Array.from(element.attributes).reduce((acc, attr) => {
|
|
304
|
+
acc[attr.name] = attr.value;
|
|
305
|
+
return acc;
|
|
306
|
+
}, {})
|
|
307
|
+
};
|
|
308
|
+
}
|
|
309
|
+
`,
|
|
310
|
+
|
|
311
|
+
scrollToElement: `
|
|
312
|
+
function(element) {
|
|
313
|
+
element.scrollIntoView({ behavior: 'smooth', block: 'center' });
|
|
314
|
+
return { scrolled: true, element: element.tagName };
|
|
315
|
+
}
|
|
316
|
+
`,
|
|
317
|
+
|
|
318
|
+
getFormData: `
|
|
319
|
+
() => {
|
|
320
|
+
const forms = Array.from(document.forms);
|
|
321
|
+
return forms.map(form => ({
|
|
322
|
+
action: form.action,
|
|
323
|
+
method: form.method,
|
|
324
|
+
fields: Array.from(form.elements).map(el => ({
|
|
325
|
+
name: el.name,
|
|
326
|
+
type: el.type,
|
|
327
|
+
value: el.value,
|
|
328
|
+
required: el.required
|
|
329
|
+
}))
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
332
|
+
`
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
module.exports = BrowserEvaluateTool;
|