diff --git a/packages/playwright-core/src/client/browser.ts b/packages/playwright-core/src/client/browser.ts index 19df307510..8ded087173 100644 --- a/packages/playwright-core/src/client/browser.ts +++ b/packages/playwright-core/src/client/browser.ts @@ -65,6 +65,8 @@ export class Browser extends ChannelOwner implements ap async _newContextForReuse(options: BrowserContextOptions = {}): Promise { for (const context of this._contexts) { await this._browserType._onWillCloseContext?.(context); + for (const page of context.pages()) + page._onClose(); context._onClose(); } this._contexts.clear(); diff --git a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts index ba644d06e0..1c5168c776 100644 --- a/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/browserContextDispatcher.ts @@ -270,8 +270,7 @@ export class BrowserContextDispatcher extends Dispatcher {}); diff --git a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts index e1a73b145e..801ab35d0f 100644 --- a/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/debugControllerDispatcher.ts @@ -79,8 +79,7 @@ export class DebugControllerDispatcher extends Dispatcher {}); + override _onDispose() { + // Avoid protocol calls for the closed page. + if (!this._page.isClosedOrClosingOrCrashed()) + this._page.setClientRequestInterceptor(undefined).catch(() => {}); } } diff --git a/packages/playwright-core/src/server/page.ts b/packages/playwright-core/src/server/page.ts index f781bb8991..266366c214 100644 --- a/packages/playwright-core/src/server/page.ts +++ b/packages/playwright-core/src/server/page.ts @@ -612,6 +612,10 @@ export class Page extends SdkObject { return this._closedState === 'closed'; } + isClosedOrClosingOrCrashed() { + return this._closedState !== 'open' || this._crashedPromise.isDone(); + } + _addWorker(workerId: string, worker: Worker) { this._workers.set(workerId, worker); this.emit(Page.Events.Worker, worker); diff --git a/tests/library/debug-controller.spec.ts b/tests/library/debug-controller.spec.ts index 8ef3622b36..fc1f224335 100644 --- a/tests/library/debug-controller.spec.ts +++ b/tests/library/debug-controller.spec.ts @@ -20,10 +20,12 @@ import { createGuid } from '../../packages/playwright-core/lib/utils'; import { Backend } from '../config/debugControllerBackend'; import type { Browser, BrowserContext } from '@playwright/test'; +type BrowserWithReuse = Browser & { _newContextForReuse: () => Promise }; type Fixtures = { wsEndpoint: string; backend: Backend; - connectedBrowser: Browser & { _newContextForReuse: () => Promise }; + connectedBrowserFactory: () => Promise; + connectedBrowser: BrowserWithReuse; }; const test = baseTest.extend({ @@ -41,16 +43,24 @@ const test = baseTest.extend({ await use(backend); await backend.close(); }, - connectedBrowser: async ({ wsEndpoint, browserType }, use) => { - const oldValue = (browserType as any)._defaultConnectOptions; - (browserType as any)._defaultConnectOptions = { - wsEndpoint, - headers: { 'x-playwright-reuse-context': '1', }, - }; - const browser = await browserType.launch(); - (browserType as any)._defaultConnectOptions = oldValue; - await use(browser as any); - await browser.close(); + connectedBrowserFactory: async ({ wsEndpoint, browserType }, use) => { + const browsers: BrowserWithReuse [] = []; + await use(async () => { + const oldValue = (browserType as any)._defaultConnectOptions; + (browserType as any)._defaultConnectOptions = { + wsEndpoint, + headers: { 'x-playwright-reuse-context': '1', }, + }; + const browser = await browserType.launch() as BrowserWithReuse; + (browserType as any)._defaultConnectOptions = oldValue; + browsers.push(browser); + return browser; + }); + for (const browser of browsers) + await browser.close(); + }, + connectedBrowser: async ({ connectedBrowserFactory }, use) => { + await use(await connectedBrowserFactory()); }, }); @@ -231,3 +241,27 @@ test('should pause and resume', async ({ backend, connectedBrowser }) => { await backend.resume(); await pausePromise; }); + +test('should reset routes before reuse', async ({ server, connectedBrowserFactory }) => { + const browser1 = await connectedBrowserFactory(); + const context1 = await browser1._newContextForReuse(); + await context1.route(server.PREFIX + '/title.html', route => route.fulfill({ body: 'Hello', contentType: 'text/html' })); + const page1 = await context1.newPage(); + await page1.route(server.PREFIX + '/consolelog.html', route => route.fulfill({ body: 'World', contentType: 'text/html' })); + + await page1.goto(server.PREFIX + '/title.html'); + await expect(page1).toHaveTitle('Hello'); + await page1.goto(server.PREFIX + '/consolelog.html'); + await expect(page1).toHaveTitle('World'); + await browser1.close(); + + const browser2 = await connectedBrowserFactory(); + const context2 = await browser2._newContextForReuse(); + const page2 = await context2.newPage(); + + await page2.goto(server.PREFIX + '/title.html'); + await expect(page2).toHaveTitle('Woof-Woof'); + await page2.goto(server.PREFIX + '/consolelog.html'); + await expect(page2).toHaveTitle('console.log test'); + await browser2.close(); +});