feat: auto handle dialogs during script evaluation (#1839)
Provide a argument to LLM to handle dialogs that come up during code execution. --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit is contained in:
@@ -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)
|
||||
@@ -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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
+1
-1
@@ -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,
|
||||
|
||||
+18
-2
@@ -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,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) {
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -107,6 +107,10 @@
|
||||
{
|
||||
"name": "args_count",
|
||||
"argType": "number"
|
||||
},
|
||||
{
|
||||
"name": "dialog_action_length",
|
||||
"argType": "number"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -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;
|
||||
}>;
|
||||
|
||||
+18
-7
@@ -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
|
||||
? {
|
||||
@@ -64,6 +70,7 @@ Example with arguments: \`(el) => {
|
||||
args: uidArgs,
|
||||
function: fnString,
|
||||
pageId,
|
||||
dialogAction,
|
||||
} = request.params;
|
||||
|
||||
if (cliArgs?.categoryExtensions && serviceWorkerId) {
|
||||
@@ -77,11 +84,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);
|
||||
});
|
||||
},
|
||||
{handleDialog: dialogAction ?? 'accept'},
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -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()));
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user