// e2e/validate-flow-domains-local.js // Local Puppeteer script to validate studio opening flow for AvanzaCast // - Navigates to BROADCAST_URL (VITE_BROADCASTPANEL_URL) // - Clicks 'Entrar al estudio' (or opens studio route) // - Opens or navigates the Studio Portal with provided TOKEN and LIVEKIT WS URL // - Captures logs and a screenshot and writes a result JSON const fs = require('fs'); const path = require('path'); const puppeteer = require('puppeteer'); (async () => { const outDir = path.resolve(__dirname); const results = { startedAt: new Date().toISOString(), console: [], navigations: [] }; 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; const LIVEKIT_WS = process.env.VITE_LIVEKIT_WS_URL || process.env.LIVEKIT_WS; const TOKEN_SERVER = process.env.VITE_TOKEN_SERVER_URL || process.env.TOKEN_SERVER_URL; const HEADLESS = process.env.HEADLESS === '0' ? false : (process.env.HEADLESS === '1' ? true : true); console.log('HEADLESS:', HEADLESS); if (!BROADCAST_URL) { console.error('BROADCAST_URL (VITE_BROADCASTPANEL_URL) is required'); process.exit(2); } if (!TOKEN) { console.error('TOKEN env is required (the e2e token to pass to studio)'); process.exit(2); } console.log('Broadcast URL:', BROADCAST_URL); console.log('Studio URL:', STUDIO_URL || '(not provided)'); console.log('LiveKit WS:', LIVEKIT_WS || '(not provided)'); console.log('Token server:', TOKEN_SERVER || '(not provided)'); const browser = await puppeteer.launch({ headless: HEADLESS, args: ['--no-sandbox', '--disable-setuid-sandbox'] }); 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 { await page.goto(BROADCAST_URL, { waitUntil: 'networkidle2' }); await page.waitForTimeout(1200); // Try to find a button or link with text Entrar al estudio or Enter studio const texts = ['Entrar al estudio', 'Entrar al Studio', 'Enter studio', 'Enter the studio', 'Entrar al estudio']; let clicked = false; for (const t of texts) { const els = await page.$x(`//a[contains(normalize-space(.), '${t}')] | //button[contains(normalize-space(.), '${t}')]`); if (els && els.length) { console.log('Found element for text:', t); try { // attempt to click and wait for popup or navigation const popupPromise = new Promise(resolve => { const onTarget = target => { try { resolve(target); } catch (e) { resolve(null); } }; browser.once('targetcreated', onTarget); // safety timeout setTimeout(() => { browser.removeListener('targetcreated', onTarget); resolve(null); }, 3000); }); await els[0].click({ delay: 50 }).catch(() => null); const popupTarget = await popupPromise; // Check for new target (popup) let studioPage = null; if (popupTarget) { try { studioPage = await popupTarget.page(); } catch(e) { studioPage = null; } } // If no popup, maybe navigation in same page if (!studioPage) { // Wait for navigation or a url change indicating studio await page.waitForTimeout(800); const url = page.url(); if (url.includes('/studio') || url.includes('studio')) { studioPage = page; } } // If studioPage found and we have STUDIO_URL, force navigate with token if (studioPage) { console.log('Studio page found; navigating with token...'); const targetStudioUrl = STUDIO_URL ? `${STUDIO_URL}?token=${encodeURIComponent(TOKEN)}` : `${page.url().split('?')[0]}?token=${encodeURIComponent(TOKEN)}`; await studioPage.goto(targetStudioUrl, { waitUntil: 'networkidle2' }); results.navigations.push({ type: 'studio_opened', url: studioPage.url() }); // Wait a bit for LiveKit connect attempts (if any) await studioPage.waitForTimeout(2500); const shot = path.join(outDir, 'studio_flow_result.png'); await studioPage.screenshot({ path: shot, fullPage: true }); results.screenshot = shot; clicked = true; break; } else { console.log('Click did not open studio; will try next match'); } } catch (err) { console.warn('Click attempt error', err && err.message); } } } if (!clicked) { // Fallback: try a generic selector or direct navigation const altSel = 'a#enter-studio, button#enter-studio, a[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 with token if STUDIO_URL provided 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_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, 'studio-flow-domains-result.json'); fs.writeFileSync(outJson, JSON.stringify(results, null, 2)); console.log('Wrote results to', outJson); // Publish artifact (JSON + screenshot) and append run summary to LOG.md try { const { publishArtifact, appendLog } = require('./logging'); const artifactUrl = publishArtifact(outJson, 'validate-flow-domains-local') || null; appendLog('validate-flow-domains-local', outJson, results, artifactUrl); } catch (e) { console.warn('Failed to publish/appenda log entry', e); } await browser.close(); 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, 'studio-flow-domains-result.json'), JSON.stringify(results, null, 2)); try { await browser.close(); } catch(e){} process.exit(1); } })();