165 lines
5.9 KiB
JavaScript
165 lines
5.9 KiB
JavaScript
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(()=>'<no-body>');
|
|
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 || '<no-json>');
|
|
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;i<GUESTS;i++){
|
|
const u = `guest-e2e-${i+1}`;
|
|
const s = await createSession(u);
|
|
sessions.push({ role: 'guest', ...s });
|
|
}
|
|
} catch (e) {
|
|
vlog('failed creating sessions', String(e));
|
|
process.exit(2);
|
|
}
|
|
|
|
vlog('Launching browser');
|
|
const browser = await chromium.launch({
|
|
headless: HEADLESS,
|
|
args: [
|
|
'--use-fake-ui-for-media-stream',
|
|
'--use-fake-device-for-media-stream',
|
|
'--allow-file-access-from-files',
|
|
'--allow-insecure-localhost'
|
|
]
|
|
});
|
|
|
|
const contexts = [];
|
|
const pages = [];
|
|
|
|
try {
|
|
// open moderator first
|
|
const modSession = sessions.find(s=>s.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<pages.length;i++){
|
|
const p = pages[i];
|
|
const shot = `/tmp/e2e_sim_page_${i}.png`;
|
|
await p.screenshot({ path: shot, fullPage:true });
|
|
try{ fs.copyFileSync(shot, `${outDir}/${shot.split('/').pop()}`); } catch(e){}
|
|
vlog('Saved guest screenshot to', shot);
|
|
}
|
|
|
|
vlog('E2E simulate finished; screenshots saved');
|
|
} catch (e) {
|
|
vlog('E2E run failed', String(e));
|
|
} finally {
|
|
try { await browser.close(); } catch(e){}
|
|
}
|
|
|
|
vlog('Done.');
|
|
process.exit(0);
|
|
}catch(e){
|
|
try{ fs.appendFileSync(VERBOSE_LOG, 'fatal:' + String(e) + '\n'); } catch(_){}
|
|
console.error('fatal error', e);
|
|
process.exit(3);
|
|
}
|
|
})();
|