// scripts/e2e/watch_browser.js // Attach to Chrome remote, find/attach to a page (http://localhost:5175) and listen for: // - console messages // - window.postMessage events (proxied to console) // - DOM mutations (studio overlay, token messages) // Usage: // CHROME_DEBUG_URL=http://127.0.0.1:9222/json/version node scripts/e2e/watch_browser.js [targetUrl] const fs = require('fs') const path = require('path') const TARGET = process.argv.find(a => a && !a.startsWith('--')) || 'http://localhost:5175' const LOG = process.env.WATCH_LOG || '/tmp/e2e_watch_browser.log' function log(...args) { const line = `[${new Date().toISOString()}] ${args.map(a => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ')}\n` try { fs.appendFileSync(LOG, line) } catch (e) {} console.log(...args) } async function fetchJson(url) { const fetch = global.fetch || (await import('node-fetch')).then(m => m.default) const r = await fetch(url) if (!r.ok) throw new Error(`fetch ${url} failed ${r.status}`) return r.json() } async function findDebuggerWS(debugUrl) { const info = await fetchJson(debugUrl) return info.webSocketDebuggerUrl } async function findOrOpenPage(browser, targetUrl) { const pages = await browser.pages() for (const p of pages) { try { const u = p.url() if (!u || u === 'about:blank') continue if (u === targetUrl || u.startsWith(targetUrl) || u.includes('localhost:5175')) { return p } } catch (e) { } } const p = await browser.newPage() await p.bringToFront() await p.goto(targetUrl, { waitUntil: 'networkidle2', timeout: 30000 }) return p } async function main() { try { const debugUrl = process.env.CHROME_DEBUG_URL || 'http://127.0.0.1:9222/json/version' log('watcher starting', { debugUrl, target: TARGET, log: LOG }) const ws = await findDebuggerWS(debugUrl) log('devtools ws', ws) const puppeteer = require('puppeteer-core') const browser = await puppeteer.connect({ browserWSEndpoint: ws, defaultViewport: { width: 1280, height: 800 } }) const page = await findOrOpenPage(browser, TARGET) await page.bringToFront() // forward page console to our logger page.on('console', msg => { try { // Prefer the textual representation; fallback to args const text = (msg && typeof msg.text === 'function') ? msg.text() : '' const t = (msg && typeof msg.type === 'function') ? msg.type() : '' // Always log errors if (t === 'error' || (text && /error/i.test(text))) { log('[page-console][error]', text) return } // Only log messages that we explicitly injected (start with __E2E_) or are clearly relevant if (text && (text.startsWith('__E2E_') || /Token recibido|Esperando token|LIVEKIT|LIVEKIT_TOKEN|Entrar al estudio|StudioPortal|useStudioLauncher/.test(text))) { log('[page-console]', text) } // otherwise ignore to reduce noise (devtools and other extensions) } catch (e) { log('[page-console] err', e && e.message) } }) page.on('pageerror', err => log('[pageerror]', err && (err.stack || err.message))) // inject listeners in page context: message and mutation observer await page.evaluate(() => { try { // avoid double-binding if (window.__e2e_watch_installed) return window.__e2e_watch_installed = true // Shim to safely stringify circular objects function safeStringify(obj) { try { return JSON.stringify(obj) } catch (e) { try { return String(obj) } catch (e2) { return '' } } } // postMessage listener that logs to console with a prefix window.addEventListener('message', (e) => { try { const data = e.data const origin = e.origin || '' const hostnameMatches = origin && typeof origin === 'string' && (window.location && origin.includes(window.location.hostname)) const isObject = data && typeof data === 'object' const hasToken = isObject && (data.token || data.TOKEN || (data.token && data.token.length)) const typeStr = isObject && data.type ? String(data.type) : (typeof data === 'string' ? data : '') const typeHasLivekit = typeStr && typeStr.toUpperCase().includes('LIVEKIT') const source = isObject && data.source ? String(data.source) : '' const sourceIsStudio = source && /studio|broadcast|livekit/i.test(source) && !/devtools/i.test(source) const shouldLog = hasToken || typeHasLivekit || sourceIsStudio || hostnameMatches && (hasToken || typeHasLivekit) if (shouldLog) { console.log('__E2E_POSTMESSAGE__', safeStringify({ origin, data })) } } catch (err) { console.error('__E2E_POSTMESSAGE_ERR__', String(err)) } }, false) // MutationObserver to detect studio overlay and token text const observer = new MutationObserver((mutations) => { try { for (const m of mutations) { const added = Array.from(m.addedNodes || []) for (const n of added) { try { const txt = (n && n.textContent) ? n.textContent.trim() : '' if (txt && (txt.includes('Esperando token') || txt.includes('Token recibido') || txt.includes('Studio') || txt.includes('LiveKit') || txt.includes('LIVEKIT'))) { console.log('__E2E_MUTATION__', txt.slice(0, 200)) } } catch (e) {} } } } catch (e) { console.error('__E2E_MUTATION_ERR__', String(e)) } }) observer.observe(document.documentElement || document.body, { childList: true, subtree: true }) console.log('__E2E_WATCHER_INSTALLED__') } catch (err) { console.error('__E2E_INSTALL_ERR__', String(err)) } }) log('watcher attached and listening; interact with the browser. Ctrl+C to stop.') // keep process alive await new Promise(() => {}) } catch (err) { log('watcher error', err && (err.stack || err.message || String(err))) process.exit(2) } } main()