diff --git a/packages/playwright-core/src/tools/backend/tab.ts b/packages/playwright-core/src/tools/backend/tab.ts index e409fa344d07c..03e99783ea1ff 100644 --- a/packages/playwright-core/src/tools/backend/tab.ts +++ b/packages/playwright-core/src/tools/backend/tab.ts @@ -115,6 +115,7 @@ export class Tab extends EventEmitter { eventsHelper.addEventListener(p, 'response', response => this._handleResponse(response)), eventsHelper.addEventListener(p, 'requestfailed', request => this._handleRequestFailed(request)), eventsHelper.addEventListener(p, 'close', () => this._onClose()), + eventsHelper.addEventListener(p, 'crash', () => this._onClose()), eventsHelper.addEventListener(p, 'filechooser', chooser => { this.setModalState({ type: 'fileChooser', diff --git a/tests/mcp/tabs.spec.ts b/tests/mcp/tabs.spec.ts index 3743dd1f25fea..c7f6a04dfc681 100644 --- a/tests/mcp/tabs.spec.ts +++ b/tests/mcp/tabs.spec.ts @@ -149,3 +149,35 @@ test('reuse first tab when navigating', async ({ startClient, cdpServer, server expect(pages.length).toBe(1); expect(await pages[0].title()).toBe('Title'); }); + +test('crashed tab is removed from tabs list and server recovers', async ({ client, mcpBrowser }) => { + test.skip(mcpBrowser !== 'chromium', 'Crash via chrome://crash is chromium-only'); + + // Open a second tab alongside the initial one + await client.callTool({ + name: 'browser_tabs', + arguments: { action: 'new' }, + }); + + // Crash the second tab; navigation to chrome://crash throws, that's expected + await client.callTool({ + name: 'browser_navigate', + arguments: { url: 'chrome://crash' }, + }).catch(() => {}); + + // Crashed tab must be gone — only the initial tab (index 0) should remain + expect(await client.callTool({ + name: 'browser_tabs', + arguments: { action: 'list' }, + })).toHaveResponse({ + result: `- 0: (current) [](about:blank)`, + }); + + // Server must recover: subsequent navigation must work without hanging + expect(await client.callTool({ + name: 'browser_navigate', + arguments: { url: 'data:text/html,recovered' }, + })).toHaveResponse({ + snapshot: expect.stringContaining('recovered'), + }); +});