From 0a5b1fbcd1c42578c528c8ea4574abfcb30aa2bb Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 15 Jan 2026 11:16:48 +0000 Subject: [PATCH] fix(browser): handle extension relay page selection --- CHANGELOG.md | 1 + ...ge-for-targetid.extension-fallback.test.ts | 54 +++++++++++++++++++ src/browser/pw-session.ts | 8 ++- 3 files changed, 62 insertions(+), 1 deletion(-) create mode 100644 src/browser/pw-session.get-page-for-targetid.extension-fallback.test.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 891e02ff3..6f0710458 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ - Browser: add `snapshot refs=aria` (Playwright aria-ref ids) for self-resolving refs across `snapshot` → `act`. - Browser: `profile="chrome"` now defaults to host control and returns clearer “attach a tab” errors. - Browser: extension mode recovers when only one tab is attached (stale targetId fallback). +- Browser: fix `tab not found` for extension relay snapshots/actions when Playwright blocks `newCDPSession` (use the single available Page). - Control UI: show raw any-map entries in config views; move Docs link into the left nav. #### Plugins diff --git a/src/browser/pw-session.get-page-for-targetid.extension-fallback.test.ts b/src/browser/pw-session.get-page-for-targetid.extension-fallback.test.ts new file mode 100644 index 000000000..42c7e76ad --- /dev/null +++ b/src/browser/pw-session.get-page-for-targetid.extension-fallback.test.ts @@ -0,0 +1,54 @@ +import { describe, expect, it, vi } from "vitest"; + +describe("pw-session getPageForTargetId", () => { + it("falls back to the only page when CDP session attachment is blocked (extension relays)", async () => { + vi.resetModules(); + + const pageOn = vi.fn(); + const contextOn = vi.fn(); + const browserOn = vi.fn(); + const browserClose = vi.fn(async () => {}); + + const context = { + pages: () => [], + on: contextOn, + newCDPSession: vi.fn(async () => { + throw new Error("Not allowed"); + }), + } as unknown as import("playwright-core").BrowserContext; + + const page = { + on: pageOn, + context: () => context, + } as unknown as import("playwright-core").Page; + + // Fill pages() after page exists. + (context as unknown as { pages: () => unknown[] }).pages = () => [page]; + + const browser = { + contexts: () => [context], + on: browserOn, + close: browserClose, + } as unknown as import("playwright-core").Browser; + + vi.doMock("playwright-core", () => ({ + chromium: { + connectOverCDP: vi.fn(async () => browser), + }, + })); + + vi.doMock("./chrome.js", () => ({ + getChromeWebSocketUrl: vi.fn(async () => null), + })); + + const mod = await import("./pw-session.js"); + const resolved = await mod.getPageForTargetId({ + cdpUrl: "http://127.0.0.1:18792", + targetId: "NOT_A_TAB", + }); + expect(resolved).toBe(page); + + await mod.closePlaywrightBrowserConnection(); + expect(browserClose).toHaveBeenCalled(); + }); +}); diff --git a/src/browser/pw-session.ts b/src/browser/pw-session.ts index ef8142323..671c6b01f 100644 --- a/src/browser/pw-session.ts +++ b/src/browser/pw-session.ts @@ -332,7 +332,13 @@ export async function getPageForTargetId(opts: { const first = pages[0]; if (!opts.targetId) return first; const found = await findPageByTargetId(browser, opts.targetId); - if (!found) throw new Error("tab not found"); + if (!found) { + // Extension relays can block CDP attachment APIs (e.g. Target.attachToBrowserTarget), + // which prevents us from resolving a page's targetId via newCDPSession(). If Playwright + // only exposes a single Page, use it as a best-effort fallback. + if (pages.length === 1) return first; + throw new Error("tab not found"); + } return found; }