- 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
128 lines
6.2 KiB
JavaScript
128 lines
6.2 KiB
JavaScript
// 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()
|