AvanzaCast/e2e/streamyard-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

195 lines
8.6 KiB
JavaScript

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