// scripts/e2e/run_broadcast_studio_flow.js // Usage: // CHROME_DEBUG_URL=http://127.0.0.1:9222/json/version node scripts/e2e/run_broadcast_studio_flow.js const fs = require('fs') const path = require('path') // Duplicar logs a archivo para asegurar trazabilidad const E2E_LOG = process.env.E2E_LOG || '/tmp/e2e_run_inline.log' const _origLog = console.log.bind(console) const _origErr = console.error.bind(console) function _appendLog(type, args) { try { const line = `[${new Date().toISOString()}] ${type} ${args.map(a => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ')}\n` fs.appendFileSync(E2E_LOG, line) } catch (e) { try { _origErr('Failed to write e2e log', e && (e.stack || e.message || e)) } catch(e){} } } console.log = (...args) => { _appendLog('LOG', args); _origLog(...args) } console.error = (...args) => { _appendLog('ERR', args); _origErr(...args) } async function main() { const debugUrl = process.env.CHROME_DEBUG_URL || 'http://127.0.0.1:9222/json/version' const target = process.argv[2] || 'http://localhost:5175' const screenshotOut = path.resolve(process.cwd(), 'e2e_studio_flow.png') console.log('[e2e] CHROME_DEBUG_URL=', debugUrl) try { const fetch = global.fetch || (await import('node-fetch')).then(m => m.default) console.log('[e2e] fetching debug endpoint...') const r = await fetch(debugUrl) if (!r.ok) throw new Error(`debug endpoint fetch failed ${r.status}`) const info = await r.json() if (!info.webSocketDebuggerUrl) throw new Error('webSocketDebuggerUrl not found in debug info') const puppeteer = require('puppeteer-core') console.log('[e2e] connecting to browser websocket', info.webSocketDebuggerUrl) const browser = await puppeteer.connect({ browserWSEndpoint: info.webSocketDebuggerUrl, defaultViewport: { width: 1280, height: 800 } }) const page = await browser.newPage() page.on('console', msg => console.log('[page]', msg.type(), msg.text())) page.on('pageerror', err => console.error('[pageerror]', err && err.stack)) console.log('[e2e] goto', target) await page.goto(target, { waitUntil: 'networkidle2', timeout: 30000 }) // Click the "Transmisión en vivo" create button console.log('[e2e] waiting 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 [createBtn] = await page.$x(createXPath) if (!createBtn) throw new Error('Create button not found') console.log('[e2e] clicking create') await createBtn.click() // Wait for modal, click 'Omitir por ahora' console.log('[e2e] waiting for "Omitir por ahora"') await page.waitForTimeout(500) const skipXPath = "//*[text()['trim' in this ? trim() : 0] = 'Omitir por ahora' or normalize-space(.)='Omitir por ahora']" // fallback use contains let skipButton = null try { await page.waitForXPath("//*[normalize-space(text())='Omitir por ahora']", { timeout: 5000 }) [skipButton] = await page.$x("//*[normalize-space(text())='Omitir por ahora']") } catch (e) { // try contains const els = await page.$x("//*[contains(normalize-space(.),'Omitir por ahora')]") if (els && els.length) skipButton = els[0] } if (skipButton) { console.log('[e2e] clicking skip') await skipButton.click() } else { console.warn('[e2e] skip button not found; continuing') } // Wait for footer button 'Empezar ahora' and click it console.log('[e2e] waiting for create/Empezar ahora button') // The create button has text 'Empezar ahora' when blank destination const startBtnXPath = "//button[normalize-space(text())='Empezar ahora' or normalize-space(text())='Crear transmisión en vivo' or normalize-space(text())='Guardar cambios']" await page.waitForXPath(startBtnXPath, { timeout: 8000 }) const [startBtn] = await page.$x(startBtnXPath) if (!startBtn) throw new Error('Start/Create button not found in modal') await startBtn.click() console.log('[e2e] clicked Empezar ahora') // Modal should close and new transmission should appear in the table; click the first 'Entrar al estudio' button console.log('[e2e] waiting for table and Entrar al estudio button') await page.waitForXPath("//button[contains(normalize-space(.),'Entrar al estudio') or @aria-label[contains(.,'Entrar al estudio')]]", { timeout: 10000 }) 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() console.log('[e2e] clicked Entrar al estudio') // Wait for StudioPortal overlay to appear: look for text 'Esperando token...' or class studio-portal await page.waitForFunction(() => { return !!document.querySelector('.studio-portal') || !!Array.from(document.querySelectorAll('*')).some(el => (el.textContent||'').includes('Esperando token')) }, { timeout: 10000 }) console.log('[e2e] Studio overlay present') // Send a postMessage to simulate token arrival const fakeToken = 'e2e-test-token-' + Math.random().toString(36).slice(2,8) const fakeServer = 'wss://livekit.example' console.log('[e2e] posting LIVEKIT_TOKEN message to window (token length', fakeToken.length, ')') await page.evaluate((token, server) => { window.postMessage({ type: 'LIVEKIT_TOKEN', token, url: server, room: 'e2e-room' }, '*') }, fakeToken, fakeServer) // Wait for StudioPortal to show 'Token recibido desde Broadcast Panel' await page.waitForFunction(() => { return Array.from(document.querySelectorAll('*')).some(el => (el.textContent||'').includes('Token recibido desde Broadcast Panel')) }, { timeout: 8000 }) console.log('[e2e] StudioPortal acknowledged token in DOM') // screenshot await page.screenshot({ path: screenshotOut, fullPage: true }) console.log('[e2e] screenshot saved to', screenshotOut) await browser.disconnect() console.log('[e2e] done') process.exit(0) } catch (err) { console.error('[e2e] ERROR', err && (err.stack || err.message || err)) process.exit(2) } } main()