From 00dd7e01847d1e369a4fc535c6e041bba85eeb0e Mon Sep 17 00:00:00 2001 From: Lightning00Blade <34244704+Lightning00Blade@users.noreply.github.com> Date: Wed, 1 Apr 2026 16:22:34 +0000 Subject: [PATCH 1/5] feat: auto handle dialogs during script evaluation Added a new option `dialog` to `waitForEventsAfterAction` to automatically handle dialogs that trigger during execution. We hardcode it to `'accept'` for the `evaluate_script` tool so that `alert()` or `confirm()` don't block and timeout script evaluation. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com> --- src/McpPage.ts | 2 +- src/WaitForHelper.ts | 16 +++++++++++++++- src/tools/ToolDefinition.ts | 2 +- src/tools/script.ts | 4 ++-- tests/tools/script.test.ts | 23 +++++++++++++++++++++++ 5 files changed, 42 insertions(+), 5 deletions(-) diff --git a/src/McpPage.ts b/src/McpPage.ts index 36925f2ea..3ac6aeffa 100644 --- a/src/McpPage.ts +++ b/src/McpPage.ts @@ -117,7 +117,7 @@ export class McpPage implements ContextPage { waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number}, + options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, ): Promise { const helper = this.createWaitForHelper( this.cpuThrottlingRate, diff --git a/src/WaitForHelper.ts b/src/WaitForHelper.ts index 2dd0f48c1..c6e777cbd 100644 --- a/src/WaitForHelper.ts +++ b/src/WaitForHelper.ts @@ -126,8 +126,19 @@ export class WaitForHelper { async waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number}, + options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, ): Promise { + const dialogHandler = (dialog: any) => { + if (options?.dialog === 'dismiss') { + void dialog.dismiss(); + } else { + void dialog.accept(); + } + }; + if (options?.dialog) { + this.#page.on('dialog', dialogHandler); + } + const navigationFinished = this.waitForNavigationStarted() .then(navigationStated => { if (navigationStated) { @@ -158,6 +169,9 @@ export class WaitForHelper { logger(error); } finally { this.#abortController.abort(); + if (options?.dialog) { + this.#page.off('dialog', dialogHandler); + } } } } diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index 2aa640f7f..d4c93d321 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -247,7 +247,7 @@ export type ContextPage = Readonly<{ clearDialog(): void; waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number}, + options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, ): Promise; getInPageTools(): ToolGroup | undefined; }>; diff --git a/src/tools/script.ts b/src/tools/script.ts index 725628bcd..469dccfb0 100644 --- a/src/tools/script.ts +++ b/src/tools/script.ts @@ -81,7 +81,7 @@ Example with arguments: \`(el) => { .getSelectedMcpPage() .waitForEventsAfterAction(async () => { await performEvaluation(worker, fnString, [], response); - }); + }, {dialog: 'accept'}); return; } @@ -103,7 +103,7 @@ Example with arguments: \`(el) => { await mcpPage.waitForEventsAfterAction(async () => { await performEvaluation(evaluatable, fnString, args, response); - }); + }, {dialog: 'accept'}); } finally { void Promise.allSettled(args.map(arg => arg.dispose())); } diff --git a/tests/tools/script.test.ts b/tests/tools/script.test.ts index 772ebc076..a0dc4f694 100644 --- a/tests/tools/script.test.ts +++ b/tests/tools/script.test.ts @@ -98,6 +98,29 @@ describe('script', () => { }); }); + it('work for scripts that trigger dialogs', async () => { + await withMcpContext(async (response, context) => { + const page = context.getSelectedPptrPage(); + + await page.setContent(html``); + + await evaluateScript().handler( + { + params: { + function: String(() => { + alert('hello'); + return 'Works'; + }), + }, + }, + response, + context, + ); + const lineEvaluation = response.responseLines.at(2)!; + assert.strictEqual(JSON.parse(lineEvaluation), 'Works'); + }); + }); + it('work for async functions', async () => { await withMcpContext(async (response, context) => { const page = context.getSelectedPptrPage(); From 74309b98f64b8c7b5b1b0976317c1b7003060a39 Mon Sep 17 00:00:00 2001 From: Lightning00Blade <34244704+Lightning00Blade@users.noreply.github.com> Date: Wed, 1 Apr 2026 16:41:47 +0000 Subject: [PATCH 2/5] fix: move dialogHandler inside conditional block --- src/McpPage.ts | 2 +- src/WaitForHelper.ts | 20 ++++++++------------ src/tools/ToolDefinition.ts | 2 +- src/tools/script.ts | 18 +++++++++++------- 4 files changed, 21 insertions(+), 21 deletions(-) diff --git a/src/McpPage.ts b/src/McpPage.ts index 3ac6aeffa..c104feaad 100644 --- a/src/McpPage.ts +++ b/src/McpPage.ts @@ -117,7 +117,7 @@ export class McpPage implements ContextPage { waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, + options?: {timeout?: number; handleDialog?: boolean}, ): Promise { const helper = this.createWaitForHelper( this.cpuThrottlingRate, diff --git a/src/WaitForHelper.ts b/src/WaitForHelper.ts index c6e777cbd..5546b2155 100644 --- a/src/WaitForHelper.ts +++ b/src/WaitForHelper.ts @@ -5,7 +5,7 @@ */ import {logger} from './logger.js'; -import type {Page, Protocol, CdpPage} from './third_party/index.js'; +import type {Page, Protocol, CdpPage, Dialog} from './third_party/index.js'; import type {PredefinedNetworkConditions} from './third_party/index.js'; export class WaitForHelper { @@ -126,17 +126,16 @@ export class WaitForHelper { async waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, + options?: {timeout?: number; handleDialog?: boolean}, ): Promise { - const dialogHandler = (dialog: any) => { - if (options?.dialog === 'dismiss') { - void dialog.dismiss(); - } else { + if (options?.handleDialog) { + const dialogHandler = (dialog: Pick) => { void dialog.accept(); - } - }; - if (options?.dialog) { + }; this.#page.on('dialog', dialogHandler); + this.#abortController.signal.addEventListener('abort', () => { + this.#page.off('dialog', dialogHandler); + }); } const navigationFinished = this.waitForNavigationStarted() @@ -169,9 +168,6 @@ export class WaitForHelper { logger(error); } finally { this.#abortController.abort(); - if (options?.dialog) { - this.#page.off('dialog', dialogHandler); - } } } } diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index d4c93d321..583e1ef99 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -247,7 +247,7 @@ export type ContextPage = Readonly<{ clearDialog(): void; waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number; dialog?: 'accept' | 'dismiss'}, + options?: {timeout?: number; handleDialog?: boolean}, ): Promise; getInPageTools(): ToolGroup | undefined; }>; diff --git a/src/tools/script.ts b/src/tools/script.ts index 469dccfb0..6c6e87e5d 100644 --- a/src/tools/script.ts +++ b/src/tools/script.ts @@ -77,11 +77,12 @@ Example with arguments: \`(el) => { } const worker = await getWebWorker(context, serviceWorkerId); - await context - .getSelectedMcpPage() - .waitForEventsAfterAction(async () => { + await context.getSelectedMcpPage().waitForEventsAfterAction( + async () => { await performEvaluation(worker, fnString, [], response); - }, {dialog: 'accept'}); + }, + {handleDialog: true}, + ); return; } @@ -101,9 +102,12 @@ Example with arguments: \`(el) => { const evaluatable = await getPageOrFrame(page, frames); - await mcpPage.waitForEventsAfterAction(async () => { - await performEvaluation(evaluatable, fnString, args, response); - }, {dialog: 'accept'}); + await mcpPage.waitForEventsAfterAction( + async () => { + await performEvaluation(evaluatable, fnString, args, response); + }, + {handleDialog: true}, + ); } finally { void Promise.allSettled(args.map(arg => arg.dispose())); } From 2d035740dd9957203ee4cd142282e20a66f05f60 Mon Sep 17 00:00:00 2001 From: Nikolay Vitkov Date: Thu, 16 Apr 2026 12:02:42 +0200 Subject: [PATCH 3/5] Add handling --- src/McpPage.ts | 2 +- src/WaitForHelper.ts | 12 +++++++--- src/tools/ToolDefinition.ts | 2 +- src/tools/script.ts | 11 +++++++-- tests/tools/script.test.ts | 46 +++++++++++++++++++++++++++++++++++++ 5 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/McpPage.ts b/src/McpPage.ts index c104feaad..01f8473c2 100644 --- a/src/McpPage.ts +++ b/src/McpPage.ts @@ -117,7 +117,7 @@ export class McpPage implements ContextPage { waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number; handleDialog?: boolean}, + options?: {timeout?: number; handleDialog?: 'accept' | 'dismiss' | string}, ): Promise { const helper = this.createWaitForHelper( this.cpuThrottlingRate, diff --git a/src/WaitForHelper.ts b/src/WaitForHelper.ts index 5546b2155..f41ca84cc 100644 --- a/src/WaitForHelper.ts +++ b/src/WaitForHelper.ts @@ -126,11 +126,17 @@ export class WaitForHelper { async waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number; handleDialog?: boolean}, + options?: {timeout?: number; handleDialog?: 'accept' | 'dismiss' | string}, ): Promise { if (options?.handleDialog) { - const dialogHandler = (dialog: Pick) => { - void dialog.accept(); + const dialogHandler = (dialog: Pick) => { + if (options.handleDialog === 'dismiss') { + void dialog.dismiss(); + } else if (options.handleDialog === 'accept') { + void dialog.accept(); + } else { + void dialog.accept(options.handleDialog); + } }; this.#page.on('dialog', dialogHandler); this.#abortController.signal.addEventListener('abort', () => { diff --git a/src/tools/ToolDefinition.ts b/src/tools/ToolDefinition.ts index 583e1ef99..b9b4a6af0 100644 --- a/src/tools/ToolDefinition.ts +++ b/src/tools/ToolDefinition.ts @@ -247,7 +247,7 @@ export type ContextPage = Readonly<{ clearDialog(): void; waitForEventsAfterAction( action: () => Promise, - options?: {timeout?: number; handleDialog?: boolean}, + options?: {timeout?: number; handleDialog?: 'accept' | 'dismiss' | string}, ): Promise; getInPageTools(): ToolGroup | undefined; }>; diff --git a/src/tools/script.ts b/src/tools/script.ts index 6c6e87e5d..c4c65f34f 100644 --- a/src/tools/script.ts +++ b/src/tools/script.ts @@ -46,6 +46,12 @@ Example with arguments: \`(el) => { ) .optional() .describe(`An optional list of arguments to pass to the function.`), + dialogAction: zod + .string() + .optional() + .describe( + 'Handle dialogs while execution. "accept", "dismiss", or a string for window.prompt. Defaults to accept.', + ), ...(cliArgs?.experimentalPageIdRouting ? pageIdSchema : {}), ...(cliArgs?.categoryExtensions ? { @@ -64,6 +70,7 @@ Example with arguments: \`(el) => { args: uidArgs, function: fnString, pageId, + dialogAction, } = request.params; if (cliArgs?.categoryExtensions && serviceWorkerId) { @@ -81,7 +88,7 @@ Example with arguments: \`(el) => { async () => { await performEvaluation(worker, fnString, [], response); }, - {handleDialog: true}, + {handleDialog: dialogAction ?? 'accept'}, ); return; } @@ -106,7 +113,7 @@ Example with arguments: \`(el) => { async () => { await performEvaluation(evaluatable, fnString, args, response); }, - {handleDialog: true}, + {handleDialog: dialogAction ?? 'accept'}, ); } finally { void Promise.allSettled(args.map(arg => arg.dispose())); diff --git a/tests/tools/script.test.ts b/tests/tools/script.test.ts index a0dc4f694..5057f8a09 100644 --- a/tests/tools/script.test.ts +++ b/tests/tools/script.test.ts @@ -121,6 +121,52 @@ describe('script', () => { }); }); + it('work for scripts that trigger dialogs and dismiss them', async () => { + await withMcpContext(async (response, context) => { + const page = context.getSelectedPptrPage(); + + await page.setContent(html``); + + await evaluateScript().handler( + { + params: { + function: String(() => { + return confirm('hello'); + }), + dialogAction: 'dismiss', + }, + }, + response, + context, + ); + const lineEvaluation = response.responseLines.at(2)!; + assert.strictEqual(JSON.parse(lineEvaluation), false); + }); + }); + + it('work for scripts that trigger prompts and fill them', async () => { + await withMcpContext(async (response, context) => { + const page = context.getSelectedPptrPage(); + + await page.setContent(html``); + + await evaluateScript().handler( + { + params: { + function: String(() => { + return prompt('Enter your name:'); + }), + dialogAction: 'John Doe', + }, + }, + response, + context, + ); + const lineEvaluation = response.responseLines.at(2)!; + assert.strictEqual(JSON.parse(lineEvaluation), 'John Doe'); + }); + }); + it('work for async functions', async () => { await withMcpContext(async (response, context) => { const page = context.getSelectedPptrPage(); From 4b037742edeede5dba18e0ce3772518685da3a77 Mon Sep 17 00:00:00 2001 From: Nikolay Vitkov Date: Thu, 16 Apr 2026 12:06:40 +0200 Subject: [PATCH 4/5] fix --- src/tools/script.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/tools/script.ts b/src/tools/script.ts index c4c65f34f..337ad2e86 100644 --- a/src/tools/script.ts +++ b/src/tools/script.ts @@ -50,7 +50,7 @@ Example with arguments: \`(el) => { .string() .optional() .describe( - 'Handle dialogs while execution. "accept", "dismiss", or a string for window.prompt. Defaults to accept.', + 'Handle dialogs while execution. "accept", "dismiss", or string for response of window.prompt. Defaults to accept.', ), ...(cliArgs?.experimentalPageIdRouting ? pageIdSchema : {}), ...(cliArgs?.categoryExtensions From bfecb6c424e54584095b78f35bac8b2a097d0ef0 Mon Sep 17 00:00:00 2001 From: Nikolay Vitkov Date: Mon, 20 Apr 2026 08:34:32 +0200 Subject: [PATCH 5/5] update docs --- docs/tool-reference.md | 3 ++- src/bin/cliDefinitions.ts | 7 +++++++ src/telemetry/tool_call_metrics.json | 4 ++++ 3 files changed, 13 insertions(+), 1 deletion(-) diff --git a/docs/tool-reference.md b/docs/tool-reference.md index 366c21ea4..9b88c8f1b 100644 --- a/docs/tool-reference.md +++ b/docs/tool-reference.md @@ -1,6 +1,6 @@ -# Chrome DevTools MCP Tool Reference (~6962 cl100k_base tokens) +# Chrome DevTools MCP Tool Reference (~7005 cl100k_base tokens) - **[Input automation](#input-automation)** (9 tools) - [`click`](#click) @@ -333,6 +333,7 @@ so returned values have to be JSON-serializable. }` - **args** (array) _(optional)_: An optional list of arguments to pass to the function. +- **dialogAction** (string) _(optional)_: Handle dialogs while execution. "accept", "dismiss", or string for response of window.prompt. Defaults to accept. --- diff --git a/src/bin/cliDefinitions.ts b/src/bin/cliDefinitions.ts index aef789af3..abf934034 100644 --- a/src/bin/cliDefinitions.ts +++ b/src/bin/cliDefinitions.ts @@ -155,6 +155,13 @@ export const commands: Commands = { description: 'An optional list of arguments to pass to the function.', required: false, }, + dialogAction: { + name: 'dialogAction', + type: 'string', + description: + 'Handle dialogs while execution. "accept", "dismiss", or string for response of window.prompt. Defaults to accept.', + required: false, + }, }, }, fill: { diff --git a/src/telemetry/tool_call_metrics.json b/src/telemetry/tool_call_metrics.json index a09d781dd..c09d46990 100644 --- a/src/telemetry/tool_call_metrics.json +++ b/src/telemetry/tool_call_metrics.json @@ -107,6 +107,10 @@ { "name": "args_count", "argType": "number" + }, + { + "name": "dialog_action_length", + "argType": "number" } ] },