()
| 91 | * Calls are coalesced — concurrent first-time calls share one launch. |
| 92 | */ |
| 93 | export async function getSession(): Promise<BrowserSession> { |
| 94 | if (session) return session; |
| 95 | if (initInFlight) return initInFlight; |
| 96 | |
| 97 | initInFlight = (async () => { |
| 98 | const playwright = await importPlaywright(); |
| 99 | const cdpUrl = resolveBrowserCdpUrl(); |
| 100 | |
| 101 | let browser: Browser, context: BrowserContext, page: Page, attached = false; |
| 102 | if (cdpUrl) { |
| 103 | // ATTACH to an already-running browser (the user's own, with real logins). Reuse |
| 104 | // its existing default context + an open tab so cookies/sessions are intact; only |
| 105 | // open a fresh tab if none exist. We never create our own isolated context here — |
| 106 | // that would defeat the point (a blank, logged-OUT session). |
| 107 | browser = await playwright.chromium.connectOverCDP(cdpUrl); |
| 108 | context = browser.contexts()[0] ?? (await browser.newContext()); |
| 109 | const open = (context.pages() as Page[]).find((p: Page) => !p.isClosed?.()); |
| 110 | page = open ?? (await context.newPage()); |
| 111 | attached = true; |
| 112 | logger.info('Browser session ATTACHED over CDP', { cdpUrl }); |
| 113 | } else { |
| 114 | const headed = process.env.QODEX_BROWSER_HEADED === '1'; |
| 115 | browser = await playwright.chromium.launch({ |
| 116 | headless: !headed, |
| 117 | // --disable-blink-features prevents the "Chrome is being controlled by |
| 118 | // automated test software" infobar from interfering with element positions. |
| 119 | args: ['--disable-blink-features=AutomationControlled'], |
| 120 | }); |
| 121 | context = await browser.newContext({ |
| 122 | viewport: { width: 1280, height: 800 }, |
| 123 | userAgent: 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36 QodeX/0.7', |
| 124 | }); |
| 125 | page = await context.newPage(); |
| 126 | logger.info('Browser session launched', { headed }); |
| 127 | } |
| 128 | |
| 129 | const s: BrowserSession = { browser, context, page, attached, consoleBuffer: [], requestBuffer: [], errorBuffer: [] }; |
| 130 | wireSessionListeners(s, page); |
| 131 | session = s; |
| 132 | return s; |
| 133 | })(); |
| 134 | |
| 135 | try { |
| 136 | return await initInFlight; |
| 137 | } finally { |
| 138 | initInFlight = null; |
| 139 | } |
| 140 | } |
| 141 | |
| 142 | /** Install the console/pageerror/network listeners that fill the read buffers. Tools |
| 143 | * read from these rather than installing per-call listeners (avoids missing events). */ |
no test coverage detected