diff --git a/src/tab.ts b/src/tab.ts index 177c345..5aac014 100644 --- a/src/tab.ts +++ b/src/tab.ts @@ -66,7 +66,26 @@ export class Tab { } async navigate(url: string) { - await this.page.goto(url, { waitUntil: 'domcontentloaded' }); + const downloadEvent = this.page.waitForEvent('download').catch(() => {}); + try { + await this.page.goto(url, { waitUntil: 'domcontentloaded' }); + } catch (_e: unknown) { + const e = _e as Error; + const mightBeDownload = + e.message.includes('net::ERR_ABORTED') // chromium + || e.message.includes('Download is starting'); // firefox + webkit + if (!mightBeDownload) + throw e; + + // on chromium, the download event is fired *after* page.goto rejects, so we wait a lil bit + const download = await Promise.race([ + downloadEvent, + new Promise(resolve => setTimeout(resolve, 500)), + ]); + if (!download) + throw e; + } + // Cap load event to 5 seconds, the page is operational at this point. await this.page.waitForLoadState('load', { timeout: 5000 }).catch(() => {}); } diff --git a/tests/files.spec.ts b/tests/files.spec.ts index 1f168f6..10fd96c 100644 --- a/tests/files.spec.ts +++ b/tests/files.spec.ts @@ -119,3 +119,21 @@ test('clicking on download link emits download', async ({ startClient }, testInf ### Downloads - Downloaded file test.txt to ${path.join(outputDir, 'test.txt')}`); }); + +test('navigating to download link emits download', async ({ client, server, mcpBrowser }) => { + test.skip(mcpBrowser === 'webkit' && process.platform === 'linux', 'https://github.com/microsoft/playwright/blob/8e08fdb52c27bb75de9bf87627bf740fadab2122/tests/library/download.spec.ts#L436'); + server.route('/download', (req, res) => { + res.writeHead(200, { + 'Content-Type': 'text/plain', + 'Content-Disposition': 'attachment; filename=test.txt', + }); + res.end('Hello world!'); + }); + + expect(await client.callTool({ + name: 'browser_navigate', + arguments: { + url: server.PREFIX + '/download', + }, + })).toContainTextContent('### Downloads'); +});