AvanzaCast/e2e/validate-flow-browserless.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

160 lines
6.8 KiB
JavaScript

// e2e/validate-flow-browserless.js
// Connects to a browserless WebSocket endpoint and validates the BroadcastPanel -> Studio flow
// Expects env vars:
// BROWSERLESS_WS (wss://...)
// BROWSERLESS_TOKEN (optional)
// VITE_BROADCASTPANEL_URL or BROADCAST_URL
// VITE_STUDIO_URL or STUDIO_URL (optional, fallback to page's url)
// TOKEN or E2E_TOKEN (token to pass to studio)
const fs = require('fs');
const path = require('path');
const puppeteer = require('puppeteer-core');
(async () => {
const outDir = path.resolve(__dirname);
const results = { startedAt: new Date().toISOString(), console: [], navigations: [] };
let ws = process.env.BROWSERLESS_WS || process.env.BWS || '';
const btoken = process.env.BROWSERLESS_TOKEN || process.env.BWS_TOKEN || '';
try {
if (ws && btoken) {
const hasQuery = ws.includes('?');
ws = hasQuery ? `${ws}&token=${encodeURIComponent(btoken)}` : `${ws}?token=${encodeURIComponent(btoken)}`;
}
} catch (e) {}
if (!ws) {
console.error('BROWSERLESS_WS is required');
process.exit(2);
}
const BROADCAST_URL = process.env.VITE_BROADCASTPANEL_URL || process.env.BROADCAST_URL || '';
const STUDIO_URL = process.env.VITE_STUDIO_URL || process.env.STUDIO_URL || null;
const TOKEN = process.env.TOKEN || process.env.E2E_TOKEN || '';
console.log('Connecting to browserless at', ws);
let browser;
try {
browser = await puppeteer.connect({ browserWSEndpoint: ws, ignoreHTTPSErrors: true, defaultViewport: { width: 1366, height: 768 } });
} catch (err) {
console.error('Failed to connect to browserless:', err && err.message ? err.message : err);
process.exit(2);
}
const page = await browser.newPage();
page.setDefaultNavigationTimeout(30000);
page.on('console', msg => { try { results.console.push({ type: msg.type(), text: msg.text() }); } catch (e) {} });
page.on('pageerror', err => { results.console.push({ type: 'pageerror', text: String(err && err.stack ? err.stack : err) }); });
try {
if (!BROADCAST_URL) {
console.error('BROADCAST_URL (VITE_BROADCASTPANEL_URL) is required');
await browser.disconnect();
process.exit(2);
}
await page.goto(BROADCAST_URL, { waitUntil: 'networkidle2' });
await page.waitForTimeout(1500);
const texts = ['Entrar al estudio', 'Entrar al Studio', 'Entrar al Estudio', 'Entrar al Studio', 'Enter studio', 'Enter the studio', 'Enter the studio'];
let clicked = false;
for (const t of texts) {
const els = await page.$x(`//a[contains(normalize-space(.), '${t}')] | //button[contains(normalize-space(.), '${t}')] | //span[contains(normalize-space(.), '${t}')]`);
if (els && els.length) {
console.log('Found element for text:', t, 'count=', els.length);
try {
// Try click and wait for targetcreated or navigation
const popupPromise = new Promise(resolve => {
const onTarget = target => { resolve(target); };
browser.once('targetcreated', onTarget);
setTimeout(() => { try { browser.removeListener('targetcreated', onTarget) } catch(e){}; resolve(null); }, 3000);
});
await els[0].click({ delay: 50 }).catch(() => null);
const popupTarget = await popupPromise;
let studioPage = null;
if (popupTarget) {
try { studioPage = await popupTarget.page(); } catch (e) { studioPage = null; }
}
if (!studioPage) {
await page.waitForTimeout(800);
const url = page.url();
if (url.includes('/studio') || url.includes('studio') || url.includes('avanzacast-studio')) studioPage = page;
}
if (studioPage) {
console.log('Studio page found; navigating with token...');
const targetStudioUrl = STUDIO_URL ? `${STUDIO_URL}?token=${encodeURIComponent(TOKEN)}` : `${studioPage.url().split('?')[0]}?token=${encodeURIComponent(TOKEN)}`;
await studioPage.goto(targetStudioUrl, { waitUntil: 'networkidle2' });
results.navigations.push({ type: 'studio_opened', url: studioPage.url() });
await studioPage.waitForTimeout(2500);
const shot = path.join(outDir, 'studio_flow_browserless_result.png');
await studioPage.screenshot({ path: shot, fullPage: true });
results.screenshot = shot;
clicked = true;
break;
} else {
console.log('Click did not open studio for text:', t);
}
} catch (err) {
console.warn('Click attempt error', err && err.message);
}
}
}
if (!clicked) {
// fallback: try alternative selectors
const altSel = 'a#enter-studio, button#enter-studio, a[data-enter-studio], button[data-enter-studio]';
try {
const alt = await page.$(altSel);
if (alt) {
console.log('Found alternative selector, clicking...');
await alt.click().catch(() => null);
await page.waitForTimeout(1000);
}
} catch (e) {}
// fallback navigate directly to studio URL with token
if (STUDIO_URL) {
console.log('Fallback: navigating directly to STUDIO_URL with token');
const directUrl = `${STUDIO_URL}?token=${encodeURIComponent(TOKEN)}`;
await page.goto(directUrl, { waitUntil: 'networkidle2' });
await page.waitForTimeout(1500);
const shot = path.join(outDir, 'studio_flow_browserless_result.png');
await page.screenshot({ path: shot, fullPage: true });
results.screenshot = shot;
results.navigations.push({ type: 'direct_studio', url: directUrl });
} else {
console.error('No studio opened and no STUDIO_URL provided for fallback.');
}
}
results.endedAt = new Date().toISOString();
const outJson = path.join(outDir, 'validate-flow-browserless-result.json');
fs.writeFileSync(outJson, JSON.stringify(results, null, 2));
console.log('Wrote results to', outJson);
// Publish artifact and append run summary to LOG.md
try {
const { publishArtifact, appendLog } = require('./logging');
const artifactUrl = publishArtifact(outJson, 'validate-flow-browserless') || null;
appendLog('validate-flow-browserless', outJson, results, artifactUrl);
} catch (e) { console.warn('Failed to write LOG.md entry', e); }
try { await browser.disconnect(); } catch(e) { try { await browser.close(); } catch(e){} }
process.exit(0);
} catch (err) {
console.error('Error validating flow', err && err.stack ? err.stack : err);
results.error = String(err && err.stack ? err.stack : err);
results.endedAt = new Date().toISOString();
fs.writeFileSync(path.join(outDir, 'validate-flow-browserless-result.json'), JSON.stringify(results, null, 2));
try { await browser.disconnect(); } catch(e) { try { await browser.close(); } catch(e){} }
process.exit(1);
}
})();