@democratize-quality/mcp-server
Version:
MCP Server for democratizing quality through browser automation and comprehensive API testing capabilities
320 lines (282 loc) • 11.2 kB
JavaScript
const ToolBase = require('../../base/ToolBase');
const browserService = require('../../../services/browserService');
/**
* Dialog Tool - Handle browser dialogs (alert, confirm, prompt)
* Inspired by Playwright MCP dialog capabilities
*/
class BrowserDialogTool extends ToolBase {
static definition = {
name: "browser_dialog",
description: "Handle browser dialogs including alert, confirm, and prompt dialogs with automatic detection and response.",
input_schema: {
type: "object",
properties: {
browserId: {
type: "string",
description: "The ID of the browser instance"
},
action: {
type: "string",
enum: ["handle", "dismiss", "accept", "getInfo", "setupHandler"],
description: "Dialog action to perform"
},
accept: {
type: "boolean",
default: true,
description: "Whether to accept or dismiss the dialog (for handle action)"
},
promptText: {
type: "string",
description: "Text to enter in prompt dialogs"
},
autoHandle: {
type: "boolean",
default: false,
description: "Whether to automatically handle future dialogs (for setupHandler action)"
},
defaultResponse: {
type: "object",
properties: {
accept: { type: "boolean", description: "Default accept/dismiss behavior" },
promptText: { type: "string", description: "Default text for prompts" }
},
description: "Default responses for auto-handling dialogs"
}
},
required: ["browserId", "action"]
},
output_schema: {
type: "object",
properties: {
success: { type: "boolean", description: "Whether the operation was successful" },
action: { type: "string", description: "The action that was performed" },
dialog: {
type: "object",
properties: {
type: { type: "string" },
message: { type: "string" },
defaultValue: { type: "string" },
handled: { type: "boolean" },
response: { type: "string" }
},
description: "Dialog information"
},
autoHandling: { type: "boolean", description: "Whether auto-handling is enabled" },
browserId: { type: "string", description: "Browser instance ID" }
},
required: ["success", "action", "browserId"]
}
};
constructor() {
super();
this.dialogStates = new Map(); // browserId -> dialog state
this.autoHandlers = new Map(); // browserId -> auto-handler config
}
async execute(parameters) {
const {
browserId,
action,
accept = true,
promptText,
autoHandle = false,
defaultResponse = {}
} = parameters;
const browser = browserService.getBrowserInstance(browserId);
if (!browser) {
throw new Error(`Browser instance '${browserId}' not found`);
}
const client = browser.client;
let result = {
success: false,
action: action,
browserId: browserId
};
switch (action) {
case 'setupHandler':
await this.setupDialogHandler(client, browserId, autoHandle, defaultResponse);
result.success = true;
result.autoHandling = autoHandle;
result.message = autoHandle ? 'Auto dialog handling enabled' : 'Dialog monitoring enabled';
break;
case 'handle':
const handleResult = await this.handleDialog(browserId, accept, promptText);
result.success = true;
result.dialog = handleResult;
result.message = 'Dialog handled';
break;
case 'accept':
const acceptResult = await this.handleDialog(browserId, true, promptText);
result.success = true;
result.dialog = acceptResult;
result.message = 'Dialog accepted';
break;
case 'dismiss':
const dismissResult = await this.handleDialog(browserId, false);
result.success = true;
result.dialog = dismissResult;
result.message = 'Dialog dismissed';
break;
case 'getInfo':
const dialogInfo = this.getDialogInfo(browserId);
result.success = true;
result.dialog = dialogInfo.dialog;
result.autoHandling = dialogInfo.autoHandling;
break;
default:
throw new Error(`Unsupported dialog action: ${action}`);
}
return result;
}
/**
* Setup dialog handler with auto-handling capability
*/
async setupDialogHandler(client, browserId, autoHandle, defaultResponse) {
// Enable Page domain for dialog events
await client.Page.enable();
// Store auto-handler configuration
if (autoHandle) {
this.autoHandlers.set(browserId, {
enabled: true,
accept: defaultResponse.accept !== undefined ? defaultResponse.accept : true,
promptText: defaultResponse.promptText || ''
});
} else {
this.autoHandlers.delete(browserId);
}
// Set up dialog event listener
client.Page.javascriptDialogOpening((params) => {
const dialogInfo = {
type: params.type,
message: params.message,
defaultValue: params.defaultPrompt || '',
timestamp: new Date().toISOString(),
handled: false
};
this.dialogStates.set(browserId, dialogInfo);
// Auto-handle if configured
const autoHandler = this.autoHandlers.get(browserId);
if (autoHandler && autoHandler.enabled) {
setTimeout(async () => {
try {
await this.handleDialog(
browserId,
autoHandler.accept,
params.type === 'prompt' ? autoHandler.promptText : undefined
);
} catch (error) {
console.error(`[Dialog] Auto-handle failed for ${browserId}:`, error.message);
}
}, 100); // Small delay to ensure dialog is fully loaded
}
});
client.Page.javascriptDialogClosed(() => {
const dialogState = this.dialogStates.get(browserId);
if (dialogState) {
dialogState.handled = true;
dialogState.closedAt = new Date().toISOString();
}
});
}
/**
* Handle an active dialog
*/
async handleDialog(browserId, accept, promptText) {
const browser = browserService.getBrowserInstance(browserId);
if (!browser) {
throw new Error(`Browser instance '${browserId}' not found`);
}
const dialogState = this.dialogStates.get(browserId);
if (!dialogState) {
throw new Error('No active dialog found');
}
if (dialogState.handled) {
throw new Error('Dialog has already been handled');
}
const client = browser.client;
try {
// Handle the dialog
await client.Page.handleJavaScriptDialog({
accept: accept,
promptText: dialogState.type === 'prompt' ? (promptText || '') : undefined
});
// Update dialog state
dialogState.handled = true;
dialogState.response = accept ? 'accepted' : 'dismissed';
dialogState.promptText = promptText;
dialogState.handledAt = new Date().toISOString();
return dialogState;
} catch (error) {
throw new Error(`Failed to handle dialog: ${error.message}`);
}
}
/**
* Get information about current dialog state
*/
getDialogInfo(browserId) {
const dialogState = this.dialogStates.get(browserId);
const autoHandler = this.autoHandlers.get(browserId);
return {
dialog: dialogState || null,
autoHandling: autoHandler?.enabled || false,
autoConfig: autoHandler || null
};
}
/**
* Wait for a dialog to appear
*/
async waitForDialog(browserId, timeout = 10000) {
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => {
reject(new Error(`Dialog wait timeout after ${timeout}ms`));
}, timeout);
const checkDialog = () => {
const dialogState = this.dialogStates.get(browserId);
if (dialogState && !dialogState.handled) {
clearTimeout(timeoutId);
resolve(dialogState);
} else {
setTimeout(checkDialog, 100);
}
};
checkDialog();
});
}
/**
* Clear dialog state
*/
clearDialogState(browserId) {
this.dialogStates.delete(browserId);
}
/**
* Get dialog history
*/
getDialogHistory(browserId) {
// In a real implementation, you might store a history of dialogs
const currentDialog = this.dialogStates.get(browserId);
return currentDialog ? [currentDialog] : [];
}
/**
* Setup custom dialog responses for testing
*/
setupTestDialogResponses(browserId, responses) {
// Store custom responses for different dialog types
this.autoHandlers.set(browserId, {
enabled: true,
customResponses: responses
});
}
/**
* Inject dialog trigger for testing
*/
async triggerTestDialog(client, type = 'alert', message = 'Test dialog') {
const expressions = {
alert: `alert('${message}')`,
confirm: `confirm('${message}')`,
prompt: `prompt('${message}', 'default value')`
};
return await client.Runtime.evaluate({
expression: expressions[type] || expressions.alert
});
}
}
module.exports = BrowserDialogTool;