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