import { chromium } from 'playwright'; import { spawn } from 'child_process'; import path from 'path'; console.log('run_e2e: starting (pid=' + process.pid + ')'); console.log('ENV VITE_STUDIO_URL=' + (process.env.VITE_STUDIO_URL || '')); console.log('ENV VITE_BROADCASTPANEL_URL=' + (process.env.VITE_BROADCASTPANEL_URL || '')); console.log('ENV VITE_TOKEN_SERVER_URL=' + (process.env.VITE_TOKEN_SERVER_URL || '')); const serverPath = path.resolve('./server.mjs'); const base = 'http://localhost:5174'; // Allow overriding the target origin via ENV (e.g., VITE_STUDIO_URL) const targetOrigin = process.env.VITE_STUDIO_URL || process.env.TARGET_ORIGIN || ''; // If provided, open this as the sender page so origin matches allowed production origins const broadcastSender = process.env.VITE_BROADCASTPANEL_URL || ''; // Token server URL for obtaining tokens automatically const tokenServer = process.env.VITE_TOKEN_SERVER_URL || process.env.TOKEN_SERVER_URL || ''; async function startServer() { console.log('run_e2e: starting static server at', serverPath); const proc = spawn(process.execPath, [serverPath], { env: { ...process.env, PORT: '5174' }, stdio: ['ignore', 'pipe', 'pipe'] }); proc.stdout.on('data', d => process.stdout.write('[server] ' + d.toString())); proc.stderr.on('data', d => process.stderr.write('[server] ' + d.toString())); await new Promise(r => setTimeout(r, 300)); console.log('run_e2e: static server started'); return proc; } // helper: request token from token server async function requestTokenFromServer() { if (!tokenServer) return null; try { // If SESSION_ID is provided, request that session; otherwise use a default /api/session/e2e endpoint const sessionId = process.env.SESSION_ID || ''; const endpoint = sessionId ? `/api/session/${encodeURIComponent(sessionId)}` : '/api/session/e2e'; const url = `${tokenServer.replace(/\/$/, '')}${endpoint}`; console.log('Requesting token from', url, sessionId ? `(sessionId=${sessionId})` : '(default)'); const res = await fetch(url, { method: 'GET' }); if (!res.ok) { console.warn('Token server returned', res.status); return null; } const data = await res.json(); if (data && data.token) return data; return null; } catch (err) { console.warn('Token request failed:', String(err)); return null; } } async function run() { console.log('run_e2e: launching browser'); const server = await startServer(); const browser = await chromium.launch({ headless: true }); console.log('run_e2e: browser launched'); const context = await browser.newContext(); const page = await context.newPage(); let url; if (broadcastSender) { url = broadcastSender; // open prod broadcast panel as sender console.log('Opening broadcast sender at', url); } else { url = base + '/sender.html'; if (targetOrigin) url += '?target=' + encodeURIComponent(targetOrigin); console.log('Opening local sender at', url); } console.log('run_e2e: navigating to', url); await page.goto(url); console.log('run_e2e: page loaded'); // If we opened local sender, click UI; if we opened remote broadcast page we try to trigger postMessage via evaluate if (!broadcastSender) { await page.click('#open'); } // Before sending a token, try to fetch a fresh token from the token server let token = 'E2E_TEST_TOKEN'; // fallback let tokenMeta = null; try { tokenMeta = await requestTokenFromServer(); if (tokenMeta && tokenMeta.token) { token = tokenMeta.token; console.log('Obtained token from server, using it for handshake'); } else { console.log('No token from server, using fallback token'); } } catch (e) { console.warn('Token fetch error, using fallback token', e); } if (!broadcastSender) { // send token from the local sender UI (click send) await page.click('#send'); // but override the message in the page to use the token we fetched try { await page.evaluate((tok) => { if (window.__studioPopup && !window.__studioPopup.closed) { try { window.__studioPopup.postMessage({ type: 'LIVEKIT_TOKEN', token: tok, room: 'e2e-room' }, window.__studioPopup.location?.origin || '*'); } catch (e) { // fallback: send to global origin used by the page try { window.postMessage({ type: 'LIVEKIT_TOKEN', token: tok, room: 'e2e-room' }, location.origin); } catch(e){} } } }, token); } catch (e) { console.warn('Override send failed', e); } } else { // For remote broadcast panel, open the studio popup and post message using the fetched token await page.evaluate(async (studioUrl, tok) => { // open popup window.popupForE2E = window.open(studioUrl, 'studioPopup', 'width=800,height=600'); // wait for a moment and then post the token await new Promise(r => setTimeout(r, 600)); try { window.popupForE2E.postMessage({ type: 'LIVEKIT_TOKEN', token: tok, room: 'e2e-room' }, studioUrl); } catch (e) { console.error('postMessage failed inside page eval', e); } }, targetOrigin || '', token); } // wait for token ack await page.waitForFunction(() => { const p = document.querySelector('pre'); return p && p.textContent && p.textContent.includes('LIVEKIT_TOKEN_ACK'); }, { timeout: 7000 }).catch(()=>{}); // wait for connected ack await page.waitForFunction(() => { const p = document.querySelector('pre'); return p && p.textContent && p.textContent.includes('LIVEKIT_ACK') && p.textContent.includes('connected'); }, { timeout: 7000 }).catch(()=>{}); console.log('run_e2e: finished script, collecting logs'); const log = await page.$eval('pre', el => el.textContent).catch(()=>''); console.log('E2E log:\n', log); await browser.close(); console.log('run_e2e: browser closed, killing server'); server.kill(); } run().catch(err => { console.error('run_e2e: fatal error', err); process.exit(1); });