@democratize-quality/mcp-server 1.2.0 → 1.2.1

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