148 lines
5.9 KiB
JavaScript

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); });