// e2e/streamyard-flow-browserless.js // Puppeteer script to run StreamYard flow via remote browserless WebSocket // Usage: // npm install --save-dev puppeteer-core // BROWSERLESS_WS=wss://browserless.bfzqqk.easypanel.host node e2e/streamyard-flow-browserless.js 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 || 'wss://browserless.bfzqqk.easypanel.host'; const token = process.env.BROWSERLESS_TOKEN || process.env.BROWSERLESS_API_KEY || null; // If token provided and WS url doesn't already have query params, append it try { if (token) { const hasQuery = ws.includes('?'); ws = hasQuery ? `${ws}&token=${encodeURIComponent(token)}` : `${ws}?token=${encodeURIComponent(token)}`; } } catch (e) { // ignore URL building errors and use raw ws } 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) }); }); // Helper: perform login if STREAMYARD_EMAIL and STREAMYARD_PASSWORD are provided async function tryLoginIfNeeded() { const email = process.env.STREAMYARD_EMAIL || process.env.STREAMYARD_USER || null; const password = process.env.STREAMYARD_PASSWORD || process.env.STREAMYARD_PASS || null; if (!email || !password) return false; const loginUrls = ['https://streamyard.com/signin', 'https://streamyard.com/login', 'https://streamyard.com/', 'https://app.streamyard.com/login']; console.log('Attempting StreamYard login for', email); for (const url of loginUrls) { try { await page.goto(url, { waitUntil: 'networkidle2', timeout: 15000 }).catch(() => null); // Try to find email input const emailSelector = await Promise.race([ page.waitForSelector('input[type="email"]', { timeout: 3000 }).then(() => 'input[type="email"]').catch(() => null), page.waitForSelector('input[name="email"]', { timeout: 3000 }).then(() => 'input[name="email"]').catch(() => null), page.waitForSelector('input[id*="email"]', { timeout: 3000 }).then(() => 'input[id*="email"]').catch(() => null), ]).catch(() => null); if (!emailSelector) { // Maybe there's a button to open login modal const accountBtn = await page.$x("//button[contains(normalize-space(.), 'Mi cuenta') or contains(normalize-space(.), 'Sign in') or contains(normalize-space(.), 'Inicia sesión')]"); if (accountBtn && accountBtn.length) { try { await accountBtn[0].click(); await page.waitForTimeout(1200); } catch(e){} } } // After clicking or direct page, try to find inputs again const finalEmailSelector = await Promise.race([ page.waitForSelector('input[type="email"]', { timeout: 3000 }).then(() => 'input[type="email"]').catch(() => null), page.waitForSelector('input[name="email"]', { timeout: 3000 }).then(() => 'input[name="email"]').catch(() => null), page.waitForSelector('input[id*="email"]', { timeout: 3000 }).then(() => 'input[id*="email"]').catch(() => null), ]).catch(() => null); const passSelector = await Promise.race([ page.waitForSelector('input[type="password"]', { timeout: 3000 }).then(() => 'input[type="password"]').catch(() => null), page.waitForSelector('input[name="password"]', { timeout: 3000 }).then(() => 'input[name="password"]').catch(() => null), page.waitForSelector('input[id*="password"]', { timeout: 3000 }).then(() => 'input[id*="password"]').catch(() => null), ]).catch(() => null); if (finalEmailSelector && passSelector) { try { await page.fill(finalEmailSelector, email); await page.fill(passSelector, password); // Try pressing Enter in password field await page.focus(passSelector); await page.keyboard.press('Enter'); // Wait for navigation or dashboard indicator await page.waitForTimeout(2000); // Wait for a known dashboard element (Transmisiones y grabaciones) or redirect to /broadcasts try { await Promise.race([ page.waitForURL('**/broadcasts', { timeout: 8000 }), page.waitForSelector('text=Transmisiones y grabaciones', { timeout: 8000 }) ]); console.log('Login appears successful (navigated to broadcasts)'); return true; } catch (e) { console.warn('Login attempt did not detect broadcasts page yet, continuing'); } } catch (e) { console.warn('Login fill/submit failed on', url, e && e.message); } } } catch (e) { // continue to next console.warn('Login attempt at', url, 'error', e && e.message); } } console.warn('Automatic login did not succeed'); return false; } try { const startUrl = 'https://streamyard.com/'; // If credentials are provided, try to authenticate first await tryLoginIfNeeded(); console.log('goto', startUrl); await page.goto(startUrl, { waitUntil: 'networkidle2' }); await page.waitForTimeout(1500); // Find elements with exact text 'Entrar al estudio' const anchors = await page.$x("//a[contains(normalize-space(.), 'Entrar al estudio')]"); const buttons = await page.$x("//button[contains(normalize-space(.), 'Entrar al estudio')]"); const elems = anchors.concat(buttons); console.log('found', elems.length, 'elements'); results.found = elems.length; for (let i = 0; i < elems.length; i++) { const el = elems[i]; let beforeHref = null; try { beforeHref = await (await el.getProperty('href')).jsonValue().catch(() => null); } catch(e) { beforeHref = null; } const navRec = { index: i, beforeHref, attempts: [] }; let clicked = false; for (let attempt = 1; attempt <= 3; attempt++) { const att = { attempt, timestamp: new Date().toISOString() }; try { // Start waiting for navigation or a short timeout const navPromise = page.waitForNavigation({ waitUntil: 'networkidle2', timeout: 10000 }).catch(() => null); await el.click({ delay: 50 }); const nav = await navPromise; att.afterUrl = page.url(); att.navigated = !!nav; att.ok = true; navRec.attempts.push(att); clicked = true; // if navigated, go back to start for next element if (nav) { await page.waitForTimeout(800); await page.goto(startUrl, { waitUntil: 'networkidle2' }); await page.waitForTimeout(800); } break; } catch (err) { att.ok = false; att.error = String(err.message || err); navRec.attempts.push(att); await page.waitForTimeout(700); } } navRec.success = clicked; results.navigations.push(navRec); } const screenshotPath = path.join(outDir, 'streamyard_flow_browserless.png'); await page.screenshot({ path: screenshotPath, fullPage: true }); results.screenshot = screenshotPath; results.endedAt = new Date().toISOString(); const jsonOut = path.join(outDir, 'streamyard-flow-browserless-result.json'); fs.writeFileSync(jsonOut, JSON.stringify(results, null, 2)); console.log('Wrote results to', jsonOut); await page.close(); try { await browser.disconnect(); } catch(e) { try { await browser.close(); } catch(e){} } process.exit(0); } catch (err) { console.error('Error during flow', err); results.error = String(err && err.stack ? err.stack : err); results.endedAt = new Date().toISOString(); fs.writeFileSync(path.join(outDir, 'streamyard-flow-browserless-result.json'), JSON.stringify(results, null, 2)); try { await page.close(); } catch(e){} try { await browser.disconnect(); } catch(e) { try { await browser.close(); } catch(e){} } process.exit(1); } })();