- 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
142 lines
5.6 KiB
JavaScript
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()
|