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