Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion docs/tool-reference.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<!-- AUTO GENERATED DO NOT EDIT - run 'npm run gen' to update-->

# 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)
Expand Down Expand Up @@ -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.

---

Expand Down
2 changes: 1 addition & 1 deletion src/McpPage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ export class McpPage implements ContextPage {

waitForEventsAfterAction(
action: () => Promise<unknown>,
options?: {timeout?: number},
options?: {timeout?: number; handleDialog?: 'accept' | 'dismiss' | string},
): Promise<void> {
const helper = this.createWaitForHelper(
this.cpuThrottlingRate,
Expand Down
20 changes: 18 additions & 2 deletions src/WaitForHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -126,8 +126,24 @@ export class WaitForHelper {

async waitForEventsAfterAction(
action: () => Promise<unknown>,
options?: {timeout?: number},
options?: {timeout?: number; handleDialog?: 'accept' | 'dismiss' | string},
): Promise<void> {
if (options?.handleDialog) {
const dialogHandler = (dialog: Pick<Dialog, 'accept' | 'dismiss'>) => {
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', () => {
this.#page.off('dialog', dialogHandler);
});
}

const navigationFinished = this.waitForNavigationStarted()
.then(navigationStated => {
if (navigationStated) {
Expand Down
7 changes: 7 additions & 0 deletions src/bin/cliDefinitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand Down
4 changes: 4 additions & 0 deletions src/telemetry/tool_call_metrics.json
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@
{
"name": "args_count",
"argType": "number"
},
{
"name": "dialog_action_length",
"argType": "number"
}
]
},
Expand Down
2 changes: 1 addition & 1 deletion src/tools/ToolDefinition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ export type ContextPage = Readonly<{
clearDialog(): void;
waitForEventsAfterAction(
action: () => Promise<unknown>,
options?: {timeout?: number},
options?: {timeout?: number; handleDialog?: 'accept' | 'dismiss' | string},
): Promise<void>;
getInPageTools(): ToolGroup<InPageToolDefinition> | undefined;
}>;
Expand Down
25 changes: 18 additions & 7 deletions src/tools/script.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 string for response of window.prompt. Defaults to accept.',
),
...(cliArgs?.experimentalPageIdRouting ? pageIdSchema : {}),
...(cliArgs?.categoryExtensions
? {
Expand All @@ -64,6 +70,7 @@ Example with arguments: \`(el) => {
args: uidArgs,
function: fnString,
pageId,
dialogAction,
} = request.params;

if (cliArgs?.categoryExtensions && serviceWorkerId) {
Expand All @@ -77,11 +84,12 @@ Example with arguments: \`(el) => {
}

const worker = await getWebWorker(context, serviceWorkerId);
await context
.getSelectedMcpPage()
.waitForEventsAfterAction(async () => {
await context.getSelectedMcpPage().waitForEventsAfterAction(
Comment thread
Lightning00Blade marked this conversation as resolved.
async () => {
await performEvaluation(worker, fnString, [], response);
});
},
{handleDialog: dialogAction ?? 'accept'},
);
return;
}

Expand All @@ -101,9 +109,12 @@ Example with arguments: \`(el) => {

const evaluatable = await getPageOrFrame(page, frames);

await mcpPage.waitForEventsAfterAction(async () => {
await performEvaluation(evaluatable, fnString, args, response);
});
await mcpPage.waitForEventsAfterAction(
async () => {
await performEvaluation(evaluatable, fnString, args, response);
},
{handleDialog: dialogAction ?? 'accept'},
);
} finally {
void Promise.allSettled(args.map(arg => arg.dispose()));
}
Expand Down
69 changes: 69 additions & 0 deletions tests/tools/script.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,75 @@ describe('script', () => {
});
});

it('work for scripts that trigger dialogs', async () => {
await withMcpContext(async (response, context) => {
const page = context.getSelectedPptrPage();

await page.setContent(html`<button id="test">test</button>`);

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 scripts that trigger dialogs and dismiss them', async () => {
await withMcpContext(async (response, context) => {
const page = context.getSelectedPptrPage();

await page.setContent(html`<button id="test">test</button>`);

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`<button id="test">test</button>`);

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();
Expand Down
Loading