From d401b22dfe9e1081e2d434c70a507a402ff404a9 Mon Sep 17 00:00:00 2001 From: "Tommy D. Rossi" Date: Tue, 3 Feb 2026 18:35:07 +0100 Subject: [PATCH] delete cdp session from cache on page close --- playwriter/src/executor.ts | 19 ++++++++++- playwriter/src/relay-session.test.ts | 48 ++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+), 1 deletion(-) diff --git a/playwriter/src/executor.ts b/playwriter/src/executor.ts index e92c24a..0a72f97 100644 --- a/playwriter/src/executor.ts +++ b/playwriter/src/executor.ts @@ -610,12 +610,29 @@ export class PlaywrightExecutor { } const getCDPSession = async (options: { page: Page }) => { + if (options.page.isClosed()) { + throw new Error('Cannot create CDP session for closed page') + } + const cached = this.cdpSessionCache.get(options.page) - if (cached) return cached + if (cached) { + return cached + } + // Generate a fresh unique URL for each CDP session to avoid client ID conflicts const wsUrl = getCdpUrl(this.cdpConfig) const session = await getCDPSessionForPage({ page: options.page, wsUrl }) this.cdpSessionCache.set(options.page, session) + + options.page.on('close', () => { + const cachedSession = this.cdpSessionCache.get(options.page) + if (!cachedSession) { + return + } + this.cdpSessionCache.delete(options.page) + cachedSession.close() + }) + return session } diff --git a/playwriter/src/relay-session.test.ts b/playwriter/src/relay-session.test.ts index 273dd98..f178449 100644 --- a/playwriter/src/relay-session.test.ts +++ b/playwriter/src/relay-session.test.ts @@ -5,6 +5,7 @@ import { getCdpUrl } from './utils.js' import { getCDPSessionForPage } from './cdp-session.js' import { Debugger } from './debugger.js' import { Editor } from './editor.js' +import { PlaywrightExecutor } from './executor.js' import { setupTestContext, cleanupTestContext, getExtensionServiceWorker, createSseServer, safeCloseCDPBrowser, type TestContext, withTimeout, js } from './test-utils.js' import './test-declarations.js' @@ -106,6 +107,53 @@ describe('CDP Session Tests', () => { await page.close() }, 60000) + it('should reuse cached CDP session and close on page close', async () => { + const browserContext = getBrowserContext() + const serviceWorker = await getExtensionServiceWorker(browserContext) + + const page = await browserContext.newPage() + await page.goto('https://example.com/') + await page.bringToFront() + + await serviceWorker.evaluate(async () => { + await globalThis.toggleExtensionForActiveTab() + }) + await new Promise(r => setTimeout(r, 100)) + + const executor = new PlaywrightExecutor({ + cdpConfig: { port: TEST_PORT }, + logger: { + log: () => {}, + error: () => {}, + }, + }) + + const result = await executor.execute(js` + const sessionA = await getCDPSession({ page }) + const sessionB = await getCDPSession({ page }) + await sessionA.send('Runtime.evaluate', { expression: '1 + 1', returnByValue: true }) + const evalResult = await sessionB.send('Runtime.evaluate', { expression: '2 + 2', returnByValue: true }) + return evalResult.result.value + `) + + expect(result.isError).toBe(false) + expect(result.text).toContain('[return value] 4') + + await page.close() + + const closeResult = await executor.execute(js` + try { + const session = await getCDPSession({ page }) + await session.send('Runtime.evaluate', { expression: '3 + 3', returnByValue: true }) + return 'unexpected' + } catch (e) { + return 'closed' + } + `) + expect(closeResult.isError).toBe(false) + expect(closeResult.text).toContain('[return value] closed') + }, 60000) + it('should list scripts with Debugger class', async () => { const browserContext = getBrowserContext() const serviceWorker = await getExtensionServiceWorker(browserContext)