diff --git a/config.d.ts b/config.d.ts index add644f..350c1ca 100644 --- a/config.d.ts +++ b/config.d.ts @@ -143,4 +143,11 @@ export type Config = { * Default is false. */ differentialSnapshots?: boolean; + + /** + * File path to write browser console output to. When specified, all console + * messages from browser pages will be written to this file in real-time. + * Useful for debugging and monitoring browser activity. + */ + consoleOutputFile?: string; }; diff --git a/src/config.ts b/src/config.ts index d2e9b2d..da46597 100644 --- a/src/config.ts +++ b/src/config.ts @@ -31,6 +31,7 @@ export type CLIOptions = { caps?: string[]; cdpEndpoint?: string; config?: string; + consoleOutputFile?: string; device?: string; executablePath?: string; headless?: boolean; @@ -93,6 +94,7 @@ export type FullConfig = Config & { includeSnapshots: boolean; maxSnapshotTokens: number; differentialSnapshots: boolean; + consoleOutputFile?: string; }; export async function resolveConfig(config: Config): Promise { @@ -212,6 +214,7 @@ export function configFromCLIOptions(cliOptions: CLIOptions): Config { includeSnapshots: cliOptions.includeSnapshots, maxSnapshotTokens: cliOptions.maxSnapshotTokens, differentialSnapshots: cliOptions.differentialSnapshots, + consoleOutputFile: cliOptions.consoleOutputFile, }; return result; @@ -238,6 +241,7 @@ function configFromEnv(): Config { options.includeSnapshots = envToBoolean(process.env.PLAYWRIGHT_MCP_INCLUDE_SNAPSHOTS); options.maxSnapshotTokens = envToNumber(process.env.PLAYWRIGHT_MCP_MAX_SNAPSHOT_TOKENS); options.differentialSnapshots = envToBoolean(process.env.PLAYWRIGHT_MCP_DIFFERENTIAL_SNAPSHOTS); + options.consoleOutputFile = envToString(process.env.PLAYWRIGHT_MCP_CONSOLE_OUTPUT_FILE); options.sandbox = envToBoolean(process.env.PLAYWRIGHT_MCP_SANDBOX); options.outputDir = envToString(process.env.PLAYWRIGHT_MCP_OUTPUT_DIR); options.port = envToNumber(process.env.PLAYWRIGHT_MCP_PORT); diff --git a/src/context.ts b/src/context.ts index b2a2647..400208a 100644 --- a/src/context.ts +++ b/src/context.ts @@ -641,6 +641,7 @@ export class Context { includeSnapshots?: boolean; maxSnapshotTokens?: number; differentialSnapshots?: boolean; + consoleOutputFile?: string; }): void { // Update configuration at runtime if (updates.includeSnapshots !== undefined) @@ -659,5 +660,9 @@ export class Context { this.resetDifferentialSnapshot(); } + + if (updates.consoleOutputFile !== undefined) + (this.config as any).consoleOutputFile = updates.consoleOutputFile === '' ? undefined : updates.consoleOutputFile; + } } diff --git a/src/program.ts b/src/program.ts index 0634ae3..452d09f 100644 --- a/src/program.ts +++ b/src/program.ts @@ -38,6 +38,7 @@ program .option('--caps ', 'comma-separated list of additional capabilities to enable, possible values: vision, pdf.', commaSeparatedList) .option('--cdp-endpoint ', 'CDP endpoint to connect to.') .option('--config ', 'path to the configuration file.') + .option('--console-output-file ', 'file path to write browser console output to for debugging and monitoring.') .option('--device ', 'device to emulate, for example: "iPhone 15"') .option('--executable-path ', 'path to the browser executable.') .option('--headless', 'run browser in headless mode, headed by default') diff --git a/src/tab.ts b/src/tab.ts index ca3e101..871e23d 100644 --- a/src/tab.ts +++ b/src/tab.ts @@ -15,6 +15,8 @@ */ import { EventEmitter } from 'events'; +import fs from 'fs'; +import path from 'path'; import * as playwright from 'playwright'; import { callOnPageNoTrace, waitForCompletion } from './tools/utils.js'; import { logUnhandledError } from './log.js'; @@ -123,6 +125,39 @@ export class Tab extends EventEmitter { private _handleConsoleMessage(message: ConsoleMessage) { this._consoleMessages.push(message); this._recentConsoleMessages.push(message); + + // Write to console output file if configured + if (this.context.config.consoleOutputFile) + this._writeConsoleToFile(message); + + } + + private _writeConsoleToFile(message: ConsoleMessage) { + try { + const consoleFile = this.context.config.consoleOutputFile!; + const timestamp = new Date().toISOString(); + const url = this.page.url(); + const sessionId = this.context.sessionId; + + const logEntry = `[${timestamp}] [${sessionId}] [${url}] ${message.toString()}\n`; + + // Ensure directory exists + const dir = path.dirname(consoleFile); + if (!fs.existsSync(dir)) + fs.mkdirSync(dir, { recursive: true }); + + + // Append to file (async to avoid blocking) + fs.appendFile(consoleFile, logEntry, err => { + if (err) { + // Log error but don't fail the operation + logUnhandledError(err); + } + }); + } catch (error) { + // Silently handle errors to avoid breaking browser functionality + logUnhandledError(error); + } } private _onClose() { diff --git a/src/tools/configure.ts b/src/tools/configure.ts index e925f02..4f9c51b 100644 --- a/src/tools/configure.ts +++ b/src/tools/configure.ts @@ -77,7 +77,8 @@ const installPopularExtensionSchema = z.object({ const configureSnapshotsSchema = z.object({ includeSnapshots: z.boolean().optional().describe('Enable/disable automatic snapshots after interactive operations. When false, use browser_snapshot for explicit snapshots.'), maxSnapshotTokens: z.number().min(0).optional().describe('Maximum tokens allowed in snapshots before truncation. Use 0 to disable truncation.'), - differentialSnapshots: z.boolean().optional().describe('Enable differential snapshots that show only changes since last snapshot instead of full page snapshots.') + differentialSnapshots: z.boolean().optional().describe('Enable differential snapshots that show only changes since last snapshot instead of full page snapshots.'), + consoleOutputFile: z.string().optional().describe('File path to write browser console output to. Set to empty string to disable console file output.') }); export default [ @@ -564,6 +565,14 @@ export default [ } + if (params.consoleOutputFile !== undefined) { + if (params.consoleOutputFile === '') + changes.push(`📝 Console output file: disabled`); + else + changes.push(`📝 Console output file: ${params.consoleOutputFile}`); + + } + // Apply the updated configuration using the context method context.updateSnapshotConfig(params); @@ -572,7 +581,8 @@ export default [ response.addResult('No snapshot configuration changes specified.\n\n**Current settings:**\n' + `📸 Auto-snapshots: ${context.config.includeSnapshots ? 'enabled' : 'disabled'}\n` + `📏 Max snapshot tokens: ${context.config.maxSnapshotTokens === 0 ? 'unlimited' : context.config.maxSnapshotTokens.toLocaleString()}\n` + - `🔄 Differential snapshots: ${context.config.differentialSnapshots ? 'enabled' : 'disabled'}`); + `🔄 Differential snapshots: ${context.config.differentialSnapshots ? 'enabled' : 'disabled'}\n` + + `📝 Console output file: ${context.config.consoleOutputFile || 'disabled'}`); return; }