diff --git a/src/context.ts b/src/context.ts
index 2b1e0d8..c7a0717 100644
--- a/src/context.ts
+++ b/src/context.ts
@@ -22,7 +22,6 @@ import { ManualPromise } from './manualPromise.js';
import { Tab } from './tab.js';
import { outputFile } from './config.js';
-import type { ImageContent, TextContent } from '@modelcontextprotocol/sdk/types.js';
import type { ModalState, Tool, ToolActionResult } from './tools/tool.js';
import type { FullConfig } from './config.js';
import type { BrowserContextFactory } from './browserContextFactory.js';
@@ -136,7 +135,6 @@ export class Context {
// Tab management is done outside of the action() call.
const toolResult = await tool.handle(this, tool.schema.inputSchema.parse(params || {}));
const { code, action, waitForNetwork, captureSnapshot, resultOverride } = toolResult;
- const racingAction = action ? () => this._raceAgainstModalDialogs(action) : undefined;
if (resultOverride)
return resultOverride;
@@ -152,16 +150,17 @@ export class Context {
const tab = this.currentTabOrDie();
// TODO: race against modal dialogs to resolve clicks.
- let actionResult: { content?: (ImageContent | TextContent)[] } | undefined;
- try {
- if (waitForNetwork)
- actionResult = await waitForCompletion(this, tab, async () => racingAction?.()) ?? undefined;
- else
- actionResult = await racingAction?.() ?? undefined;
- } finally {
- if (captureSnapshot && !this._javaScriptBlocked())
- await tab.captureSnapshot();
- }
+ const actionResult = await this._raceAgainstModalDialogs(async () => {
+ try {
+ if (waitForNetwork)
+ return await waitForCompletion(this, tab, async () => action?.()) ?? undefined;
+ else
+ return await action?.() ?? undefined;
+ } finally {
+ if (captureSnapshot && !this._javaScriptBlocked())
+ await tab.captureSnapshot();
+ }
+ });
const result: string[] = [];
result.push(`- Ran Playwright code:
diff --git a/tests/dialogs.spec.ts b/tests/dialogs.spec.ts
index b89613e..ecf7d04 100644
--- a/tests/dialogs.spec.ts
+++ b/tests/dialogs.spec.ts
@@ -16,9 +16,6 @@
import { test, expect } from './fixtures.js';
-// https://github.com/microsoft/playwright/issues/35663
-test.skip(({ mcpBrowser, mcpHeadless }) => mcpBrowser === 'webkit' && mcpHeadless);
-
test('alert dialog', async ({ client, server }) => {
server.setContent('/', ``, 'text/html');
expect(await client.callTool({
@@ -49,7 +46,7 @@ await page.getByRole('button', { name: 'Button' }).click();
});
expect(result).not.toContainTextContent('### Modal state');
- expect(result).toHaveTextContent(`- Ran Playwright code:
+ expect(result).toContainTextContent(`- Ran Playwright code:
\`\`\`js
//
\`\`\`
@@ -58,14 +55,10 @@ await page.getByRole('button', { name: 'Button' }).click();
- Page Title:
- Page Snapshot
\`\`\`yaml
-- button "Button" [active] [ref=e2]
-\`\`\`
-`);
+- button "Button"`);
});
test('two alert dialogs', async ({ client, server }) => {
- test.fixme(true, 'Race between the dialog and ariaSnapshot');
-
server.setContent('/', `
Title
@@ -100,7 +93,18 @@ await page.getByRole('button', { name: 'Button' }).click();
},
});
- expect(result).not.toContainTextContent('### Modal state');
+ expect(result).toContainTextContent(`
+### Modal state
+- ["alert" dialog with message "Alert 2"]: can be handled by the "browser_handle_dialog" tool`);
+
+ const result2 = await client.callTool({
+ name: 'browser_handle_dialog',
+ arguments: {
+ accept: true,
+ },
+ });
+
+ expect(result2).not.toContainTextContent('### Modal state');
});
test('confirm dialog (true)', async ({ client, server }) => {
@@ -210,3 +214,45 @@ test('prompt dialog', async ({ client, server }) => {
- generic [active] [ref=e1]: Answer
\`\`\``);
});
+
+test('alert dialog w/ race', async ({ client, server }) => {
+ server.setContent('/', ``, 'text/html');
+ expect(await client.callTool({
+ name: 'browser_navigate',
+ arguments: { url: server.PREFIX },
+ })).toContainTextContent('- button "Button" [ref=e2]');
+
+ expect(await client.callTool({
+ name: 'browser_click',
+ arguments: {
+ element: 'Button',
+ ref: 'e2',
+ },
+ })).toHaveTextContent(`- Ran Playwright code:
+\`\`\`js
+// Click Button
+await page.getByRole('button', { name: 'Button' }).click();
+\`\`\`
+
+### Modal state
+- ["alert" dialog with message "Alert"]: can be handled by the "browser_handle_dialog" tool`);
+
+ const result = await client.callTool({
+ name: 'browser_handle_dialog',
+ arguments: {
+ accept: true,
+ },
+ });
+
+ expect(result).not.toContainTextContent('### Modal state');
+ expect(result).toContainTextContent(`- Ran Playwright code:
+\`\`\`js
+//
+\`\`\`
+
+- Page URL: ${server.PREFIX}
+- Page Title:
+- Page Snapshot
+\`\`\`yaml
+- button "Button"`);
+});