Skip to content
Closed
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
14 changes: 13 additions & 1 deletion packages/playwright-core/src/tools/backend/tab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,8 +348,16 @@ export class Tab extends EventEmitter<TabEventsInterface> {
this._clearCollectedArtifacts();

const { promise: downloadEvent, abort: abortDownloadEvent } = eventWaiter<playwright.Download>(this.page, 'download', 3000);
// A dialog that opens during the load blocks it, so report the dialog
// right away instead of waiting for the navigation timeout.
const modalStatePromise = new ManualPromise<void>();
const modalStateListener = () => modalStatePromise.resolve();
this.once(TabEvents.modalState, modalStateListener);
try {
await this.page.goto(url, { waitUntil: 'domcontentloaded', ...this.navigationTimeoutOptions });
await Promise.race([
this.page.goto(url, { waitUntil: 'domcontentloaded', ...this.navigationTimeoutOptions }),
modalStatePromise,
]);
abortDownloadEvent();
} catch (_e: unknown) {
const e = _e as Error;
Expand All @@ -363,7 +371,11 @@ export class Tab extends EventEmitter<TabEventsInterface> {
// Make sure other "download" listeners are notified first.
await new Promise(resolve => setTimeout(resolve, 500));
return;
} finally {
this.off(TabEvents.modalState, modalStateListener);
}
if (modalStatePromise.isDone())
return;

// Cap load event to 5 seconds, the page is operational at this point.
await this.waitForLoadState('load', { timeout: 5000 });
Expand Down
26 changes: 26 additions & 0 deletions tests/mcp/dialogs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -293,3 +293,29 @@ test('alert dialog w/ race', async ({ client, server }) => {
- Page Title: Title`),
});
});

test('alert dialog during navigation', async ({ client, server }) => {
server.setContent('/', `<title>Title</title><script>alert('Alert')</script><button>Button</button>`, 'text/html');
expect(await client.callTool({
name: 'browser_navigate',
arguments: { url: server.PREFIX },
})).toHaveResponse({
modalState: expect.stringContaining(`- ["alert" dialog with message "Alert"]: can be handled by browser_handle_dialog`),
});

expect(await client.callTool({
name: 'browser_handle_dialog',
arguments: { accept: true },
})).toHaveResponse({
modalState: undefined,
page: expect.stringContaining(`- Page URL: ${server.PREFIX}/
- Page Title: Title`),
});

expect(await client.callTool({
name: 'browser_snapshot',
arguments: {},
})).toHaveResponse({
inlineSnapshot: expect.stringContaining(`- button "Button"`),
});
});