AvanzaCast/packages/broadcast-panel/e2e/run_browserless_e2e.js
Cesar Mendivil 8b458a3ddf feat: add initial LiveKit Meet integration with utility scripts, configs, and core components
- Add Next.js app structure with base configs, linting, and formatting
- Implement LiveKit Meet page, types, and utility functions
- Add Docker, Compose, and deployment scripts for backend and token server
- Provide E2E and smoke test scaffolding with Puppeteer and Playwright helpers
- Include CSS modules and global styles for UI
- Add postMessage and studio integration utilities
- Update package.json with dependencies and scripts for development and testing
2025-11-20 12:50:38 -07:00

212 lines
8.4 KiB
JavaScript

// filepath: packages/broadcast-panel/e2e/run_browserless_e2e.js
import fetch from 'node-fetch';
import puppeteer from 'puppeteer-core';
import fs from 'fs';
import path from 'path';
async function main() {
const BROWSERLESS_WS = process.env.BROWSERLESS_WS || process.env.BROWSERLESS_URL || 'wss://browserless.bfzqqk.easypanel.host';
const BROWSERLESS_TOKEN = process.env.BROWSERLESS_TOKEN || process.env.BROWSERLESS_KEY || '';
const TOKEN_SERVER = process.env.TOKEN_SERVER || 'https://avanzacast-servertokens.bfzqqk.easypanel.host';
const ROOM = process.env.ROOM || 'e2e-room';
const USERNAME = process.env.USERNAME || 'e2e-runner';
const OUT_DIR = process.env.OUT_DIR || null;
function outLog(...args) {
console.log(...args);
if (OUT_DIR) {
try {
fs.appendFileSync(path.join(OUT_DIR, 'e2e.log'), args.map(String).join(' ') + '\n');
} catch (e) { /* ignore */ }
}
}
outLog('E2E runner starting with:', { BROWSERLESS_WS, TOKEN_SERVER, ROOM, USERNAME, OUT_DIR });
if (!BROWSERLESS_TOKEN) {
outLog('Missing BROWSERLESS_TOKEN env');
process.exit(2);
}
outLog('Creating session on token server', TOKEN_SERVER, ROOM, USERNAME);
let resp;
try {
resp = await fetch(`${TOKEN_SERVER.replace(/\/$/, '')}/api/session`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ room: ROOM, username: USERNAME, ttl: 300 }),
});
} catch (err) {
outLog('Network error while calling token server:', String(err));
process.exit(3);
}
outLog('Token server responded with status', resp.status, resp.statusText);
let data;
try {
const text = await resp.text();
try { data = JSON.parse(text); } catch(e) { data = null; }
outLog('Token server response body:', text);
} catch (err) {
outLog('Failed to read token server response body', String(err));
process.exit(3);
}
if (!resp.ok) {
outLog('Failed to create session, status', resp.status);
process.exit(4);
}
if (!data) {
outLog('Token server returned non-JSON or empty body');
process.exit(5);
}
outLog('Session created:', data);
const sessionId = data.id;
const studioUrl = data.studioUrl || data.redirectUrl || data.url;
let token = data.token || null;
if (!studioUrl) {
outLog('No studio URL returned from token server');
process.exit(4);
}
// If POST didn't return the token, try to GET it from the session endpoint
if (!token && sessionId) {
outLog('POST did not include token, attempting GET /api/session/:id to fetch token');
try {
const sessResp = await fetch(`${TOKEN_SERVER.replace(/\/$/, '')}/api/session/${encodeURIComponent(sessionId)}`);
const sessText = await sessResp.text();
let sessJson = null;
try { sessJson = JSON.parse(sessText); } catch(e) { sessJson = null; }
outLog('GET /api/session/:id status', sessResp.status, 'body start:', String(sessText).slice(0,400));
if (sessJson && sessJson.token) {
token = sessJson.token;
outLog('Obtained token from GET /api/session/:id (length', (token && token.length) || 0, ')');
} else {
outLog('GET /api/session/:id did not return token');
}
} catch (err) {
outLog('Error while GET /api/session/:id', String(err));
}
}
// connect to browserless
const wsEndpoint = `${BROWSERLESS_WS}${BROWSERLESS_WS.includes('?') ? '&' : '?'}token=${encodeURIComponent(BROWSERLESS_TOKEN)}`;
outLog('Connecting to browserless WS endpoint:', wsEndpoint);
let browser;
try {
browser = await puppeteer.connect({ browserWSEndpoint: wsEndpoint, defaultViewport: { width: 1366, height: 768 }, timeout: 20000 });
} catch (err) {
outLog('Failed to connect to browserless via puppeteer.connect:', err && err.stack ? err.stack : String(err));
process.exit(6);
}
try {
const page = await browser.newPage();
page.on('console', msg => {
try { outLog('[BROWSER]', msg.type(), msg.text()); } catch(e){}
});
page.on('pageerror', err => outLog('[PAGEERROR]', err && err.stack ? err.stack : String(err)));
// Log network failures
page.on('requestfailed', req => {
try { outLog('[REQFAILED]', req.url(), req.failure() && req.failure().errorText); } catch(e){}
});
page.on('response', async res => {
try {
const status = res.status();
if (status >= 400) {
outLog('[RESP_ERR]', status, res.url());
if (OUT_DIR) {
try {
const text = await res.text();
fs.writeFileSync(path.join(OUT_DIR, `resp_${sessionId || 'noid'}_${status}.txt`), text);
outLog('Saved failing response body to', path.join(OUT_DIR, `resp_${sessionId || 'noid'}_${status}.txt`));
} catch (e) { outLog('Failed to save response body', String(e)); }
}
}
} catch (e) { /* ignore */ }
});
outLog('Navigating to studioUrl:', studioUrl);
try {
await page.goto(studioUrl, { waitUntil: 'domcontentloaded', timeout: 20000 });
} catch (err) {
outLog('page.goto failed:', err && err.stack ? err.stack : String(err));
throw err;
}
// Wait for StudioPortal text indicating waiting for token
const waited = await page.waitForFunction(() => {
return document.body && document.body.innerText && (document.body.innerText.includes('Esperando token') || document.body.innerText.includes('Token recibido'));
}, { timeout: 8000 }).catch(() => false);
if (!waited) outLog('Did not see StudioPortal waiting/received token text');
// If token was not included in redirect, try to postMessage token to window
if (token) {
outLog('Posting token via postMessage (token length', token.length, ')');
try {
await page.evaluate((tk) => {
window.postMessage({ type: 'LIVEKIT_TOKEN', token: tk, url: window.location.href, room: '' }, window.location.origin);
}, token);
} catch (err) {
outLog('postMessage evaluate failed:', err && err.stack ? err.stack : String(err));
}
} else {
outLog('No token present in session response; relying on redirect/session id flow');
}
// Wait for StudioPortal to report token received
const gotToken = await page.waitForFunction(() => {
return document.body && document.body.innerText && document.body.innerText.includes('Token recibido desde Broadcast Panel');
}, { timeout: 10000 }).catch(() => false);
if (gotToken) {
outLog('SUCCESS: StudioPortal received token via postMessage or redirect.');
} else {
outLog('FAIL: StudioPortal did not report token received within timeout.');
// print some page content for debugging
const snapshotText = await page.evaluate(() => document.body ? document.body.innerText.slice(0, 2000) : '');
outLog('Page snapshot:', snapshotText);
if (OUT_DIR) {
try {
const html = await page.content();
fs.writeFileSync(path.join(OUT_DIR, `page_${sessionId || 'noid'}.html`), html);
outLog('Saved full page HTML to', path.join(OUT_DIR, `page_${sessionId || 'noid'}.html`));
} catch (e) { outLog('Failed to save page HTML', String(e)); }
}
if (OUT_DIR) {
try {
await page.screenshot({ path: path.join(OUT_DIR, `e2e_${sessionId || 'noid'}.png`), fullPage: true });
outLog('Saved screenshot to', path.join(OUT_DIR, `e2e_${sessionId || 'noid'}.png`));
} catch (e) { outLog('Failed to save screenshot', e && e.stack ? e.stack : String(e)); }
}
process.exit(5);
}
// Optionally wait for connection attempt log
const connected = await page.waitForFunction(() => {
return document.body && document.body.innerText && document.body.innerText.includes('Conectado');
}, { timeout: 10000 }).catch(() => false);
outLog('Connected flag on StudioPortal:', !!connected);
outLog('E2E finished');
if (OUT_DIR) {
try {
await page.screenshot({ path: path.join(OUT_DIR, `e2e_${sessionId || 'noid'}.png`), fullPage: true });
outLog('Saved screenshot to', path.join(OUT_DIR, `e2e_${sessionId || 'noid'}.png`));
} catch (e) { outLog('Failed to save screenshot', e && e.stack ? e.stack : String(e)); }
}
await page.close();
} finally {
try { await browser.disconnect(); } catch(e){}
}
}
main().catch(err => { console.error('Unhandled error in main:', err && err.stack ? err.stack : String(err)); process.exit(99); });