- 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
195 lines
8.6 KiB
JavaScript
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);
|
|
}
|
|
})();
|