- 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
155 lines
6.1 KiB
JavaScript
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()
|