#!/usr/bin/env node // E2E script: connect to remote Chrome on localhost:9222 and run token flow (create session -> open Broadcast Panel -> Entrar al Estudio) // Usage: BROWSERLESS_WS not used. Instead set REMOTE_DEBUGGER_URL (e.g. http://localhost:9222) or default to http://localhost:9222 // Environment variables: // TOKEN_SERVER (default: https://avanzacast-servertokens.bfzqqk.easypanel.host) // ROOM, USERNAME // OUT_DIR to save artifacts const fetch = require('node-fetch'); const puppeteer = require('puppeteer-core'); const fs = require('fs'); const path = require('path'); async function main() { const REMOTE_DEBUGGER = process.env.REMOTE_DEBUGGER_URL || 'http://localhost:9222'; 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 || '/tmp'; function outLog(...args) { console.log(...args); if (OUT_DIR) { try { fs.appendFileSync(path.join(OUT_DIR, 'e2e_remote_9222.log'), args.map(String).join(' ') + '\n'); } catch (e) {} } } outLog('E2E remote 9222 starting', { REMOTE_DEBUGGER, TOKEN_SERVER, ROOM, USERNAME, OUT_DIR }); // Create session on token server let sessResp; try { sessResp = 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 when creating session:', String(err)); process.exit(3); } outLog('Token server status', sessResp.status); let sessBodyText = await sessResp.text().catch(() => ''); let sessJson = null; try { sessJson = JSON.parse(sessBodyText); } catch(e) { sessJson = null } outLog('Session response body start:', String(sessBodyText).slice(0,400)); if (!sessResp.ok) { outLog('Failed to create session'); process.exit(4); } if (!sessJson) { outLog('Invalid JSON from token server'); process.exit(5); } const sessionId = sessJson.id || sessJson.sessionId || null; const token = sessJson.token || null; const studioUrl = sessJson.studioUrl || sessJson.redirectUrl || sessJson.url || null; outLog('Session created', { sessionId: sessionId ? sessionId.slice(0,10) + '...' : null, studioUrl, hasToken: !!token }); // Connect to remote Chrome DevTools // We expect a running Chrome with --remote-debugging-port=9222. puppeteer.connect accepts websocket endpoint; fetch the ws endpoint from /json/version let wsEndpoint; try { const versionResp = await fetch(`${REMOTE_DEBUGGER.replace(/\/$/, '')}/json/version`); const verJson = await versionResp.json(); wsEndpoint = verJson.webSocketDebuggerUrl || null; } catch (err) { outLog('Failed to query remote debugger /json/version:', String(err)); process.exit(6); } if (!wsEndpoint) { outLog('Remote debugger did not return webSocketDebuggerUrl. Check Chrome is running with --remote-debugging-port=9222'); process.exit(7); } outLog('Connecting to remote chrome wsEndpoint', wsEndpoint); let browser; try { browser = await puppeteer.connect({ browserWSEndpoint: wsEndpoint, defaultViewport: { width: 1366, height: 768 }, timeout: 20000 }); } catch (err) { outLog('puppeteer.connect failed:', String(err)); process.exit(8); } try { const page = await browser.newPage(); page.on('console', msg => outLog('[BROWSER]', msg.type(), msg.text())); page.on('pageerror', err => outLog('[PAGEERROR]', String(err))); // Navigate to either the studioUrl returned or to broadcast panel root and click 'Entrar al Estudio' let targetUrl = studioUrl; if (!targetUrl && sessionId) { // use broadcast panel host from environment or default const BROADCAST_HOST = process.env.BROADCAST_HOST || 'https://avanzacast-broadcastpanel.bfzqqk.easypanel.host'; targetUrl = `${BROADCAST_HOST.replace(/\/$/, '')}/${encodeURIComponent(sessionId)}`; } if (!targetUrl) { outLog('No target URL to open'); process.exit(9); } outLog('Opening target URL:', targetUrl); await page.goto(targetUrl, { waitUntil: 'domcontentloaded', timeout: 30000 }).catch(err => { outLog('page.goto error', String(err)); throw err; }); // If page has a visible button/link text 'Entrar' or 'Entrar al Estudio', click it const clickSelectors = ["button[aria-label='Entrar al Estudio']","button:has-text('Entrar al Estudio')","button:has-text('Entrar')","a:has-text('Entrar al Estudio')","a:has-text('Entrar')"]; let clicked = false; for (const sel of clickSelectors) { try { // Puppeteer does not support :has-text in older versions; use evaluate to find by text const found = await page.evaluate((text) => { const els = Array.from(document.querySelectorAll('button,a')); const el = els.find(e => (e.innerText || '').trim().toLowerCase().includes(text.toLowerCase())); if (el) { el.scrollIntoView(); el.click(); return true; } return false; }, sel.replace(/.*:has-text\('(.+)'\).*/, '$1')); if (found) { clicked = true; outLog('Clicked element matching', sel); break; } } catch (e) { /* ignore */ } } // If token is present, postMessage it if (token) { outLog('Posting token via postMessage (len=' + String(token.length) + ')'); try { await page.evaluate((tk) => { window.postMessage({ type: 'LIVEKIT_TOKEN', token: tk, url: window.location.href }, window.location.origin); }, token); } catch (e) { outLog('postMessage failed', String(e)); } } else { outLog('No token present in session response; relying on redirect flow'); } // Wait for token received indicator in page const gotToken = await page.waitForFunction(() => document.body && document.body.innerText && (document.body.innerText.includes('Token recibido') || document.body.innerText.includes('Token recibido desde Broadcast Panel') || document.body.innerText.includes('Esperando token')), { timeout: 10000 }).catch(() => false); outLog('Token indicator found:', !!gotToken); // Save artifacts if (OUT_DIR) { try { const html = await page.content(); fs.writeFileSync(path.join(OUT_DIR, `page_${sessionId || 'noid'}.html`), html); outLog('Saved page HTML'); } catch(e) { outLog('Failed to save HTML', String(e)); } try { await page.screenshot({ path: path.join(OUT_DIR, `e2e_${sessionId || 'noid'}.png`), fullPage: true }); outLog('Saved screenshot'); } catch(e) { outLog('Failed to save screenshot', String(e)); } } await page.close(); } finally { try { await browser.disconnect(); } catch(e) {} } outLog('E2E remote 9222 finished'); } main().catch(err => { console.error('Unhandled error in main:', err && err.stack ? err.stack : String(err)); process.exit(99); });