(async ()=>{ const mod = await import('puppeteer') const puppeteer = (mod && mod.default) ? mod.default : mod const path = await import('path') const fs = await import('fs') const chromePath = process.env.CHROME_PATH || '/usr/bin/chromium' const url = process.env.VITE_BROADCASTPANEL_URL || 'https://avanzacast-broadcastpanel.bfzqqk.easypanel.host' const token = process.env.TOKEN || 'e2e098863b912f6a178b68e71ec3c58d' const livekitUrl = process.env.VITE_LIVEKIT_WS_URL || 'wss://livekit-server.bfzqqk.easypanel.host' const outDir = process.cwd() console.log('Launching Chromium at', chromePath) const browser = await puppeteer.launch({ executablePath: chromePath, args: ['--no-sandbox','--disable-setuid-sandbox','--disable-dev-shm-usage'], defaultViewport: { width: 1400, height: 900 }, headless: 'new' }) const page = await browser.newPage() const consoles = [] page.on('console', msg => { try { consoles.push({ type: msg.type(), text: msg.text() }) } catch(e) {} }) const pageErrors = [] page.on('pageerror', err => { pageErrors.push(String(err && err.stack ? err.stack : err)) }) try { console.log('Navigating to', url) await page.goto(url, { waitUntil: 'networkidle2', timeout: 90000 }) console.log('Page loaded') } catch(e){ console.warn('goto error', e && e.message?e.message:e) } await page.waitForTimeout(2000) // Try to click 'Transmisión en vivo' card to open transmissions panel const navClicked = await page.evaluate(()=>{ const norm = (s)=> (s||'').toLowerCase().trim() // 1) Try within create grid try{ const grid = document.querySelector('[class*="createGrid"], .createGrid') if(grid){ const buttons = Array.from(grid.querySelectorAll('button, a')) for(const b of buttons){ const t = norm(b.textContent || b.innerText || b.getAttribute('aria-label') || '') if(t.includes('transmisión en vivo') || t.includes('transmision en vivo') || t.includes('transmis')){ try{ b.click(); return {ok:true, from:'createGrid', text:t.slice(0,120)} }catch(e){} } } } }catch(e){} // 2) exact text anywhere among buttons/links const nodes = Array.from(document.querySelectorAll('button, a, [role="button"]')) const hits = [] for(const n of nodes){ const t = norm(n.textContent || n.innerText || n.getAttribute('aria-label') || '') if(!t) continue if(t === 'transmisión en vivo' || t === 'transmision en vivo' || t.includes('transmis')){ try{ n.click(); return {ok:true, from:'global', text:t.slice(0,120)} }catch(e){} } if(t.includes('transmis')) hits.push({text:t,tag:n.tagName,class:n.className.slice(0,120),aria:n.getAttribute('aria-label')||''}) } // expose hits to page console for debugging try{ console.debug('transmis-candidates', JSON.stringify(hits.slice(0,20))) }catch(e){} return {ok:false, hits: hits.slice(0,10)} }) console.log('navClicked:', navClicked) await page.waitForTimeout(2000) // Wait for 'Entrar al estudio' button to appear (retry for up to 10s) let foundEnter = false for(let attempt=0; attempt<8; attempt++){ foundEnter = await page.evaluate(()=>{ const nodes = Array.from(document.querySelectorAll('button, a, [role="button"]')) const norm = (s)=> (s||'').toLowerCase().trim() for(const n of nodes){ const t = norm(n.textContent||n.getAttribute('aria-label')||n.innerText||''); if(t.includes('entrar al estudio') || t === 'entrar al estudio' || t.includes('entrando...')) return true } return false }) console.log('check enter button attempt', attempt, 'found=', foundEnter) if(foundEnter) break await page.waitForTimeout(1000 + attempt*500) } // If enter button does not exist, attempt to create a new transmission via UI flow if(!foundEnter){ console.log('No Entrar al estudio found — attempting to create a transmission via UI') try{ // Attempt to click a 'Transmisión en vivo' or 'Nueva transmisión' card/button again await page.evaluate(()=>{ const nodes = Array.from(document.querySelectorAll('button, a, div')) const norm = (s)=> (s||'').toLowerCase().trim() for(const n of nodes){ const t = norm(n.textContent || n.innerText || n.getAttribute('aria-label') || '') if(!t) continue if(t.includes('transmisión en vivo') || t.includes('transmisión') || t.includes('nueva escena') || t.includes('nueva transmisión') || t.includes('nueva')){ try{ n.click(); break }catch(e){} } } }) await page.waitForTimeout(1200) // If a modal appears with 'Omitir ahora' (skip scheduling), click it const clickedOmit = await page.evaluate(()=>{ const nodes = Array.from(document.querySelectorAll('button, a')) const norm = (s)=> (s||'').toLowerCase().trim() for(const n of nodes){ const t = norm(n.textContent || n.innerText || n.getAttribute('aria-label') || '') if(!t) continue if(t.includes('omitir') || t.includes('omitir ahora') || t.includes('skip') ){ try{ n.click(); return {ok:true, text:t.slice(0,120)} }catch(e){ return {ok:false, err:String(e)} } } } return {ok:false} }) console.log('clickedOmit result', clickedOmit) await page.waitForTimeout(1000) // Fill title input if present and click 'Empezar ahora' or similar const created = await page.evaluate(()=>{ // Try to find an input/textarea for title const inputs = Array.from(document.querySelectorAll('input, textarea')) const norm = (s)=> (s||'').toLowerCase().trim() let filled = false for(const inp of inputs){ try{ const p = inp.getAttribute('placeholder') || inp.getAttribute('aria-label') || inp.name || '' const t = norm(p) if(t.includes('tít') || t.includes('titu') || t.includes('title') || t.includes('nombre') || t.includes('transmisi')){ try{ inp.focus(); inp.value = 'Transmision'; inp.dispatchEvent(new Event('input', { bubbles: true })); }catch(e){} filled = true; break } }catch(e){} } // If not filled, try first visible input if(!filled && inputs.length>0){ try{ inputs[0].focus(); inputs[0].value = 'Transmision'; inputs[0].dispatchEvent(new Event('input', { bubbles: true })); filled = true }catch(e){} } // Find and click 'Empezar ahora' / 'Empezar' / 'Start' / 'Iniciar ahora' const buttons = Array.from(document.querySelectorAll('button, a')) for(const b of buttons){ const txt = norm(b.textContent||b.getAttribute('aria-label')||b.innerText||'') if(txt.includes('empezar ahora') || txt.includes('empezar') || txt.includes('iniciar ahora') || txt.includes('start now') || txt.includes('comenzar')){ try{ b.click(); return {ok:true, clicked: txt} }catch(e){ return {ok:false, err:String(e)} } } } return {ok:false, filled} }) console.log('created transmission result', created) await page.waitForTimeout(2000) // After creation, re-check for Enter button for(let attempt=0; attempt<8; attempt++){ foundEnter = await page.evaluate(()=>{ const nodes = Array.from(document.querySelectorAll('button, a, [role="button"]')) const norm = (s)=> (s||'').toLowerCase().trim() for(const n of nodes){ const t = norm(n.textContent||n.getAttribute('aria-label')||n.innerText||''); if(t.includes('entrar al estudio') || t === 'entrar al estudio' || t.includes('entrando...')) return true } return false }) console.log('re-check enter button attempt', attempt, 'found=', foundEnter) if(foundEnter) break await page.waitForTimeout(1000 + attempt*500) } }catch(e){ console.warn('create transmission flow failed', e && e.message?e.message:e) } } // If enter button exists, try to click the first one let clickedEnter = { ok: false } if(foundEnter){ clickedEnter = await page.evaluate(()=>{ const nodes = Array.from(document.querySelectorAll('button, a, [role="button"]')) const norm = (s)=> (s||'').toLowerCase().trim() for(const n of nodes){ const t = norm(n.textContent||n.getAttribute('aria-label')||n.innerText||''); if(t.includes('entrar al estudio')){ try{ n.click(); return {ok:true, text:t.slice(0,120)} }catch(e){ return {ok:false, err: String(e)} } } } return {ok:false} }) console.log('clicked enter button result', clickedEnter) } else { console.log('Entrar al estudio button still not found after create attempt — will still postMessage to window') } await page.waitForTimeout(1000) const beforePath = path.join(outDir, 'send-token-before.png') try { await page.screenshot({ path: beforePath, fullPage: true }); console.log('Saved screenshot', beforePath) } catch(e){ console.warn('screenshot before failed', e && e.message)} const POST_ORIGIN = '*' const payload = { type: 'LIVEKIT_TOKEN', token, url: livekitUrl, room: 'e2e-room' } const result = await page.evaluate(async (payload, POST_ORIGIN)=>{ const tryPost = (w, origin) => { try{ w.postMessage(payload, origin); return true }catch(e){return false} } const tried = [] try{ const globals = ['__studioPopup','popupForE2E','window.__studioPopup','__AVZ_LAST_MSG_SOURCE','__AVZ_LAST_MSG_SOURCE?.source'] for(const g of globals){ try{ const w = window[g]; if(w && typeof w.postMessage === 'function') { tried.push({target:g,ok:tryPost(w, window.location.origin || '*')}) } }catch(e){} } if(window.opener && !window.opener.closed){ tried.push({target:'opener', ok:tryPost(window.opener, POST_ORIGIN)}) } try{ window.postMessage(payload, window.location.origin); tried.push({target:'self', ok:true}) } catch(e) { tried.push({target:'self', ok:false}) } }catch(e){ tried.push({error: String(e)}) } return tried }, payload, POST_ORIGIN) console.log('postMessage attempts:', result) // Fallback: if not connected yet, navigate to URL with token query so app auto-opens studio const fallbackUrl = `${url.replace(/\/$/, '')}/?token=${encodeURIComponent(token)}&room=e2e-room`; console.log('fallbackUrl:', fallbackUrl) try{ await page.goto(fallbackUrl, { waitUntil: 'networkidle2', timeout: 15000 }) console.log('Navigated to fallback token URL') } catch(e){ console.warn('fallback navigation failed', e && e.message)} // wait and check for Studio status element added by App (id='status' or presence of StudioPortal root) let connected = false try { const maxWait = 20000 const start = Date.now() while (Date.now() - start < maxWait) { try { const statusText = await page.evaluate(()=>{ const el = document.getElementById('status') if(el) return el.textContent // try detecting studio overlay by looking for known texts const nodes = Array.from(document.querySelectorAll('div,span')) for(const n of nodes){ const t=(n.textContent||'').toLowerCase(); if(t.includes('validando token') || t.includes('validando') || t.includes('entrando') || t.includes('validando token') || t.includes('conectado')) return t } return null }) if (statusText && statusText.toLowerCase().includes('conectado')) { connected = true; break } } catch(e) {} await page.waitForTimeout(1000) } } catch(e) { console.warn('status check failed', e && e.message)} await page.waitForTimeout(1000) const afterPath = path.join(outDir, 'send-token-after.png') try { await page.screenshot({ path: afterPath, fullPage: true }); console.log('Saved screenshot', afterPath) } catch(e){ console.warn('screenshot after failed', e && e.message)} try { const out = { consoles, pageErrors, navClicked: navClicked, clickedEnter: clickedEnter, postAttempts: result, connected } const logPath = path.join(outDir, 'send-token-browser-log.json') await fs.promises.writeFile(logPath, JSON.stringify(out, null, 2), 'utf8') console.log('Wrote browser log to', logPath) } catch(e){ console.warn('failed to write browser log', e && e.message)} await browser.close() console.log('done, connected=', connected) })().catch(e=>{ console.error(e && e.stack?e.stack:e); process.exit(2) })