AvanzaCast/scripts/e2e/watch_browser.js
Cesar Mendivil 8b458a3ddf feat: add initial LiveKit Meet integration with utility scripts, configs, and core components
- Add Next.js app structure with base configs, linting, and formatting
- Implement LiveKit Meet page, types, and utility functions
- Add Docker, Compose, and deployment scripts for backend and token server
- Provide E2E and smoke test scaffolding with Puppeteer and Playwright helpers
- Include CSS modules and global styles for UI
- Add postMessage and studio integration utilities
- Update package.json with dependencies and scripts for development and testing
2025-11-20 12:50:38 -07:00

155 lines
6.1 KiB
JavaScript

// 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 '<unserializable>' }
}
}
// 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()