From 7f5e686742de0649d0b8a703c6facef4cdfc30e9 Mon Sep 17 00:00:00 2001 From: Tarun Sukhani Date: Fri, 30 Jan 2026 13:38:43 +0000 Subject: [PATCH] fix: per-profile browser caching to allow parallel connections Changes global singleton cached/connecting variables to per-URL Maps. This allows different browser profiles (e.g., linkedin on port 18805, clawd on port 18800) to connect in parallel instead of blocking. Fixes #4289, #3605 --- src/browser/pw-session.ts | 37 ++++++++++++++++++++++++------------- 1 file changed, 24 insertions(+), 13 deletions(-) diff --git a/src/browser/pw-session.ts b/src/browser/pw-session.ts index e1dbcf7a1..b7ab026af 100644 --- a/src/browser/pw-session.ts +++ b/src/browser/pw-session.ts @@ -98,8 +98,9 @@ const MAX_CONSOLE_MESSAGES = 500; const MAX_PAGE_ERRORS = 200; const MAX_NETWORK_REQUESTS = 500; -let cached: ConnectedBrowser | null = null; -let connecting: Promise | null = null; +// Per-profile caching to allow parallel connections to different Chrome instances +const cachedByUrl = new Map(); +const connectingByUrl = new Map>(); function normalizeCdpUrl(raw: string) { return raw.replace(/\/$/, ""); @@ -281,8 +282,14 @@ function observeBrowser(browser: Browser) { async function connectBrowser(cdpUrl: string): Promise { const normalized = normalizeCdpUrl(cdpUrl); - if (cached?.cdpUrl === normalized) return cached; - if (connecting) return await connecting; + + // Check if we already have a cached connection for this specific URL + const cached = cachedByUrl.get(normalized); + if (cached) return cached; + + // Check if there's already a connection in progress for this specific URL + const existingConnecting = connectingByUrl.get(normalized); + if (existingConnecting) return await existingConnecting; const connectWithRetry = async (): Promise => { let lastErr: unknown; @@ -294,10 +301,12 @@ async function connectBrowser(cdpUrl: string): Promise { const headers = getHeadersWithAuth(endpoint); const browser = await chromium.connectOverCDP(endpoint, { timeout, headers }); const connected: ConnectedBrowser = { browser, cdpUrl: normalized }; - cached = connected; + cachedByUrl.set(normalized, connected); observeBrowser(browser); browser.on("disconnected", () => { - if (cached?.browser === browser) cached = null; + if (cachedByUrl.get(normalized)?.browser === browser) { + cachedByUrl.delete(normalized); + } }); return connected; } catch (err) { @@ -313,11 +322,12 @@ async function connectBrowser(cdpUrl: string): Promise { throw new Error(message); }; - connecting = connectWithRetry().finally(() => { - connecting = null; + const connectingPromise = connectWithRetry().finally(() => { + connectingByUrl.delete(normalized); }); + connectingByUrl.set(normalized, connectingPromise); - return await connecting; + return await connectingPromise; } async function getAllPages(browser: Browser): Promise { @@ -450,10 +460,11 @@ export function refLocator(page: Page, ref: string) { } export async function closePlaywrightBrowserConnection(): Promise { - const cur = cached; - cached = null; - if (!cur) return; - await cur.browser.close().catch(() => {}); + // Close all cached browser connections + const connections = Array.from(cachedByUrl.values()); + cachedByUrl.clear(); + connectingByUrl.clear(); + await Promise.all(connections.map((c) => c.browser.close().catch(() => {}))); } /**