import { chromium } from 'playwright'; import fs from 'fs'; const BACKEND = process.env.BACKEND_URL || 'http://127.0.0.1:4000'; const GUESTS = Number(process.env.GUESTS || '2'); const HEADLESS = process.env.HEADLESS !== 'false'; const VERBOSE_LOG = '/tmp/e2e_sim_verbose.log'; function vlog(...args){ const line = `[${new Date().toISOString()}] ` + args.map(a => (typeof a === 'string' ? a : JSON.stringify(a))).join(' '); try { fs.appendFileSync(VERBOSE_LOG, line + '\n'); } catch(e){} console.log(line); } async function createSession(username) { vlog('createSession ->', username); try { const res = await fetch(`${BACKEND}/api/session`, { method: 'POST', headers: { 'content-type': 'application/json' }, body: JSON.stringify({ room: 'studio-demo', username }), // keepAlive not available in fetch, but backend should be reachable }); if (!res.ok) { const txt = await res.text().catch(()=>''); throw new Error(`session create failed ${res.status} ${txt}`); } const j = await res.json(); vlog('session created', j.redirectUrl || j); return j; } catch (e) { vlog('createSession error', String(e)); throw e; } } async function checkBackend(){ try { vlog('Checking backend health at', BACKEND + '/health'); const r = await fetch(`${BACKEND}/health`, { method: 'GET', cache: 'no-store' , redirect: 'follow' }); if (!r.ok) { vlog('backend health not ok', r.status); return false; } const j = await r.json().catch(()=>null); vlog('backend health response', j || ''); return true; } catch (e) { vlog('backend health check failed', String(e)); return false; } } (async ()=>{ try{ // reset verbose log try{ fs.writeFileSync(VERBOSE_LOG, ''); } catch(e){} vlog('E2E simulate guests - starting', { BACKEND, GUESTS, HEADLESS }); const ok = await checkBackend(); if (!ok) { vlog('Backend not available at', BACKEND, ' -> aborting'); process.exit(2); } // create sessions const sessions = []; try { const mod = await createSession('moderator-e2e'); sessions.push({ role: 'moderator', ...mod }); for (let i=0;is.role==='moderator'); if (!modSession) throw new Error('no moderator session'); const modContext = await browser.newContext({ permissions: ['camera','microphone'] }); const modPage = await modContext.newPage(); modPage.on('console', m => vlog('MOD PAGE:', m.text())); vlog('Opening moderator at', modSession.redirectUrl); await modPage.goto(modSession.redirectUrl, { waitUntil: 'networkidle', timeout: 60000 }); contexts.push(modContext); pages.push(modPage); // open guests for (let s of sessions.filter(x=>x.role==='guest')){ const ctx = await browser.newContext({ permissions: ['camera','microphone'] }); const pg = await ctx.newPage(); pg.on('console', m => vlog('GUEST PAGE:', m.text())); vlog('Opening guest at', s.redirectUrl); await pg.goto(s.redirectUrl, { waitUntil: 'networkidle', timeout: 60000 }); contexts.push(ctx); pages.push(pg); } // wait short await modPage.waitForTimeout(2000); // click Conectar todos on moderator vlog('Clicking Conectar todos'); await modPage.evaluate(()=>{ const btn = Array.from(document.querySelectorAll('button')).find(b => { const txt = (b.textContent||'').trim().toLowerCase(); return txt.includes('conectar todos') || txt.includes('connect all'); }); if (btn && typeof btn.click === 'function') btn.click(); }); // wait for invites to be processed; wait until an svg line has green stroke vlog('Waiting for accepted lines...'); const accepted = await modPage.waitForFunction(()=>{ const svg = document.querySelector('.connections-overlay'); if (!svg) return false; const lines = Array.from(svg.querySelectorAll('line')); return lines.some(l => ((l.getAttribute('stroke')||'').toLowerCase() === '#34d399')); }, { timeout: 20000 }).catch(()=>null); if (accepted) vlog('Accepted detected'); else vlog('No accepted detected within timeout'); // screenshots const outDir = 'packages/studio-panel/.playwright-mcp'; try{ fs.mkdirSync(outDir, { recursive: true }); } catch(e){} const modShot = '/tmp/e2e_sim_moderator.png'; await modPage.screenshot({ path: modShot, fullPage:true }); fs.copyFileSync(modShot, `${outDir}/${modShot.split('/').pop()}`); vlog('Saved moderator screenshot to', modShot); for (let i=0;i