223 lines
10 KiB
JavaScript
223 lines
10 KiB
JavaScript
#!/usr/bin/env node
|
|
/*
|
|
generate_visual_baseline.js
|
|
- Creates a session on the token server
|
|
- Connects to a remote Chrome with debugging enabled (default http://localhost:9222),
|
|
or to browserless via WS/CDP using puppeteer-core if remote-debug is not available
|
|
- Navigates to studioUrl and posts token if present
|
|
- Saves a screenshot to e2e/baseline/studio.png and e2e/out/<timestamp>/studio.png
|
|
*/
|
|
|
|
// Diagnostic helpers early to surface environment issues quickly
|
|
try {
|
|
console.log('=== generate_visual_baseline start ===');
|
|
console.log('cwd:', process.cwd());
|
|
console.log('node version:', process.version);
|
|
console.log('SCRIPT: generate_visual_baseline.js');
|
|
console.log('ENV: REMOTE_DEBUG, REMOTE_DEBUG_WS, BROWSERLESS_WS, BROWSERLESS_TOKEN, TOKEN_SERVER, BROADCAST_URL');
|
|
console.log('REMOTE_DEBUG=', process.env.REMOTE_DEBUG, 'REMOTE_DEBUG_WS=', process.env.REMOTE_DEBUG_WS || process.env.REMOTE_WS, 'REMOTE_DEBUG_PORT=', process.env.REMOTE_DEBUG_PORT);
|
|
console.log('BROWSERLESS_WS=', process.env.BROWSERLESS_WS, 'BROWSERLESS_TOKEN=', !!process.env.BROWSERLESS_TOKEN);
|
|
console.log('TOKEN_SERVER=', process.env.TOKEN_SERVER || process.env.BACKEND);
|
|
console.log('BROADCAST_URL=', process.env.BROADCAST_URL);
|
|
} catch (e) { /* ignore */ }
|
|
|
|
process.on('uncaughtException', (err) => {
|
|
console.error('UNCAUGHT EXCEPTION:', err && err.stack ? err.stack : err);
|
|
process.exit(2);
|
|
});
|
|
process.on('unhandledRejection', (reason) => {
|
|
console.error('UNHANDLED REJECTION:', reason && reason.stack ? reason.stack : reason);
|
|
process.exit(2);
|
|
});
|
|
|
|
const fetch = global.fetch ? global.fetch : (...args) => import('node-fetch').then(m => m.default(...args));
|
|
const puppeteer = require('puppeteer-core');
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
// Enhanced remote debug resolution: support REMOTE_DEBUG_ADDRESS + REMOTE_DEBUG_PORT
|
|
const REMOTE_DEBUG_ADDRESS = process.env.REMOTE_DEBUG_ADDRESS || process.env.REMOTE_DEBUG_HOST || 'localhost';
|
|
const REMOTE_DEBUG_PORT = process.env.REMOTE_DEBUG_PORT || process.env.REMOTE_PORT || '';
|
|
let REMOTE_DEBUG_WS = process.env.REMOTE_WS || process.env.REMOTE_DEBUG || '';
|
|
if (!REMOTE_DEBUG_WS) {
|
|
if (REMOTE_DEBUG_PORT) {
|
|
REMOTE_DEBUG_WS = `http://${REMOTE_DEBUG_ADDRESS}:${REMOTE_DEBUG_PORT}`;
|
|
} else {
|
|
REMOTE_DEBUG_WS = 'http://localhost:9222';
|
|
}
|
|
}
|
|
|
|
const BROWSERLESS_WS = process.env.BROWSERLESS_WS || process.env.REMOTE_WSE || 'wss://browserless.bfzqqk.easypanel.host';
|
|
const BROWSERLESS_TOKEN = process.env.BROWSERLESS_TOKEN || process.env.BROWSERLESS_KEY || process.env.BROWSERLESS || '';
|
|
const TOKEN_SERVER = process.env.TOKEN_SERVER || process.env.BACKEND || 'https://avanzacast-servertokens.bfzqqk.easypanel.host';
|
|
// Cambiar BROADCAST default para seguir la estructura rooms/<room>
|
|
const BROADCAST_BASE = process.env.BROADCAST_BASE || process.env.STUDIO_BASE || 'http://localhost:5175';
|
|
const ROOM = process.env.ROOM || 'iuqiw-aksjka';
|
|
const BROADCAST = process.env.BROADCAST_URL || `${BROADCAST_BASE.replace(/\/$/, '')}/rooms/${encodeURIComponent(ROOM)}`;
|
|
const CDP_RESOLVE_RETRIES = Number(process.env.CDP_RESOLVE_RETRIES || 6);
|
|
const CDP_RESOLVE_INTERVAL = Number(process.env.CDP_RESOLVE_INTERVAL_MS || 2000);
|
|
|
|
function log(...args){
|
|
try { console.log(...args); } catch (e) {}
|
|
try {
|
|
const outdir = path.join(process.cwd(), 'e2e', 'out');
|
|
try { fs.mkdirSync(outdir, { recursive: true }); } catch(e) {}
|
|
const logfile = path.join(outdir, 'generate_visual_baseline.log');
|
|
try { fs.appendFileSync(logfile, `[${new Date().toISOString()}] ${args.map(a => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ')}\n`); } catch(e) {}
|
|
} catch (e) {}
|
|
}
|
|
|
|
async function createSession(){
|
|
log('Creating session on token server', TOKEN_SERVER, ROOM);
|
|
const res = await fetch(`${TOKEN_SERVER.replace(/\/$/, '')}/api/session`, {
|
|
method: 'POST', headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ room: ROOM, username: 'visual-runner', ttl: 300 }),
|
|
});
|
|
const text = await res.text(); try { return JSON.parse(text); } catch(e){ return null; }
|
|
}
|
|
|
|
async function resolveRemoteWSEndpoint(raw){
|
|
if (!raw) return null;
|
|
raw = String(raw).trim();
|
|
// if already a ws:// or wss:// endpoint, return as-is
|
|
if (raw.startsWith('ws://') || raw.startsWith('wss://')) return raw;
|
|
// if given as a numeric port, assume localhost:port
|
|
if (/^\d+$/.test(raw)) raw = `http://localhost:${raw}`;
|
|
// if it's missing scheme, assume http
|
|
if (!raw.startsWith('http://') && !raw.startsWith('https://')) raw = `http://${raw}`;
|
|
try{
|
|
const ver = await fetch(raw.replace(/\/$/, '') + '/json/version');
|
|
if (ver && ver.ok){ const j = await ver.json(); if (j.webSocketDebuggerUrl) return j.webSocketDebuggerUrl; }
|
|
}catch(e){ log('resolveRemoteWSEndpoint /json/version error', String(e)); }
|
|
try{
|
|
const list = await fetch(raw.replace(/\/$/, '') + '/json/list');
|
|
if (list && list.ok){ const arr = await list.json(); if (Array.isArray(arr) && arr.length && arr[0].webSocketDebuggerUrl) return arr[0].webSocketDebuggerUrl; }
|
|
}catch(e){ log('resolveRemoteWSEndpoint /json/list error', String(e)); }
|
|
try{
|
|
const j = await fetch(raw.replace(/\/$/, '') + '/json');
|
|
if (j && j.ok){ const arr = await j.json(); if (Array.isArray(arr) && arr.length && arr[0].webSocketDebuggerUrl) return arr[0].webSocketDebuggerUrl; }
|
|
}catch(e){ log('resolveRemoteWSEndpoint /json error', String(e)); }
|
|
return null;
|
|
}
|
|
|
|
async function waitForCDP(raw, retries = CDP_RESOLVE_RETRIES, interval = CDP_RESOLVE_INTERVAL){
|
|
let attempt = 0;
|
|
while(attempt < retries){
|
|
attempt++;
|
|
try{
|
|
log(`CDP resolve attempt ${attempt}/${retries} for ${raw}`);
|
|
const resolved = await resolveRemoteWSEndpoint(raw);
|
|
if (resolved) return resolved;
|
|
}catch(e){ log('waitForCDP attempt error', String(e)); }
|
|
await new Promise(r => setTimeout(r, interval));
|
|
}
|
|
return null;
|
|
}
|
|
|
|
(async ()=>{
|
|
try{
|
|
const session = await createSession();
|
|
if (!session) throw new Error('Failed to create session');
|
|
log('Session created:', session.id, 'studioUrl=', session.studioUrl || session.url);
|
|
const studioUrl = session.studioUrl || session.redirectUrl || session.url || BROADCAST;
|
|
let token = session.token || null;
|
|
if (!token && session.id) {
|
|
// try GET
|
|
try{
|
|
const getResp = await fetch(`${TOKEN_SERVER.replace(/\/$/, '')}/api/session/${encodeURIComponent(session.id)}`);
|
|
const txt = await getResp.text(); const json = JSON.parse(txt);
|
|
if (json && json.token) token = json.token;
|
|
}catch(e){ log('GET session token failed', String(e)); }
|
|
}
|
|
|
|
// Try to resolve CDP endpoint from remote debug first
|
|
let connectEndpoint = null;
|
|
if (REMOTE_DEBUG_WS) {
|
|
try{
|
|
const resolved = await waitForCDP(REMOTE_DEBUG_WS);
|
|
if (resolved) {
|
|
connectEndpoint = resolved;
|
|
log('Resolved remote-debug CDP endpoint:', connectEndpoint);
|
|
} else {
|
|
log('No CDP endpoint discovered at remote-debug URL after retries:', REMOTE_DEBUG_WS);
|
|
}
|
|
} catch(e){ log('Error resolving remote-debug endpoint', String(e)); }
|
|
}
|
|
|
|
// If remote debug not available, try browserless
|
|
if (!connectEndpoint && BROWSERLESS_WS) {
|
|
try{
|
|
if ((BROWSERLESS_WS.startsWith('ws://') || BROWSERLESS_WS.startsWith('wss://')) && BROWSERLESS_TOKEN) {
|
|
connectEndpoint = `${BROWSERLESS_WS}${BROWSERLESS_WS.includes('?') ? '&' : '?'}token=${encodeURIComponent(BROWSERLESS_TOKEN)}`;
|
|
} else {
|
|
connectEndpoint = await waitForCDP(BROWSERLESS_WS + (BROWSERLESS_TOKEN ? (BROWSERLESS_WS.includes('?') ? '&' : '?') + `token=${encodeURIComponent(BROWSERLESS_TOKEN)}` : ''));
|
|
if (!connectEndpoint) connectEndpoint = await waitForCDP(BROWSERLESS_WS);
|
|
}
|
|
log('Browserless resolution result:', connectEndpoint || '(none)');
|
|
}catch(e){ log('Browserless resolve error', String(e)); }
|
|
}
|
|
|
|
log('connectEndpoint resolved to', connectEndpoint || '(none)');
|
|
|
|
let browser = null;
|
|
if (connectEndpoint) {
|
|
try{
|
|
browser = await puppeteer.connect({ browserWSEndpoint: connectEndpoint, timeout: 30000, ignoreHTTPSErrors: true });
|
|
log('Connected to remote browser via puppeteer (CDP)');
|
|
}catch(e){
|
|
log('puppeteer.connect failed:', String(e));
|
|
browser = null;
|
|
}
|
|
}
|
|
|
|
if (!browser) {
|
|
log('Launching local puppeteer fallback');
|
|
browser = await puppeteer.launch({ headless: true, args: ['--no-sandbox'] });
|
|
}
|
|
|
|
const page = await browser.newPage();
|
|
page.on('console', msg => { try{ log('[BROWSER]', msg.type(), msg.text()); } catch(e){} });
|
|
page.on('pageerror', err => log('[PAGEERROR]', err && err.stack ? err.stack : String(err)));
|
|
|
|
log('Navigating to', studioUrl);
|
|
await page.goto(studioUrl, { waitUntil: 'networkidle2' }).catch(e=>{ log('page.goto failed', String(e)); });
|
|
await page.waitForTimeout(1200);
|
|
|
|
if (token) {
|
|
try{
|
|
log('Posting token to page via postMessage (length', token.length, ')');
|
|
await page.evaluate((tk)=>{ try{ window.postMessage({ type:'LIVEKIT_TOKEN', token: tk, room: '', url: window.location.href }, window.location.origin); } catch(e){} }, token);
|
|
}catch(e){ log('postMessage evaluate failed', String(e)); }
|
|
}
|
|
|
|
await page.waitForTimeout(1200);
|
|
|
|
const baselineDir = path.join(process.cwd(), 'e2e', 'baseline');
|
|
try{ fs.mkdirSync(baselineDir, { recursive: true }); } catch(e){}
|
|
const outDir = path.join(process.cwd(), 'e2e', 'out', `visual_${Date.now()}`);
|
|
try{ fs.mkdirSync(outDir, { recursive: true }); } catch(e){}
|
|
const baselinePath = path.join(baselineDir, 'studio.png');
|
|
const outPath = path.join(outDir, 'studio.png');
|
|
|
|
await page.screenshot({ path: outPath, fullPage: false });
|
|
log('Saved capture to', outPath);
|
|
|
|
// Copy to baseline if not exists or if FORCE_BASELINE=1
|
|
const force = process.env.FORCE_BASELINE === '1';
|
|
if (!fs.existsSync(baselinePath) || force) {
|
|
try{ fs.copyFileSync(outPath, baselinePath); log('Wrote baseline to', baselinePath); } catch(e){ log('Failed writing baseline', String(e)); }
|
|
} else {
|
|
log('Baseline exists at', baselinePath, '(not overwritten)');
|
|
}
|
|
|
|
try{ await page.close(); } catch(e){}
|
|
try{ await browser.close(); } catch(e){}
|
|
|
|
log('Done');
|
|
process.exit(0);
|
|
}catch(err){
|
|
console.error('Fatal', err && err.stack ? err.stack : err);
|
|
process.exit(2);
|
|
}
|
|
})();
|