// 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()