AvanzaCast/scripts/e2e/attach_and_run.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

142 lines
5.6 KiB
JavaScript

// scripts/e2e/attach_and_run.js
// Connects to an existing Chrome (remote-debugging) and attaches to a page with the target URL
// Usage:
// CHROME_DEBUG_URL=http://127.0.0.1:9222/json/version node scripts/e2e/attach_and_run.js [targetUrl] [--keep-open]
const path = require('path')
const fs = require('fs')
const TARGET = process.argv.find(a => a && !a.startsWith('--')) || 'http://localhost:5175'
const KEEP_OPEN = process.argv.includes('--keep-open') || process.env.KEEP_OPEN === '1'
const E2E_LOG = process.env.E2E_LOG || '/tmp/e2e_attach_run.log'
function log(...args) {
try { fs.appendFileSync(E2E_LOG, `[${new Date().toISOString()}] ${args.map(a => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ')}\n`) } 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()
// try to find page by exact or partial match
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) { /* ignore */ }
}
// not found -> open a new tab
const p = await browser.newPage()
await p.bringToFront()
await p.goto(targetUrl, { waitUntil: 'networkidle2', timeout: 30000 })
return p
}
async function waitForXPathPoly(page, xpath, timeout = 15000, interval = 250) {
const start = Date.now()
while (Date.now() - start < timeout) {
try {
const els = await page.$x(xpath)
if (els && els.length) return els
} catch (e) {
// ignore transient errors
}
await new Promise(r => setTimeout(r, interval))
}
throw new Error(`Timeout waiting for XPath: ${xpath}`)
}
async function runFlowOnPage(page) {
try {
log('[attach] wait for create card')
const createXPath = "//button[.//span/text() = 'Transmisión en vivo' or .//span[contains(text(),'Transmisión en vivo')]]"
// await page.waitForXPath(createXPath, { timeout: 15000 })
const createEls = await waitForXPathPoly(page, createXPath, 15000)
const [createBtn] = createEls
if (createBtn) { await createBtn.click(); log('[attach] clicked create') }
await page.waitForTimeout(500)
// click skip if exists
try {
const [skip] = await page.$x("//*[normalize-space(text())='Omitir por ahora' or normalize-space(text())='Omitar por ahora' or normalize-space(text())='Skip for now']")
if (skip) { await skip.click(); log('[attach] clicked skip') }
} catch(e) { log('[attach] skip check err', e.message) }
// click start
try {
const [startBtn] = await page.$x("//button[normalize-space(text())='Empezar ahora' or normalize-space(text())='Crear transmisión en vivo' or normalize-space(text())='Start now']")
if (startBtn) { await startBtn.click(); log('[attach] clicked start') }
} catch (e) { log('[attach] start click err', e.message) }
// click enter studio
const [enterBtn] = await page.$x("(//button[contains(normalize-space(.),'Entrar al estudio') or contains(@aria-label,'Entrar al estudio')])[1]")
if (!enterBtn) throw new Error('Entrar al estudio button not found')
await enterBtn.click()
log('[attach] clicked Entrar al estudio')
// wait for overlay
await page.waitForFunction(()=> !!document.querySelector('.studio-portal') || Array.from(document.querySelectorAll('*')).some(el => (el.textContent||'').includes('Esperando token')), { timeout: 10000 })
log('[attach] Studio overlay present')
const fakeToken = 'attach-token-' + Math.random().toString(36).slice(2,8)
const fakeServer = 'wss://livekit.example'
await page.evaluate((t,s)=> window.postMessage({ type: 'LIVEKIT_TOKEN', token: t, url: s, room: 'attach-room' }, '*'), fakeToken, fakeServer)
log('[attach] posted LIVEKIT_TOKEN')
await page.waitForFunction(()=> Array.from(document.querySelectorAll('*')).some(el => (el.textContent||'').includes('Token recibido desde Broadcast Panel')), { timeout: 8000 })
log('[attach] StudioPortal acknowledged token')
return true
} catch (err) {
log('[attach] runFlowOnPage error', err && (err.stack||err.message))
return false
}
}
async function main(){
try{
const debugUrl = process.env.CHROME_DEBUG_URL || 'http://127.0.0.1:9222/json/version'
log('[attach] debugUrl', debugUrl, 'target', TARGET, 'KEEP_OPEN', KEEP_OPEN)
const ws = await findDebuggerWS(debugUrl)
log('[attach] 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()
const ok = await runFlowOnPage(page)
// screenshot
const out = path.resolve(process.cwd(), ok ? 'attach_flow.png' : 'attach_error.png')
await page.screenshot({ path: out, fullPage: true })
log('[attach] screenshot', out)
if (!KEEP_OPEN) {
await page.close()
await browser.disconnect()
process.exit(ok?0:2)
} else {
log('[attach] KEEP_OPEN set; process will keep running until you Ctrl+C; page remains open')
// keep alive
await new Promise(()=>{})
}
}catch(e){
log('[attach] ERROR', e && (e.stack||e.message))
process.exit(2)
}
}
main()