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

205 lines
7.7 KiB
JavaScript

// scripts/e2e/auto_enter_and_wait_token.js
// Automate navigation to broadcast-panel and repeatedly click "Entrar al estudio"
// until a token-like value appears in sessionStorage (key contains 'token' or 'livekit').
// Usage:
// CHROME_DEBUG_URL=http://127.0.0.1:9222/json/version E2E_LOG=/tmp/e2e_auto_run.log node scripts/e2e/auto_enter_and_wait_token.js [targetUrl] [--keep-open]
const fs = require('fs')
const path = require('path')
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_auto_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()
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 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) {}
await new Promise(r => setTimeout(r, interval))
}
throw new Error(`Timeout waiting for XPath: ${xpath}`)
}
async function detectTokenInSession(page) {
return await page.evaluate(() => {
try {
const keys = Object.keys(sessionStorage || {})
for (const k of keys) {
const v = sessionStorage.getItem(k)
if (!v) continue
const kl = k.toLowerCase()
if (kl.includes('token') || kl.includes('livekit') || kl.includes('session') || kl.includes('studio')) {
if (typeof v === 'string' && v.length > 8) return { key: k, value: v }
}
}
// fallback: find any value that looks like JWT (three parts separated by .)
for (const k of keys) {
const v = sessionStorage.getItem(k) || ''
if (typeof v === 'string' && v.split('.').length === 3 && v.length > 20) return { key: k, value: v }
}
return null
} catch (e) { return null }
})
}
async function clickEnterStudio(page) {
// Try to click an 'Entrar al estudio' button; return true if clicked
const xpaths = [
"(//button[contains(normalize-space(.),'Entrar al estudio') or contains(@aria-label,'Entrar al estudio') or normalize-space(text())='Entrar al estudio'])[1]",
"//button[contains(.,'Entrar al estudio')][1]",
"//a[contains(.,'Entrar al estudio')][1]"
]
for (const xp of xpaths) {
try {
const els = await page.$x(xp)
if (els && els.length) {
await els[0].click()
return true
}
} catch (e) {}
}
return false
}
async function main() {
try {
const debugUrl = process.env.CHROME_DEBUG_URL || 'http://127.0.0.1:9222/json/version'
log('[auto] debugUrl', debugUrl, 'target', TARGET, 'KEEP_OPEN', KEEP_OPEN)
const ws = await findDebuggerWS(debugUrl)
log('[auto] 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()
// install lightweight listeners to log relevant console messages
page.on('console', msg => {
try {
const txt = msg.text && typeof msg.text === 'function' ? msg.text() : ''
if (txt && (txt.startsWith('__E2E_') || /Token recibido|Esperando token|LIVEKIT|LIVEKIT_TOKEN|Entrar al estudio|StudioPortal|useStudioLauncher/.test(txt))) log('[page]', txt)
} catch (e) {}
})
log('[auto] starting loop to click Entrar al estudio and wait for session token')
const maxAttempts = 40
for (let i = 0; i < maxAttempts; i++) {
log('[auto] attempt', i+1)
// try to click enter studio
const clicked = await clickEnterStudio(page)
log('[auto] clickedEnter?', clicked)
// after clicking, wait a bit for overlay and for sessionStorage to update
try {
await page.waitForTimeout(1000)
// bring to front
try { await page.bringToFront() } catch(e){}
// check sessionStorage repeatedly for a short window
const checkStart = Date.now()
while (Date.now() - checkStart < 8000) {
const found = await detectTokenInSession(page)
if (found) {
log('[auto] TOKEN FOUND', found.key, 'len', (found.value || '').length)
// save screenshot and exit
const out = path.resolve(process.cwd(), 'auto_token_found.png')
await page.screenshot({ path: out, fullPage: true })
log('[auto] screenshot saved', out)
if (!KEEP_OPEN) {
await page.close()
await browser.disconnect()
process.exit(0)
} else {
log('[auto] KEEP_OPEN set; leaving page open. Ctrl+C to terminate.')
await new Promise(()=>{})
}
}
await page.waitForTimeout(500)
}
} catch (e) {
log('[auto] wait/check error', e && e.message)
}
// if not found, try to recreate the flow: click create -> skip -> start
try {
// try to click create transmission (if exists)
const createXPath = "//button[.//span/text() = 'Transmisión en vivo' or .//span[contains(text(),'Transmisión en vivo')]]"
try {
const createEls = await page.$x(createXPath)
if (createEls && createEls.length) { await createEls[0].click(); log('[auto] clicked create') }
} catch (e) {}
await page.waitForTimeout(500)
// try skip
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('[auto] clicked skip') }
} catch(e) {}
await page.waitForTimeout(300)
// try 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('[auto] clicked start') }
} catch (e) {}
await page.waitForTimeout(800)
} catch (e) { log('[auto] flow recreate err', e && e.message) }
}
log('[auto] max attempts reached; did not find token')
const out = path.resolve(process.cwd(), 'auto_token_notfound.png')
await page.screenshot({ path: out, fullPage: true })
log('[auto] screenshot saved', out)
if (!KEEP_OPEN) {
await page.close()
await browser.disconnect()
process.exit(2)
} else {
log('[auto] KEEP_OPEN set; leaving page open. Ctrl+C to terminate.')
await new Promise(()=>{})
}
} catch (err) {
log('[auto] ERROR', err && (err.stack || err.message || String(err)))
process.exit(2)
}
}
main()