133 lines
5.7 KiB
TypeScript
133 lines
5.7 KiB
TypeScript
import { test, expect } from '@playwright/test';
|
|
import { chromium as pwChromium } from 'playwright';
|
|
|
|
// Defaults point to public deployments (overrideable via env)
|
|
const BACKEND = process.env.TOKEN_SERVER || process.env.BACKEND || 'https://avanzacast-servertokens.bfzqqk.easypanel.host';
|
|
const BROADCAST = process.env.BROADCAST_URL || 'https://avanzacast-broadcastpanel.bfzqqk.easypanel.host';
|
|
const ROOM = process.env.ROOM || `e2e-visual-${Date.now()}`;
|
|
const BROWSERLESS_WS = process.env.BROWSERLESS_WS || process.env.REMOTE_WS || '';
|
|
const BROWSERLESS_TOKEN = process.env.BROWSERLESS_TOKEN || process.env.BROWSERLESS_KEY || '';
|
|
|
|
async function createSession() {
|
|
const res = await fetch(`${BACKEND.replace(/\/$/, '')}/api/session`, {
|
|
method: 'POST',
|
|
headers: { 'Content-Type': 'application/json' },
|
|
body: JSON.stringify({ room: ROOM, username: 'visual-runner', ttl: 300 }),
|
|
});
|
|
const text = await res.text();
|
|
try { return JSON.parse(text); } catch (e) { return null; }
|
|
}
|
|
|
|
async function resolveRemoteWSEndpoint(raw: string | undefined) {
|
|
if (!raw) return null;
|
|
let r = String(raw).trim();
|
|
// if starts with ws or wss, return as-is (may still need token appended)
|
|
if (r.startsWith('ws://') || r.startsWith('wss://')) return r;
|
|
// numeric port -> assume localhost
|
|
if (/^\d+$/.test(r)) r = `http://localhost:${r}`;
|
|
if (!r.startsWith('http://') && !r.startsWith('https://')) r = `http://${r}`;
|
|
try {
|
|
const ver = await fetch(r.replace(/\/$/, '') + '/json/version');
|
|
if (ver && ver.ok) {
|
|
const j = await ver.json();
|
|
if (j.webSocketDebuggerUrl) return j.webSocketDebuggerUrl;
|
|
}
|
|
} catch (e) { /* ignore */ }
|
|
try {
|
|
const list = await fetch(r.replace(/\/$/, '') + '/json/list');
|
|
if (list && list.ok) {
|
|
const arr = await list.json();
|
|
if (Array.isArray(arr) && arr.length && arr[0].webSocketDebuggerUrl) return arr[0].webSocketDebuggerUrl;
|
|
}
|
|
} catch (e) { /* ignore */ }
|
|
try {
|
|
const j = await fetch(r.replace(/\/$/, '') + '/json');
|
|
if (j && j.ok) {
|
|
const arr = await j.json();
|
|
if (Array.isArray(arr) && arr.length && arr[0].webSocketDebuggerUrl) return arr[0].webSocketDebuggerUrl;
|
|
}
|
|
} catch (e) { /* ignore */ }
|
|
return null;
|
|
}
|
|
|
|
test('Studio visual snapshot (browserless-aware)', async () => {
|
|
const session = await createSession();
|
|
const target = session && session.studioUrl ? session.studioUrl : BROADCAST;
|
|
|
|
// Create browser/page either via browserless CDP or local chromium
|
|
let browser: any = null;
|
|
let context: any = null;
|
|
let page: any = null;
|
|
let usedRemote = false;
|
|
|
|
try {
|
|
if (BROWSERLESS_WS) {
|
|
// try resolve to webSocketDebuggerUrl (CDP)
|
|
let connectEndpoint: string | null = null;
|
|
// if BROWSERLESS_WS already contains token param, use as-is; otherwise append token
|
|
if ((BROWSERLESS_WS.startsWith('ws://') || BROWSERLESS_WS.startsWith('wss://')) && BROWSERLESS_TOKEN) {
|
|
connectEndpoint = `${BROWSERLESS_WS}${BROWSERLESS_WS.includes('?') ? '&' : '?'}token=${encodeURIComponent(BROWSERLESS_TOKEN)}`;
|
|
} else {
|
|
// try to resolve via http endpoints
|
|
connectEndpoint = await resolveRemoteWSEndpoint(BROWSERLESS_WS + (BROWSERLESS_TOKEN ? (BROWSERLESS_WS.includes('?') ? '&' : '?') + `token=${encodeURIComponent(BROWSERLESS_TOKEN)}` : ''));
|
|
if (!connectEndpoint) connectEndpoint = await resolveRemoteWSEndpoint(BROWSERLESS_WS);
|
|
}
|
|
|
|
if (connectEndpoint) {
|
|
// connect via CDP
|
|
try {
|
|
browser = await pwChromium.connectOverCDP(connectEndpoint, { timeout: 20000 });
|
|
context = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
|
page = await context.newPage();
|
|
usedRemote = true;
|
|
console.log('Connected to remote browser via CDP:', connectEndpoint);
|
|
} catch (err) {
|
|
console.warn('connectOverCDP failed, will fallback to local chromium:', String(err));
|
|
}
|
|
} else {
|
|
console.warn('Could not resolve remote CDP endpoint for', BROWSERLESS_WS);
|
|
}
|
|
}
|
|
|
|
if (!page) {
|
|
// fallback: launch local chromium
|
|
browser = await pwChromium.launch({ headless: true });
|
|
context = await browser.newContext({ viewport: { width: 1280, height: 800 } });
|
|
page = await context.newPage();
|
|
console.log('Launched local chromium for visual test');
|
|
}
|
|
|
|
console.log('Navigating to', target);
|
|
await page.goto(target, { waitUntil: 'networkidle' });
|
|
|
|
// Wait a bit for UI to settle
|
|
await page.waitForTimeout(1500);
|
|
|
|
// Attempt to post message if we have token
|
|
if (session && session.token) {
|
|
try {
|
|
await page.evaluate((tk) => {
|
|
window.postMessage({ type: 'LIVEKIT_TOKEN', token: tk, room: '', url: window.location.href }, window.location.origin);
|
|
}, session.token);
|
|
} catch (e) { console.warn('postMessage failed', e); }
|
|
}
|
|
|
|
// Wait for connection indicators or token received text
|
|
await page.waitForTimeout(1500);
|
|
|
|
// Take screenshot and compare to baseline
|
|
const now = Date.now();
|
|
const outDir = `${process.cwd()}/e2e/out/visual_${now}`;
|
|
const fs = require('fs');
|
|
try { fs.mkdirSync(outDir, { recursive: true }); } catch (e) {}
|
|
const capturePath = `${outDir}/studio.png`;
|
|
await page.screenshot({ path: capturePath, fullPage: false });
|
|
console.log('Saved visual capture to', capturePath);
|
|
// Playwright snapshot assertion (baseline management)
|
|
await expect(page).toHaveScreenshot('studio.png', { fullPage: false });
|
|
} finally {
|
|
try { if (context) await context.close(); } catch (e) {}
|
|
try { if (browser && usedRemote && typeof browser.close === 'function') await browser.close(); else if (browser) await browser.close(); } catch (e) {}
|
|
}
|
|
});
|