feat(prejoin): refactor PreJoin UI and styles; remove mock studio feature; add visual test scripts and update dependencies
- Redesign PreJoin component and CSS for improved template compatibility and deterministic rendering - Remove mock studio toggle and related runtime logic; update useStudioLauncher to always use real backend - Add README-MOCK.md to document mock studio deprecation - Add mock-studio.html for manual popup emulation - Update environment variable resolution in route.ts for backend API - Add visual regression test scripts (capture, compare, visual_test_prejoin) using Playwright, Puppeteer, pixelmatch, and pngjs - Update package.json scripts and devDependencies for visual testing - Simplify PreJoin.stories.tsx for robust Storybook usage
This commit is contained in:
parent
f8516a5330
commit
adbec08f5e
@ -5,7 +5,7 @@ export async function GET(req: Request) {
|
||||
const id = parts[parts.length - 1] || '';
|
||||
if (!id) return new Response(JSON.stringify({ error: 'missing id' }), { status: 400, headers: { 'content-type': 'application/json' } });
|
||||
|
||||
const backend = process.env.BACKEND_URL || process.env.VITE_BACKEND_TOKENS_URL || '';
|
||||
const backend = process.env.VITE_BACKEND_API_URL || process.env.VITE_TOKEN_SERVER_URL || process.env.BACKEND_URL || process.env.BACKEND || '';
|
||||
if (!backend) {
|
||||
return new Response(JSON.stringify({ error: 'BACKEND_URL not configured' }), { status: 500, headers: { 'content-type': 'application/json' } });
|
||||
}
|
||||
@ -19,4 +19,3 @@ export async function GET(req: Request) {
|
||||
return new Response(JSON.stringify({ error: 'internal' }), { status: 500, headers: { 'content-type': 'application/json' } });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
export async function POST(req: Request) {
|
||||
try {
|
||||
const body = await req.json();
|
||||
const backend = process.env.BACKEND_URL || process.env.VITE_BACKEND_TOKENS_URL || '';
|
||||
// Prefer VITE_BACKEND_API_URL (frontend env) then VITE_TOKEN_SERVER_URL then BACKEND_URL / BACKEND
|
||||
const backend = process.env.VITE_BACKEND_API_URL || process.env.VITE_TOKEN_SERVER_URL || process.env.BACKEND_URL || process.env.BACKEND || '';
|
||||
if (!backend) {
|
||||
return new Response(JSON.stringify({ error: 'BACKEND_URL not configured' }), { status: 500, headers: { 'content-type': 'application/json' } });
|
||||
}
|
||||
@ -16,4 +17,3 @@ export async function POST(req: Request) {
|
||||
return new Response(JSON.stringify({ error: 'internal' }), { status: 500, headers: { 'content-type': 'application/json' } });
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
3
app/rooms/[roomName]/StudioReceiver.tsx
Normal file
3
app/rooms/[roomName]/StudioReceiver.tsx
Normal file
@ -0,0 +1,3 @@
|
||||
// file removed - StudioReceiver replaced by real studio flow
|
||||
// This file was intentionally removed when reverting mock changes.
|
||||
|
||||
@ -15,4 +15,3 @@ export default function RoomPage({ params }: { params: { roomName: string } }) {
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
130
docs/mock-studio.html
Normal file
130
docs/mock-studio.html
Normal file
@ -0,0 +1,130 @@
|
||||
<!doctype html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1" />
|
||||
<title>Mock Studio — AvanzaCast</title>
|
||||
<style>
|
||||
body{font-family:system-ui,-apple-system,Segoe UI,Roboto,Helvetica,Arial;display:flex;align-items:center;justify-content:center;height:100vh;margin:0;background:#0f172a;color:#e6eef8}
|
||||
.card{width:760px;max-width:95%;background:#0b1220;border-radius:12px;padding:20px;box-shadow:0 10px 30px rgba(2,6,23,0.6)}
|
||||
h1{margin:0 0 8px;font-size:18px}
|
||||
p{margin:0 0 12px;color:#9fb0d1}
|
||||
.row{display:flex;gap:8px;margin-bottom:8px}
|
||||
button{background:#0ea5a3;border:none;padding:8px 12px;border-radius:8px;color:#042024;cursor:pointer}
|
||||
pre{background:#041025;padding:12px;border-radius:8px;color:#cfe8ff;overflow:auto;max-height:220px}
|
||||
input{background:#031423;border:1px solid #103247;color:#cfe8ff;padding:8px;border-radius:6px;flex:1}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="card">
|
||||
<h1>Mock Studio — Emulación popup</h1>
|
||||
<p>Esta página simula el popup que responde a mensajes postMessage desde la app (LIVEKIT_PING, LIVEKIT_TOKEN). Úsala en local si el host remoto no es accesible.</p>
|
||||
|
||||
<div class="row">
|
||||
<input id="roomInput" placeholder="room (mock-room)" value="mock-room" />
|
||||
<input id="tokenInput" placeholder="token (mock-token-<room>)" value="mock-token-mock-room" />
|
||||
<button id="sendToken">Enviar LIVEKIT_TOKEN</button>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<button id="sendPing">Enviar LIVEKIT_PING</button>
|
||||
<button id="openOpener" title="intenta comunicarte con window.opener">Ping opener</button>
|
||||
<button id="clearLog">Limpiar</button>
|
||||
</div>
|
||||
|
||||
<pre id="log">Log de mensajes:
|
||||
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
const logEl = document.getElementById('log');
|
||||
function log(...args){
|
||||
const text = args.map(a => typeof a === 'object' ? JSON.stringify(a) : String(a)).join(' ');
|
||||
logEl.textContent = logEl.textContent + '\n' + new Date().toISOString().slice(11,23) + ' ' + text;
|
||||
logEl.scrollTop = logEl.scrollHeight;
|
||||
}
|
||||
|
||||
// Mensajes entrantes (desde opener)
|
||||
window.addEventListener('message', ev => {
|
||||
try {
|
||||
const msg = ev.data;
|
||||
log('RECV', msg);
|
||||
// Normalizar formato: puede ser string o objeto
|
||||
if (typeof msg === 'string') {
|
||||
if (msg === 'LIVEKIT_PING') {
|
||||
// responder inmediatamente
|
||||
ev.source.postMessage('LIVEKIT_READY', '*');
|
||||
log('SENT', 'LIVEKIT_READY');
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (typeof msg === 'object' && msg !== null) {
|
||||
if (msg.type === 'LIVEKIT_TOKEN' || msg.type === 'LIVEKIT_GET_TOKEN') {
|
||||
const room = msg.room || document.getElementById('roomInput').value || 'mock-room';
|
||||
const token = document.getElementById('tokenInput').value || ('mock-token-' + room);
|
||||
// enviar ACK con payload
|
||||
const ack = { type: 'LIVEKIT_ACK', token, room };
|
||||
// simular pequeña latencia
|
||||
setTimeout(() => {
|
||||
ev.source.postMessage(ack, '*');
|
||||
log('SENT', ack);
|
||||
// también enviar READY después
|
||||
setTimeout(() => {
|
||||
ev.source.postMessage('LIVEKIT_READY', '*');
|
||||
log('SENT', 'LIVEKIT_READY');
|
||||
}, 120);
|
||||
}, 120);
|
||||
return;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
log('ERR', err && err.message ? err.message : err);
|
||||
}
|
||||
});
|
||||
|
||||
// Botones UI para debug / manual
|
||||
document.getElementById('sendPing').addEventListener('click', () => {
|
||||
if (window.opener && !window.opener.closed) {
|
||||
window.opener.postMessage('LIVEKIT_PING', '*');
|
||||
log('SENT (opener)', 'LIVEKIT_PING');
|
||||
} else {
|
||||
log('NOP', 'No hay opener disponible');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('sendToken').addEventListener('click', () => {
|
||||
if (window.opener && !window.opener.closed) {
|
||||
const room = document.getElementById('roomInput').value || 'mock-room';
|
||||
const token = document.getElementById('tokenInput').value || ('mock-token-' + room);
|
||||
const msg = { type: 'LIVEKIT_TOKEN', token, room };
|
||||
window.opener.postMessage(msg, '*');
|
||||
log('SENT (opener)', msg);
|
||||
} else {
|
||||
log('NOP', 'No hay opener disponible');
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('openOpener').addEventListener('click', () => {
|
||||
try {
|
||||
if (window.opener && !window.opener.closed) {
|
||||
window.opener.postMessage('MOCK_HELLO', '*');
|
||||
log('SENT (opener)', 'MOCK_HELLO');
|
||||
} else {
|
||||
log('NOP', 'Opener no encontrado');
|
||||
}
|
||||
} catch (e) {
|
||||
log('ERR', e.message);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('clearLog').addEventListener('click', () => logEl.textContent = 'Log de mensajes:\n');
|
||||
|
||||
// Si quieres abrir esta página desde la app usando window.open(url) y el popup fue bloqueado,
|
||||
// puedes abrir manualmente y usar el botón 'Enviar LIVEKIT_TOKEN' para simular el handshake.
|
||||
|
||||
log('Mock studio listo');
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@ -94,9 +94,12 @@
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mic-icon::before {
|
||||
content: '🎤';
|
||||
font-size: 24px;
|
||||
.mic-icon svg {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 2;
|
||||
}
|
||||
|
||||
.mic-meter {
|
||||
@ -337,7 +340,15 @@
|
||||
</div>
|
||||
|
||||
<div class="mic-status">
|
||||
<div class="mic-icon"></div>
|
||||
<div class="mic-icon">
|
||||
<!-- replace emoji with inline SVG for deterministic rendering -->
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" aria-hidden="true">
|
||||
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z"></path>
|
||||
<path d="M19 10v2a7 7 0 0 1-14 0v-2"></path>
|
||||
<line x1="12" y1="19" x2="12" y2="23"></line>
|
||||
<line x1="8" y1="23" x2="16" y2="23"></line>
|
||||
</svg>
|
||||
</div>
|
||||
<div class="mic-meter">
|
||||
<div class="mic-level"></div>
|
||||
</div>
|
||||
|
||||
504
package-lock.json
generated
504
package-lock.json
generated
@ -20,13 +20,15 @@
|
||||
"shared/*"
|
||||
],
|
||||
"dependencies": {
|
||||
"puppeteer": "^19.11.1",
|
||||
"puppeteer-core": "^24.30.0",
|
||||
"react-icons": "^5.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^8.2.2",
|
||||
"playwright": "^1.51.0",
|
||||
"pixelmatch": "^7.1.0",
|
||||
"playwright": "^1.56.1",
|
||||
"pngjs": "^7.0.0",
|
||||
"puppeteer": "^24.31.0",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"engines": {
|
||||
@ -4307,53 +4309,6 @@
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test/node_modules/fsevents": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
|
||||
"integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
],
|
||||
"engines": {
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test/node_modules/playwright": {
|
||||
"version": "1.56.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz",
|
||||
"integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.56.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "2.3.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@playwright/test/node_modules/playwright-core": {
|
||||
"version": "1.56.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz",
|
||||
"integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
"playwright-core": "cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/@pmmmwh/react-refresh-webpack-plugin": {
|
||||
"version": "0.5.17",
|
||||
"resolved": "https://registry.npmjs.org/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.5.17.tgz",
|
||||
@ -8566,17 +8521,6 @@
|
||||
"file-uri-to-path": "1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bl": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
|
||||
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"buffer": "^5.5.0",
|
||||
"inherits": "^2.0.4",
|
||||
"readable-stream": "^3.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/bluebird": {
|
||||
"version": "3.4.7",
|
||||
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.4.7.tgz",
|
||||
@ -13385,12 +13329,6 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/fs-constants": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
|
||||
"integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fs-extra": {
|
||||
"version": "10.1.0",
|
||||
"resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz",
|
||||
@ -19432,6 +19370,19 @@
|
||||
"node": ">= 6"
|
||||
}
|
||||
},
|
||||
"node_modules/pixelmatch": {
|
||||
"version": "7.1.0",
|
||||
"resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-7.1.0.tgz",
|
||||
"integrity": "sha512-1wrVzJ2STrpmONHKBy228LM1b84msXDUoAzVEl0R8Mz4Ce6EPr+IVtxm8+yvrqLYMHswREkjYFaMxnyGnaY3Ng==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"pngjs": "^7.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"pixelmatch": "bin/pixelmatch"
|
||||
}
|
||||
},
|
||||
"node_modules/pkg-dir": {
|
||||
"version": "4.2.0",
|
||||
"resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
|
||||
@ -19537,13 +19488,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright": {
|
||||
"version": "1.51.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.51.0.tgz",
|
||||
"integrity": "sha512-442pTfGM0xxfCYxuBa/Pu6B2OqxqqaYq39JS8QDMGThUvIOCd6s0ANDog3uwA0cHavVlnTQzGCN7Id2YekDSXA==",
|
||||
"version": "1.56.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright/-/playwright-1.56.1.tgz",
|
||||
"integrity": "sha512-aFi5B0WovBHTEvpM3DzXTUaeN6eN0qWnTkKx4NQaH4Wvcmc153PdaY2UBdSYKaGYw+UyWXSVyxDUg5DoPEttjw==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"playwright-core": "1.51.0"
|
||||
"playwright-core": "1.56.1"
|
||||
},
|
||||
"bin": {
|
||||
"playwright": "cli.js"
|
||||
@ -19556,9 +19507,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/playwright-core": {
|
||||
"version": "1.51.0",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.51.0.tgz",
|
||||
"integrity": "sha512-x47yPE3Zwhlil7wlNU/iktF7t2r/URR3VLbH6EknJd/04Qc/PSJ0EY3CMXipmglLG+zyRxW6HNo2EGbKLHPWMg==",
|
||||
"version": "1.56.1",
|
||||
"resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.56.1.tgz",
|
||||
"integrity": "sha512-hutraynyn31F+Bifme+Ps9Vq59hKuUCz7H1kDOcBs+2oGguKkWTU50bBWrtz34OUWmIwpBTWDxaRPXrIXkgvmQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"bin": {
|
||||
@ -19582,6 +19533,16 @@
|
||||
"node": "^8.16.0 || ^10.6.0 || >=11.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/pngjs": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/pngjs/-/pngjs-7.0.0.tgz",
|
||||
"integrity": "sha512-LKWqWJRhstyYo9pGvgor/ivk2w94eSjE3RGVuzLGlr3NmD8bf7RcYGze1mNdEHRP6TRP6rMuDHk5t44hnTRyow==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.19.0"
|
||||
}
|
||||
},
|
||||
"node_modules/possible-typed-array-names": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz",
|
||||
@ -21313,25 +21274,30 @@
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer": {
|
||||
"version": "19.11.1",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-19.11.1.tgz",
|
||||
"integrity": "sha512-39olGaX2djYUdhaQQHDZ0T0GwEp+5f9UB9HmEP0qHfdQHIq0xGQZuAZ5TLnJIc/88SrPLpEflPC+xUqOTv3c5g==",
|
||||
"deprecated": "< 24.15.0 is no longer supported",
|
||||
"version": "24.31.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.31.0.tgz",
|
||||
"integrity": "sha512-q8y5yLxLD8xdZdzNWqdOL43NbfvUOp60SYhaLZQwHC9CdKldxQKXOyJAciOr7oUJfyAH/KgB2wKvqT2sFKoVXA==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@puppeteer/browsers": "0.5.0",
|
||||
"cosmiconfig": "8.1.3",
|
||||
"https-proxy-agent": "5.0.1",
|
||||
"progress": "2.0.3",
|
||||
"proxy-from-env": "1.1.0",
|
||||
"puppeteer-core": "19.11.1"
|
||||
"@puppeteer/browsers": "2.10.13",
|
||||
"chromium-bidi": "11.0.0",
|
||||
"cosmiconfig": "^9.0.0",
|
||||
"devtools-protocol": "0.0.1521046",
|
||||
"puppeteer-core": "24.31.0",
|
||||
"typed-query-selector": "^2.12.0"
|
||||
},
|
||||
"bin": {
|
||||
"puppeteer": "lib/cjs/puppeteer/node/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer-core": {
|
||||
"version": "24.30.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.30.0.tgz",
|
||||
"integrity": "sha512-2S3Smy0t0W4wJnNvDe7W0bE7wDmZjfZ3ljfMgJd6hn2Hq/f0jgN+x9PULZo2U3fu5UUIJ+JP8cNUGllu8P91Pg==",
|
||||
"version": "24.31.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.31.0.tgz",
|
||||
"integrity": "sha512-pnAohhSZipWQoFpXuGV7xCZfaGhqcBR9C4pVrU0QSrcMi7tQMH9J9lDBqBvyMAHQqe8HCARuREqFuVKRQOgTvg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@puppeteer/browsers": "2.10.13",
|
||||
@ -21339,7 +21305,7 @@
|
||||
"debug": "^4.4.3",
|
||||
"devtools-protocol": "0.0.1521046",
|
||||
"typed-query-selector": "^2.12.0",
|
||||
"webdriver-bidi-protocol": "0.3.8",
|
||||
"webdriver-bidi-protocol": "0.3.9",
|
||||
"ws": "^8.18.3"
|
||||
},
|
||||
"engines": {
|
||||
@ -21367,125 +21333,38 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/@puppeteer/browsers": {
|
||||
"version": "0.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-0.5.0.tgz",
|
||||
"integrity": "sha512-Uw6oB7VvmPRLE4iKsjuOh8zgDabhNX67dzo8U/BB0f9527qx+4eeUs+korU98OhG5C4ubg7ufBgVi63XYwS6TQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"debug": "4.3.4",
|
||||
"extract-zip": "2.0.1",
|
||||
"https-proxy-agent": "5.0.1",
|
||||
"progress": "2.0.3",
|
||||
"proxy-from-env": "1.1.0",
|
||||
"tar-fs": "2.1.1",
|
||||
"unbzip2-stream": "1.4.3",
|
||||
"yargs": "17.7.1"
|
||||
},
|
||||
"bin": {
|
||||
"browsers": "lib/cjs/main-cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">= 4.7.4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/ansi-regex": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
|
||||
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/chownr": {
|
||||
"version": "1.1.4",
|
||||
"resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
|
||||
"integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/chromium-bidi": {
|
||||
"version": "0.4.7",
|
||||
"resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-0.4.7.tgz",
|
||||
"integrity": "sha512-6+mJuFXwTMU6I3vYLs6IL8A1DyQTPjCfIL971X0aMPVGRbGnNfl6i6Cl0NMbxi2bRYLGESt9T2ZIMRM5PAEcIQ==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"mitt": "3.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"devtools-protocol": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/cosmiconfig": {
|
||||
"version": "8.1.3",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.1.3.tgz",
|
||||
"integrity": "sha512-/UkO2JKI18b5jVMJUp0lvKFMpa/Gye+ZgZjKD+DGEN9y7NRcf/nK1A0sp67ONmKtnDCNMS44E6jrk0Yc3bDuUw==",
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
|
||||
"integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"import-fresh": "^3.2.1",
|
||||
"env-paths": "^2.2.1",
|
||||
"import-fresh": "^3.3.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"parse-json": "^5.0.0",
|
||||
"path-type": "^4.0.0"
|
||||
"parse-json": "^5.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/d-fischer"
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/cross-fetch": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.1.5.tgz",
|
||||
"integrity": "sha512-lvb1SBsI0Z7GDwmuid+mU3kWVBwTVUbe7S0H52yaaAdQOXq2YktTCZdlAcNKFzE6QtRz0snpw9bNiPeOIkkQvw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-fetch": "2.6.7"
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/debug": {
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.9.5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/devtools-protocol": {
|
||||
"version": "0.0.1107588",
|
||||
"resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1107588.tgz",
|
||||
"integrity": "sha512-yIR+pG9x65Xko7bErCUSQaDLrO/P1p3JUzEk7JCU4DowPcGHkTGUGQapcfcLc4qj0UaALwZ+cr0riFgiqpixcg==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/js-yaml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
@ -21498,155 +21377,6 @@
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/node-fetch": {
|
||||
"version": "2.6.7",
|
||||
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz",
|
||||
"integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"whatwg-url": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "4.x || >=6.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"encoding": "^0.1.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"encoding": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/puppeteer-core": {
|
||||
"version": "19.11.1",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-19.11.1.tgz",
|
||||
"integrity": "sha512-qcuC2Uf0Fwdj9wNtaTZ2OvYRraXpAK+puwwVW8ofOhOgLPZyz1c68tsorfIZyCUOpyBisjr+xByu7BMbEYMepA==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@puppeteer/browsers": "0.5.0",
|
||||
"chromium-bidi": "0.4.7",
|
||||
"cross-fetch": "3.1.5",
|
||||
"debug": "4.3.4",
|
||||
"devtools-protocol": "0.0.1107588",
|
||||
"extract-zip": "2.0.1",
|
||||
"https-proxy-agent": "5.0.1",
|
||||
"proxy-from-env": "1.1.0",
|
||||
"tar-fs": "2.1.1",
|
||||
"unbzip2-stream": "1.4.3",
|
||||
"ws": "8.13.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.14.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">= 4.7.4"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/string-width": {
|
||||
"version": "4.2.3",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
|
||||
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^8.0.0",
|
||||
"is-fullwidth-code-point": "^3.0.0",
|
||||
"strip-ansi": "^6.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/strip-ansi": {
|
||||
"version": "6.0.1",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
|
||||
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/tar-fs": {
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
|
||||
"integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chownr": "^1.1.1",
|
||||
"mkdirp-classic": "^0.5.2",
|
||||
"pump": "^3.0.0",
|
||||
"tar-stream": "^2.1.4"
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/tar-stream": {
|
||||
"version": "2.2.0",
|
||||
"resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
|
||||
"integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bl": "^4.0.3",
|
||||
"end-of-stream": "^1.4.1",
|
||||
"fs-constants": "^1.0.0",
|
||||
"inherits": "^2.0.3",
|
||||
"readable-stream": "^3.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/ws": {
|
||||
"version": "8.13.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
|
||||
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/puppeteer/node_modules/yargs": {
|
||||
"version": "17.7.1",
|
||||
"resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.1.tgz",
|
||||
"integrity": "sha512-cwiTb08Xuv5fqF4AovYacTFNxk62th7LKJ6BL9IGUpTJrWoU7/7WdQGTP2SjKf1dUNBGzDd28p/Yfs/GI6JrLw==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cliui": "^8.0.1",
|
||||
"escalade": "^3.1.1",
|
||||
"get-caller-file": "^2.0.5",
|
||||
"require-directory": "^2.1.1",
|
||||
"string-width": "^4.2.3",
|
||||
"y18n": "^5.0.5",
|
||||
"yargs-parser": "^21.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/q": {
|
||||
"version": "1.5.1",
|
||||
"resolved": "https://registry.npmjs.org/q/-/q-1.5.1.tgz",
|
||||
@ -29528,9 +29258,9 @@
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/webdriver-bidi-protocol": {
|
||||
"version": "0.3.8",
|
||||
"resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.3.8.tgz",
|
||||
"integrity": "sha512-21Yi2GhGntMc671vNBCjiAeEVknXjVRoyu+k+9xOMShu+ZQfpGQwnBqbNz/Sv4GXZ6JmutlPAi2nIJcrymAWuQ==",
|
||||
"version": "0.3.9",
|
||||
"resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.3.9.tgz",
|
||||
"integrity": "sha512-uIYvlRQ0PwtZR1EzHlTMol1G0lAlmOe6wPykF9a77AK3bkpvZHzIVxRE2ThOx5vjy2zISe0zhwf5rzuUfbo1PQ==",
|
||||
"license": "Apache-2.0"
|
||||
},
|
||||
"node_modules/webidl-conversions": {
|
||||
@ -31686,38 +31416,6 @@
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"packages/broadcast-panel/node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"packages/broadcast-panel/node_modules/cosmiconfig": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz",
|
||||
"integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"env-paths": "^2.2.1",
|
||||
"import-fresh": "^3.3.0",
|
||||
"js-yaml": "^4.1.0",
|
||||
"parse-json": "^5.2.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/d-fischer"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"typescript": ">=4.9.5"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"typescript": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"packages/broadcast-panel/node_modules/emoji-regex": {
|
||||
"version": "8.0.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
|
||||
@ -31810,18 +31508,6 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"packages/broadcast-panel/node_modules/js-yaml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"packages/broadcast-panel/node_modules/lru-cache": {
|
||||
"version": "7.18.3",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
|
||||
@ -31869,27 +31555,6 @@
|
||||
"node": ">= 14"
|
||||
}
|
||||
},
|
||||
"packages/broadcast-panel/node_modules/puppeteer": {
|
||||
"version": "24.30.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.30.0.tgz",
|
||||
"integrity": "sha512-A5OtCi9WpiXBQgJ2vQiZHSyrAzQmO/WDsvghqlN4kgw21PhxA5knHUaUQq/N3EMt8CcvSS0RM+kmYLJmedR3TQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@puppeteer/browsers": "2.10.13",
|
||||
"chromium-bidi": "11.0.0",
|
||||
"cosmiconfig": "^9.0.0",
|
||||
"devtools-protocol": "0.0.1521046",
|
||||
"puppeteer-core": "24.30.0",
|
||||
"typed-query-selector": "^2.12.0"
|
||||
},
|
||||
"bin": {
|
||||
"puppeteer": "lib/cjs/puppeteer/node/cli.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"packages/broadcast-panel/node_modules/puppeteer-core": {
|
||||
"version": "20.9.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-20.9.0.tgz",
|
||||
@ -31979,45 +31644,6 @@
|
||||
"integrity": "sha512-hyWmRrexdhbZ1tcJUGpO95ivbRhWXz++F4Ko+n21AY5PNln2ovoJw+8ZMNDTtip+CNFQfrtLVh/w4009dXO/eQ==",
|
||||
"license": "BSD-3-Clause"
|
||||
},
|
||||
"packages/broadcast-panel/node_modules/puppeteer/node_modules/puppeteer-core": {
|
||||
"version": "24.30.0",
|
||||
"resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.30.0.tgz",
|
||||
"integrity": "sha512-2S3Smy0t0W4wJnNvDe7W0bE7wDmZjfZ3ljfMgJd6hn2Hq/f0jgN+x9PULZo2U3fu5UUIJ+JP8cNUGllu8P91Pg==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"@puppeteer/browsers": "2.10.13",
|
||||
"chromium-bidi": "11.0.0",
|
||||
"debug": "^4.4.3",
|
||||
"devtools-protocol": "0.0.1521046",
|
||||
"typed-query-selector": "^2.12.0",
|
||||
"webdriver-bidi-protocol": "0.3.8",
|
||||
"ws": "^8.18.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"packages/broadcast-panel/node_modules/puppeteer/node_modules/ws": {
|
||||
"version": "8.18.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz",
|
||||
"integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": ">=5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"packages/broadcast-panel/node_modules/rollup": {
|
||||
"version": "4.52.5",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.52.5.tgz",
|
||||
|
||||
@ -18,6 +18,7 @@
|
||||
],
|
||||
"scripts": {
|
||||
"dev": "concurrently \"npm:dev:*\"",
|
||||
"visual-test:prejoin": "node scripts/visual_test_prejoin.cjs",
|
||||
"e2e:remote-chrome": "bash e2e/run-remote-chrome.sh",
|
||||
"dev:landing": "npm run dev --workspace=packages/landing-page",
|
||||
"dev:api": "npm run dev --workspace=packages/backend-api",
|
||||
@ -37,7 +38,10 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"concurrently": "^8.2.2",
|
||||
"playwright": "^1.51.0",
|
||||
"pixelmatch": "^7.1.0",
|
||||
"playwright": "^1.56.1",
|
||||
"pngjs": "^7.0.0",
|
||||
"puppeteer": "^24.31.0",
|
||||
"typescript": "^5.2.2"
|
||||
},
|
||||
"engines": {
|
||||
@ -45,7 +49,6 @@
|
||||
"npm": ">=10.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"puppeteer": "^19.11.1",
|
||||
"puppeteer-core": "^24.30.0",
|
||||
"react-icons": "^5.5.0"
|
||||
}
|
||||
|
||||
18
packages/broadcast-panel/README-MOCK.md
Normal file
18
packages/broadcast-panel/README-MOCK.md
Normal file
@ -0,0 +1,18 @@
|
||||
# Broadcast Panel — Mock Studio (deprecated)
|
||||
|
||||
La funcionalidad de "mock studio" integrada (toggle runtime y variable de entorno `VITE_MOCK_STUDIO`) ha sido eliminada del flujo principal de la aplicación.
|
||||
|
||||
Motivo
|
||||
- El modo mock introducía complejidad en el código de producción y causaba confusiones al depurar flujos reales. Para asegurar comportamiento consistente, el panel ahora usa siempre el `backend-api` real para crear sesiones y tokens.
|
||||
|
||||
Qué cambió
|
||||
- Se eliminó el toggle `MockToggle` del UI y la detección de `VITE_MOCK_STUDIO` en runtime.
|
||||
- `useStudioLauncher` ya no genera sesiones mock; siempre usa la API real (`/api/session` / `connection-details`) para crear/obtener tokens.
|
||||
- Las referencias a `localStorage['avz:mock_studio']` fueron retiradas del flujo principal.
|
||||
|
||||
Pruebas y E2E
|
||||
- Si necesitas ejecutar pruebas E2E o flujos aislados con un servidor mock, existen utilidades en la carpeta `e2e/`:
|
||||
- `e2e/mock_server.js` y `e2e/run_e2e_with_mock.js` siguen disponibles para pruebas locales y no forman parte del flujo de la aplicación.
|
||||
- Usa esos scripts explícitamente cuando quieras simular la infra (no se cargan por defecto en el dev server).
|
||||
|
||||
Si necesitas que vuelva a habilitarse un modo mock controlado (documentado y con feature flag), puedo preparar un PR con una implementación aislada y conmutador que no afecte el código en producción: dime si quieres que lo haga.
|
||||
@ -0,0 +1,4 @@
|
||||
// MockToggle removed: mock studio feature is disabled in this codebase.
|
||||
// This file was intentionally left blank to avoid build errors from leftover imports.
|
||||
export default function MockToggle() { return null as any }
|
||||
|
||||
@ -1,165 +1,208 @@
|
||||
/* filepath: /home/xesar/Documentos/Nextream/AvanzaCast/packages/broadcast-panel/src/features/studio/PreJoin.module.css */
|
||||
:root{
|
||||
--card-bg: #ffffff;
|
||||
--muted: #666666;
|
||||
--accent: #6366f1;
|
||||
--badge-bg: rgba(99,102,241,0.9);
|
||||
--danger: #dc2626;
|
||||
}
|
||||
/* filepath: packages/broadcast-panel/src/features/studio/PreJoin.module.css */
|
||||
* { box-sizing: border-box; }
|
||||
|
||||
.prejoinContainer{
|
||||
.container {
|
||||
max-width: 628px;
|
||||
width: 100%;
|
||||
margin: 0 auto;
|
||||
padding: 20px;
|
||||
/* match template font stack to reduce font rendering diffs */
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
-moz-osx-font-smoothing: grayscale;
|
||||
}
|
||||
|
||||
.card{
|
||||
background: var(--card-bg);
|
||||
border-radius: 12px;
|
||||
padding: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.header{
|
||||
.header {
|
||||
text-align: center;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.header > div:first-child{
|
||||
.header h1 {
|
||||
font-size: 28px;
|
||||
font-weight: 600;
|
||||
color: #1a1a1a;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.note{
|
||||
.header p {
|
||||
font-size: 14px;
|
||||
color: var(--muted);
|
||||
color: #666666;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.contentRow{
|
||||
.video-container {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.previewColumn{ flex: 1 }
|
||||
|
||||
.previewCard{
|
||||
.video-preview {
|
||||
flex: 1;
|
||||
background-color: #0a0a1a;
|
||||
border-radius: 12px;
|
||||
overflow: hidden;
|
||||
background: #0a0a1a;
|
||||
position: relative;
|
||||
aspect-ratio: 16/9;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.videoEl{
|
||||
width:100%;
|
||||
height:100%;
|
||||
object-fit:cover;
|
||||
background:#0b0b0b;
|
||||
}
|
||||
|
||||
.badge{
|
||||
.user-badge {
|
||||
position: absolute;
|
||||
bottom: 16px;
|
||||
left: 16px;
|
||||
background: var(--badge-bg);
|
||||
color: #fff;
|
||||
background-color: rgba(99, 102, 241, 0.9);
|
||||
color: white;
|
||||
padding: 8px 20px;
|
||||
border-radius: 20px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
border-radius: 20px;
|
||||
}
|
||||
|
||||
.micPanel{
|
||||
.mic-status {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 12px;
|
||||
padding: 20px;
|
||||
min-width: 180px;
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
}
|
||||
|
||||
.micPanelInner{
|
||||
display:flex;
|
||||
flex-direction:column;
|
||||
align-items:center;
|
||||
gap:8px;
|
||||
}
|
||||
|
||||
.mic-icon{ width:48px; height:48px; border-radius:50%; background:#e8e8e8; display:flex; align-items:center; justify-content:center; margin-bottom:12px }
|
||||
|
||||
.mic-meter{ width:32px; height:80px; background:#e8e8e8; border-radius:16px; margin-bottom:12px; position:relative; overflow:hidden }
|
||||
.mic-level{ position:absolute; bottom:0; left:0; right:0; height:20%; background: linear-gradient(to top, #22c55e, #86efac); border-radius:16px; transition:height 0.1s ease-out }
|
||||
|
||||
.micStatus{ color: #22c55e; font-weight:500; font-size:14px; text-align:center; margin-bottom:4px }
|
||||
.mic-device{ font-size:11px; color:#999999; text-align:center }
|
||||
|
||||
.controlsRow{
|
||||
display:inline-flex;
|
||||
justify-content:center;
|
||||
gap:8px;
|
||||
padding:12px;
|
||||
background-color:var(--card-bg);
|
||||
border:1px solid #e5e5e5;
|
||||
border-radius:12px;
|
||||
margin-bottom:24px;
|
||||
margin-left:auto;
|
||||
margin-right:auto;
|
||||
box-shadow: 0 1px 3px rgba(0,0,0,0.1), 0 1px 2px rgba(0,0,0,0.06);
|
||||
}
|
||||
|
||||
.controlButtonLocal{ display:flex; flex-direction:column; align-items:center; gap:8px; background:transparent; border:none; cursor:pointer; color:var(--muted); font-size:13px; transition:all .2s; padding:12px 20px; border-radius:8px }
|
||||
.controlButtonLocal:hover{ color:#1a1a1a; background-color:#fee2e2 }
|
||||
|
||||
.controlsRow > button[data-active="false"], .controlButtonLocal.disabled{ color:var(--danger); background-color:#fecaca }
|
||||
.controlButtonLocal.disabled:hover, .controlsRow > button[data-active="false"]:hover{ color:#b91c1c; background-color:#fca5a5 }
|
||||
|
||||
.controlButtonLocal > span:first-child{ width:24px; height:24px; display:inline-flex; align-items:center; justify-content:center }
|
||||
.controlButtonLocal > span:first-child svg{ width:24px; height:24px }
|
||||
|
||||
.control-hint{ position:absolute; bottom:100%; left:50%; transform:translateX(-50%); background-color:#1a1a1a; color:white; padding:6px 12px; border-radius:6px; font-size:12px; white-space:nowrap; opacity:0; pointer-events:none; transition:opacity .2s; margin-bottom:8px }
|
||||
.controlButtonLocal:hover .control-hint{ opacity:1 }
|
||||
|
||||
.roomTitle{ margin-top:8px; margin-bottom:8px; font-weight:500; color:#1a1a1a }
|
||||
.input{ width:100%; padding:12px 16px; border-radius:8px; border:1px solid #d1d5db; font-size:14px; margin-bottom:16px }
|
||||
|
||||
.shortcutsLegend{ text-align:center; margin-top:12px; color:var(--muted) }
|
||||
.kbd{ background-color:#374151; padding:2px 6px; border-radius:3px; font-family:monospace; font-size:11px; color:#fff }
|
||||
|
||||
.checkboxRow{ margin-top:12px; margin-bottom:12px; display:flex; align-items:center; gap:8px }
|
||||
|
||||
.actions{ display:flex; gap:12px; margin-top:16px }
|
||||
.cancelBtn{ background:transparent; border:1px solid #e5e7eb; padding:10px 14px; border-radius:8px; cursor:pointer }
|
||||
.primaryBtn{ background:#2563eb; color:#fff; border:none; padding:12px 18px; border-radius:8px; cursor:pointer }
|
||||
.primaryBtn:disabled{ opacity:0.7; cursor:not-allowed }
|
||||
|
||||
/* small error box */
|
||||
.error{
|
||||
background:#fff5f5;
|
||||
border:1px solid #fecaca;
|
||||
color:#b91c1c;
|
||||
padding:10px 12px;
|
||||
border-radius:8px;
|
||||
margin-bottom:12px;
|
||||
font-size:13px;
|
||||
}
|
||||
|
||||
/* Side column (form & actions) */
|
||||
.sideColumn{
|
||||
width: 320px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 180px;
|
||||
}
|
||||
|
||||
/* ensure controls row centers on small screens */
|
||||
@media (max-width:800px){
|
||||
.contentRow{ flex-direction:column }
|
||||
.micPanel{ min-width:unset; width:100% }
|
||||
.sideColumn{ width:100% }
|
||||
.controlsRow{ width:100%; justify-content:space-around }
|
||||
.mic-icon {
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
background-color: #e8e8e8;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mic-icon svg { width: 24px; height: 24px; display: block; }
|
||||
|
||||
.mic-meter {
|
||||
width: 32px;
|
||||
height: 80px;
|
||||
background-color: #e8e8e8;
|
||||
border-radius: 16px;
|
||||
margin-bottom: 12px;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.mic-level {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
height: 20%;
|
||||
/* match template gradient to reduce rendering differences */
|
||||
background: linear-gradient(to top, #22c55e, #86efac);
|
||||
border-radius: 16px;
|
||||
transition: height 0.1s ease-out;
|
||||
}
|
||||
|
||||
.mic-status-text {
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
color: #22c55e;
|
||||
font-weight: 500;
|
||||
margin-bottom: 4px;
|
||||
/* preserve template line breaks to avoid subtle rendering diffs */
|
||||
white-space: pre-line;
|
||||
/* enforce exact line-height to match template rendering */
|
||||
line-height: 1.0;
|
||||
}
|
||||
|
||||
.mic-device {
|
||||
font-size: 11px;
|
||||
color: #999999;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.controls {
|
||||
display: inline-flex;
|
||||
justify-content: center;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
background-color: #ffffff;
|
||||
border: 1px solid #e5e5e5;
|
||||
border-radius: 12px;
|
||||
margin-bottom: 24px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1), 0 1px 2px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.controls-wrapper { text-align: center; }
|
||||
|
||||
.control-btn {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
background: transparent;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
color: #666666;
|
||||
font-size: 13px;
|
||||
transition: all 0.2s;
|
||||
padding: 12px 20px;
|
||||
border-radius: 8px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.control-btn:hover { color: #1a1a1a; background-color: #fee2e2; }
|
||||
|
||||
.control-btn.disabled { color: #dc2626; background-color: #fecaca; }
|
||||
.control-btn.disabled:hover { color: #b91c1c; background-color: #fca5a5; }
|
||||
|
||||
.control-icon { width: 24px; height: 24px; display: flex; align-items: center; justify-content: center; position: relative; }
|
||||
|
||||
.control-hint {
|
||||
position: absolute;
|
||||
bottom: 100%;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background-color: #1a1a1a;
|
||||
color: white;
|
||||
padding: 6px 12px;
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
white-space: nowrap;
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
transition: opacity 0.2s;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.control-btn:hover .control-hint { opacity: 1; }
|
||||
|
||||
.kbd { background-color: #374151; padding: 2px 6px; border-radius: 3px; font-family: monospace; font-size: 11px; color: #fff; }
|
||||
|
||||
.form-group { margin-bottom: 20px; }
|
||||
|
||||
.form-label { display: block; font-size: 14px; color: #1a1a1a; margin-bottom: 8px; font-weight: 500; }
|
||||
|
||||
.form-label .optional { color: #666666; font-weight: 400; }
|
||||
|
||||
.info-icon { display: inline-flex; align-items: center; justify-content: center; width: 16px; height: 16px; border: 1.5px solid #3b82f6; border-radius: 50%; color: #3b82f6; font-size: 11px; font-weight: 600; margin-left: 4px; cursor: help; }
|
||||
|
||||
.form-input { width: 100%; padding: 12px 16px; border: 1px solid #d1d5db; border-radius: 8px; font-size: 14px; color: #1a1a1a; transition: border-color 0.2s, box-shadow 0.2s; }
|
||||
|
||||
.form-input:focus { outline: none; border-color: #3b82f6; box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.1); }
|
||||
|
||||
.form-input::placeholder { color: #9ca3af; }
|
||||
|
||||
.submit-btn { width: 100%; padding: 14px; background-color: #2563eb; color: white; border: none; border-radius: 8px; font-size: 15px; font-weight: 600; cursor: pointer; transition: background-color 0.2s; }
|
||||
|
||||
.submit-btn:hover { background-color: #1d4ed8; }
|
||||
.submit-btn:active { background-color: #1e40af; }
|
||||
|
||||
/* responsive */
|
||||
@media (max-width: 800px) {
|
||||
.video-container { flex-direction: column; }
|
||||
.mic-status { min-width: unset; width: 100%; }
|
||||
}
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import React, { useEffect, useRef, useState } from 'react'
|
||||
import styles from './PreJoin.module.css'
|
||||
import { ControlButton, MicrophoneMeter, modifierKeyLabel, isMacPlatform } from 'avanza-ui'
|
||||
// We'll dynamically import MockToggle inside the component when appropriate (DEV mode or VITE_MOCK_STUDIO).
|
||||
|
||||
import { isMacPlatform } from 'avanza-ui'
|
||||
import { FiMic, FiVideo, FiSettings } from 'react-icons/fi'
|
||||
|
||||
type Props = {
|
||||
@ -11,25 +13,19 @@ type Props = {
|
||||
token?: string
|
||||
}
|
||||
|
||||
export default function PreJoin({ roomName: _roomName, onProceed, onCancel }: Props) {
|
||||
export default function PreJoin({ roomName: _roomName, onProceed, onCancel: _onCancel }: Props) {
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null)
|
||||
const [name, setName] = useState(() => {
|
||||
try { return localStorage.getItem('avanzacast_user') || '' } catch { return '' }
|
||||
})
|
||||
const [micEnabled, setMicEnabled] = useState(true)
|
||||
const [camEnabled, setCamEnabled] = useState(true)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [isChecking, setIsChecking] = useState(false)
|
||||
// checkbox state is local only; do NOT persist skip preference so PreJoin always appears
|
||||
const [skipNextTime, setSkipNextTime] = useState<boolean>(false)
|
||||
// keep preview stream active for meter and preview
|
||||
const [previewStream, setPreviewStream] = useState<MediaStream | null>(null)
|
||||
|
||||
// Use shared platform utils
|
||||
const isMac = isMacPlatform()
|
||||
const modLabel = modifierKeyLabel()
|
||||
const micHint = `${modLabel.display} + D`
|
||||
const camHint = `${modLabel.display} + E`
|
||||
|
||||
useEffect(() => {
|
||||
// ensure any old skip flag does not affect behavior: remove legacy key
|
||||
@ -102,7 +98,6 @@ export default function PreJoin({ roomName: _roomName, onProceed, onCancel }: Pr
|
||||
}, [micEnabled, camEnabled])
|
||||
|
||||
const handleProceed = async () => {
|
||||
setError(null)
|
||||
setIsChecking(true)
|
||||
try {
|
||||
// request permissions explicitly
|
||||
@ -112,7 +107,7 @@ export default function PreJoin({ roomName: _roomName, onProceed, onCancel }: Pr
|
||||
// proceed to connect
|
||||
onProceed()
|
||||
} catch (e: any) {
|
||||
setError(e?.message || 'No se pudo acceder a la cámara/micrófono')
|
||||
console.log(e)
|
||||
} finally {
|
||||
setIsChecking(false)
|
||||
}
|
||||
@ -127,95 +122,70 @@ export default function PreJoin({ roomName: _roomName, onProceed, onCancel }: Pr
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.prejoinContainer}>
|
||||
<div className={styles.card}>
|
||||
<div className={styles.header}>
|
||||
<div>Configura tu estudio</div>
|
||||
<div className={styles.note}>Entrar al estudio no iniciará automáticamente la transmisión.</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.contentRow}>
|
||||
<div className={styles.previewColumn}>
|
||||
<div className={styles.previewCard}>
|
||||
<video ref={videoRef} className={styles.videoEl} playsInline muted />
|
||||
<div className={styles.badge}>{name || 'Invitado'}</div>
|
||||
<div className={styles.micPanel}>
|
||||
<div className={styles.micPanelInner}>
|
||||
<MicrophoneMeter level={previewStream ? 1 : 0} />
|
||||
<div className={styles.micStatus}>{micEnabled ? 'El micrófono está funcionando' : 'Micrófono desactivado'}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.controlsRow}>
|
||||
<ControlButton
|
||||
className={styles.controlButtonLocal}
|
||||
icon={<FiMic />}
|
||||
label={micEnabled ? 'Desactivar audio' : 'Activar audio'}
|
||||
active={micEnabled}
|
||||
danger={!micEnabled}
|
||||
layout="column"
|
||||
variant="studio"
|
||||
onClick={toggleMic}
|
||||
hint={micHint}
|
||||
size="md"
|
||||
/>
|
||||
|
||||
<ControlButton
|
||||
className={styles.controlButtonLocal}
|
||||
icon={<FiVideo />}
|
||||
label={camEnabled ? 'Detener cámara' : 'Iniciar cámara'}
|
||||
active={camEnabled}
|
||||
danger={!camEnabled}
|
||||
layout="column"
|
||||
variant="studio"
|
||||
onClick={toggleCam}
|
||||
hint={camHint}
|
||||
size="md"
|
||||
/>
|
||||
|
||||
<ControlButton
|
||||
className={styles.controlButtonLocal}
|
||||
icon={<FiSettings />}
|
||||
label={'Configuración'}
|
||||
active={true}
|
||||
layout="column"
|
||||
variant="studio"
|
||||
onClick={() => { /* abrir modal de settings si aplica */ }}
|
||||
size="md"
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Leyenda de atajos: muestra las combinaciones detectadas (ej: ⌘ + D) */}
|
||||
<div className={styles.shortcutsLegend} aria-hidden="true">
|
||||
Atajos: <span className={styles.kbd}>{micHint}</span> mic · <span className={styles.kbd}>{camHint}</span> cámara
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div className={styles.sideColumn}>
|
||||
{error && <div className={styles.error}>{error}</div>}
|
||||
|
||||
<div className={styles.roomTitle}>Nombre para mostrar</div>
|
||||
<input className={styles.input} value={name} onChange={e => setName(e.target.value)} placeholder="Tu nombre" />
|
||||
|
||||
<div className={styles.roomTitle}>Título (opcional)</div>
|
||||
<input className={styles.input} placeholder="p. ej.: Founder of Creativity Inc" />
|
||||
|
||||
<div className={styles.checkboxRow}>
|
||||
<input id="skipNext" type="checkbox" checked={skipNextTime} onChange={e => setSkipNextTime(e.target.checked)} />
|
||||
<label htmlFor="skipNext">Omitir PreJoin la próxima vez</label>
|
||||
</div>
|
||||
|
||||
<div className={styles.actions}>
|
||||
<button className={styles.cancelBtn} onClick={() => { onCancel?.() }}>Cancelar</button>
|
||||
<button className={styles.primaryBtn} onClick={handleProceed} disabled={isChecking}>{isChecking ? 'Comprobando...' : 'Entrar al estudio'}</button>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles.container}>
|
||||
<div className={styles.header}>
|
||||
<h1>Configura tu estudio</h1>
|
||||
<p>Entrar al estudio no iniciará automáticamente<br/>la transmisión.</p>
|
||||
</div>
|
||||
|
||||
<div className={styles['video-container']}>
|
||||
<div className={styles['video-preview']}>
|
||||
{/* Preview video */}
|
||||
<video ref={videoRef} className={styles.videoEl} playsInline muted />
|
||||
<div className={styles['user-badge']}>{name || 'Invitado'}</div>
|
||||
</div>
|
||||
|
||||
<div className={styles['mic-status']}>
|
||||
<div className={styles['mic-icon']} aria-hidden="true">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth={2}>
|
||||
<path d="M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" />
|
||||
<path d="M19 10v2a7 7 0 0 1-14 0v-2" />
|
||||
<line x1="12" y1="19" x2="12" y2="23" />
|
||||
<line x1="8" y1="23" x2="16" y2="23" />
|
||||
</svg>
|
||||
</div>
|
||||
<div className={styles['mic-meter']}>
|
||||
<div className={styles['mic-level']} style={{height: previewStream ? '60%' : '20%'}}></div>
|
||||
</div>
|
||||
<div className={styles['mic-status-text']}>{micEnabled ? 'El micrófono\nestá\nfuncionando' : 'Micrófono desactivado'}</div>
|
||||
<div className={styles['mic-device']}>Microphone Array (R...)</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className={styles['controls-wrapper']}>
|
||||
<div className={styles.controls}>
|
||||
<button className={`control-btn ${micEnabled ? '' : 'disabled'}`} onClick={toggleMic} aria-pressed={micEnabled}>
|
||||
<span className={styles['control-hint']}>Presiona <span className={styles.kbd}>{isMac ? '⌘' : 'CTRL'}</span> + <span className={styles.kbd}>D</span></span>
|
||||
<div className={styles['control-icon']}><FiMic /></div>
|
||||
<span>{micEnabled ? 'Desactivar audio' : 'Activar audio'}</span>
|
||||
</button>
|
||||
|
||||
<button className={`control-btn ${camEnabled ? '' : 'disabled'}`} onClick={toggleCam} aria-pressed={camEnabled}>
|
||||
<span className={styles['control-hint']}>Presiona <span className={styles.kbd}>{isMac ? '⌘' : 'CTRL'}</span> + <span className={styles.kbd}>E</span></span>
|
||||
<div className={styles['control-icon']}><FiVideo /></div>
|
||||
<span>{camEnabled ? 'Detener cámara' : 'Iniciar cámara'}</span>
|
||||
</button>
|
||||
|
||||
<button className="control-btn" onClick={() => {}}>
|
||||
<div className={styles['control-icon']}><FiSettings /></div>
|
||||
<span>Configuración</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<form onSubmit={(e) => { e.preventDefault(); handleProceed(); }}>
|
||||
<div className={styles['form-group']}>
|
||||
<label className={styles['form-label']} htmlFor="display-name">Nombre para mostrar</label>
|
||||
<input id="display-name" className={styles['form-input']} value={name} onChange={e => setName(e.target.value)} />
|
||||
</div>
|
||||
|
||||
<div className={styles['form-group']}>
|
||||
<label className={styles['form-label']} htmlFor="title">Título <span className={styles.optional}>(opcional)</span> <span className={styles['info-icon']}>?</span></label>
|
||||
<input id="title" className={styles['form-input']} placeholder="p. ej.: Founder of Creativity Inc" />
|
||||
</div>
|
||||
|
||||
<button type="submit" className={styles['submit-btn']}>{isChecking ? 'Comprobando...' : 'Entrar al estudio'}</button>
|
||||
</form>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@ -20,13 +20,19 @@ export default function useStudioLauncher() {
|
||||
const [loadingId, setLoadingId] = useState<string | null>(null);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
|
||||
// NOTE: mock mode removed. useStudioLauncher now always uses the real backend to create/obtain sessions.
|
||||
|
||||
async function openStudio(opts: OpenStudioOptions) {
|
||||
const { room, username, ttl } = opts;
|
||||
let { room, username, ttl } = opts as OpenStudioOptions;
|
||||
|
||||
setError(null);
|
||||
setLoadingId(room);
|
||||
|
||||
if (!room || !username) {
|
||||
setError("room and username are required");
|
||||
return null;
|
||||
}
|
||||
setError(null);
|
||||
|
||||
setLoadingId(room);
|
||||
|
||||
// Timeouts and retry config
|
||||
|
||||
@ -3,7 +3,7 @@ import react from '@vitejs/plugin-react'
|
||||
import path from 'path'
|
||||
|
||||
export default defineConfig(({ mode }) => ({
|
||||
plugins: [react()],
|
||||
plugins: [react() as any],
|
||||
resolve: {
|
||||
alias: {
|
||||
'@': path.resolve(__dirname, 'src'),
|
||||
@ -43,6 +43,7 @@ export default defineConfig(({ mode }) => ({
|
||||
},
|
||||
// Allowlist hosts for preview/remote access
|
||||
allowedHosts: [
|
||||
'avanzacast-broadcastpanel.zuqtxy.easypanel.host',
|
||||
'avanzacast-broadcastpanel.bfzqqk.easypanel.host',
|
||||
'localhost',
|
||||
],
|
||||
|
||||
@ -1,20 +1,15 @@
|
||||
import React from 'react';
|
||||
import type { Meta, StoryObj } from '@storybook/react';
|
||||
import PreJoin from '../../../../packages/broadcast-panel/src/features/studio/PreJoin';
|
||||
// Simple Storybook story for PreJoin (keeps it robust across envs)
|
||||
import PreJoin from '../../../../packages/broadcast-panel/src/features/studio/PreJoin'
|
||||
|
||||
const meta: Meta<typeof PreJoin> = {
|
||||
export default {
|
||||
title: 'Broadcast/PreJoin',
|
||||
component: PreJoin,
|
||||
};
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<typeof PreJoin>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
roomName: 'Sala de prueba',
|
||||
onProceed: () => alert('Proceed clicked'),
|
||||
onCancel: () => alert('Cancel clicked'),
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
export const Default = () => (
|
||||
<PreJoin
|
||||
roomName="Sala de prueba"
|
||||
onProceed={() => { alert('Entrar al estudio') }}
|
||||
onCancel={() => { alert('Cancel') }}
|
||||
/>
|
||||
)
|
||||
|
||||
65
scripts/capture_and_diff_playwright.mjs
Normal file
65
scripts/capture_and_diff_playwright.mjs
Normal file
@ -0,0 +1,65 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { chromium } from 'playwright';
|
||||
import { PNG } from 'pngjs';
|
||||
import pixelmatch from 'pixelmatch';
|
||||
|
||||
async function capture(url, outPath, width = 1280, height = 720) {
|
||||
const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
|
||||
const page = await browser.newPage({ viewport: { width, height } });
|
||||
await page.goto(url, { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: outPath, fullPage: false });
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
function compare(imgAPath, imgBPath, diffOut) {
|
||||
const imgA = PNG.sync.read(fs.readFileSync(imgAPath));
|
||||
const imgB = PNG.sync.read(fs.readFileSync(imgBPath));
|
||||
if (imgA.width !== imgB.width || imgA.height !== imgB.height) {
|
||||
throw new Error('Images must have same dimensions');
|
||||
}
|
||||
const { width, height } = imgA;
|
||||
const diff = new PNG({ width, height });
|
||||
const mismatched = pixelmatch(imgA.data, imgB.data, diff.data, width, height, { threshold: 0.1 });
|
||||
fs.writeFileSync(diffOut, PNG.sync.write(diff));
|
||||
return { mismatched, total: width * height, ratio: mismatched / (width * height) };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
try {
|
||||
const arg = process.argv[2] || `file://${path.resolve(process.cwd(), 'docs/prejoin_template.html')}`;
|
||||
const outDir = '/tmp';
|
||||
const outFile = path.join(outDir, 'prejoin_playwright_1280x720.png');
|
||||
console.log('Capturing', arg, '->', outFile);
|
||||
await capture(arg, outFile, 1280, 720);
|
||||
console.log('Saved capture to', outFile);
|
||||
|
||||
const baselineA = path.resolve(process.cwd(), 'baselines/prejoin_regen_1280x720.png');
|
||||
const baselineB = path.resolve(process.cwd(), 'baselines/prejoin_browserless_1280x720.png');
|
||||
const report = { captured: outFile, compared: false };
|
||||
|
||||
if (fs.existsSync(baselineA)) {
|
||||
const diffOut = path.join(outDir, 'prejoin_diff_1280x720.png');
|
||||
const metrics = compare(outFile, baselineA, diffOut);
|
||||
report.compared = true; report.baseline = baselineA; report.diff = diffOut; report.metrics = metrics;
|
||||
console.log('Compared with', baselineA, 'metrics=', metrics);
|
||||
} else if (fs.existsSync(baselineB)) {
|
||||
const diffOut = path.join(outDir, 'prejoin_diff_1280x720.png');
|
||||
const metrics = compare(outFile, baselineB, diffOut);
|
||||
report.compared = true; report.baseline = baselineB; report.diff = diffOut; report.metrics = metrics;
|
||||
console.log('Compared with', baselineB, 'metrics=', metrics);
|
||||
} else {
|
||||
console.log('No baseline found (checked baselines/prejoin_regen_1280x720.png and baselines/prejoin_browserless_1280x720.png)');
|
||||
}
|
||||
|
||||
fs.writeFileSync(path.join(outDir, 'prejoin_playwright_report.json'), JSON.stringify(report, null, 2));
|
||||
console.log('Report written to /tmp/prejoin_playwright_report.json');
|
||||
} catch (e) {
|
||||
console.error('Error:', e);
|
||||
process.exit(2);
|
||||
}
|
||||
}
|
||||
|
||||
main();
|
||||
|
||||
64
scripts/capture_and_diff_prejoin.cjs
Normal file
64
scripts/capture_and_diff_prejoin.cjs
Normal file
@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env node
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const puppeteer = require('puppeteer');
|
||||
const PNG = require('pngjs').PNG;
|
||||
const pixelmatch = require('pixelmatch');
|
||||
|
||||
async function capture(fileUrl, outPath, width=1280, height=720) {
|
||||
const browser = await puppeteer.launch({args: ['--no-sandbox','--disable-setuid-sandbox']});
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width, height });
|
||||
await page.goto(fileUrl, { waitUntil: 'networkidle0' });
|
||||
// wait a bit for scripts to run
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: outPath, fullPage: false });
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
function compare(imgAPath, imgBPath, diffOut) {
|
||||
const imgA = PNG.sync.read(fs.readFileSync(imgAPath));
|
||||
const imgB = PNG.sync.read(fs.readFileSync(imgBPath));
|
||||
if (imgA.width !== imgB.width || imgA.height !== imgB.height) {
|
||||
throw new Error('Images must have same dimensions');
|
||||
}
|
||||
const { width, height } = imgA;
|
||||
const diff = new PNG({ width, height });
|
||||
const mismatched = pixelmatch(imgA.data, imgB.data, diff.data, width, height, { threshold: 0.1 });
|
||||
fs.writeFileSync(diffOut, PNG.sync.write(diff));
|
||||
return { mismatched, total: width*height, ratio: mismatched / (width*height) };
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const outDir = '/tmp';
|
||||
const fileArg = process.argv[2] || `file://${path.resolve(process.cwd(),'docs/prejoin_template.html')}`;
|
||||
const outFile = path.join(outDir, 'prejoin_capture_1280x720.png');
|
||||
console.log('Capturing', fileArg, '->', outFile);
|
||||
await capture(fileArg, outFile, 1280, 720);
|
||||
console.log('Saved capture to', outFile);
|
||||
// If baseline exists in repo root under baselines/, compare
|
||||
const baselineA = path.resolve(process.cwd(), 'baselines/prejoin_regen_1280x720.png');
|
||||
const baselineB = path.resolve(process.cwd(), 'baselines/prejoin_browserless_1280x720.png');
|
||||
let result = { captured: outFile, compared: false };
|
||||
if (fs.existsSync(baselineA)) {
|
||||
const diffOut = path.join(outDir, 'prejoin_diff_1280x720.png');
|
||||
const metrics = compare(outFile, baselineA, diffOut);
|
||||
result.compared = true; result.baseline = baselineA; result.diff = diffOut; result.metrics = metrics;
|
||||
console.log('Compared with', baselineA, 'metrics=', metrics);
|
||||
} else if (fs.existsSync(baselineB)) {
|
||||
const diffOut = path.join(outDir, 'prejoin_diff_1280x720.png');
|
||||
const metrics = compare(outFile, baselineB, diffOut);
|
||||
result.compared = true; result.baseline = baselineB; result.diff = diffOut; result.metrics = metrics;
|
||||
console.log('Compared with', baselineB, 'metrics=', metrics);
|
||||
} else {
|
||||
console.log('No baseline found (checked baselines/prejoin_regen_1280x720.png and baselines/prejoin_browserless_1280x720.png)');
|
||||
}
|
||||
fs.writeFileSync(path.join('/tmp','prejoin_capture_report.json'), JSON.stringify(result, null, 2));
|
||||
console.log('Report written to /tmp/prejoin_capture_report.json');
|
||||
} catch (err) {
|
||||
console.error('Failed:', err);
|
||||
process.exit(2);
|
||||
}
|
||||
})();
|
||||
|
||||
64
scripts/capture_and_diff_prejoin.js
Normal file
64
scripts/capture_and_diff_prejoin.js
Normal file
@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env node
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const puppeteer = require('puppeteer');
|
||||
const PNG = require('pngjs').PNG;
|
||||
const pixelmatch = require('pixelmatch');
|
||||
|
||||
async function capture(fileUrl, outPath, width=1280, height=720) {
|
||||
const browser = await puppeteer.launch({args: ['--no-sandbox','--disable-setuid-sandbox']});
|
||||
const page = await browser.newPage();
|
||||
await page.setViewport({ width, height });
|
||||
await page.goto(fileUrl, { waitUntil: 'networkidle0' });
|
||||
// wait a bit for scripts to run
|
||||
await page.waitForTimeout(500);
|
||||
await page.screenshot({ path: outPath, fullPage: false });
|
||||
await browser.close();
|
||||
}
|
||||
|
||||
function compare(imgAPath, imgBPath, diffOut) {
|
||||
const imgA = PNG.sync.read(fs.readFileSync(imgAPath));
|
||||
const imgB = PNG.sync.read(fs.readFileSync(imgBPath));
|
||||
if (imgA.width !== imgB.width || imgA.height !== imgB.height) {
|
||||
throw new Error('Images must have same dimensions');
|
||||
}
|
||||
const { width, height } = imgA;
|
||||
const diff = new PNG({ width, height });
|
||||
const mismatched = pixelmatch(imgA.data, imgB.data, diff.data, width, height, { threshold: 0.1 });
|
||||
fs.writeFileSync(diffOut, PNG.sync.write(diff));
|
||||
return { mismatched, total: width*height, ratio: mismatched / (width*height) };
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const outDir = '/tmp';
|
||||
const fileArg = process.argv[2] || `file://${path.resolve(process.cwd(),'docs/prejoin_template.html')}`;
|
||||
const outFile = path.join(outDir, 'prejoin_capture_1280x720.png');
|
||||
console.log('Capturing', fileArg, '->', outFile);
|
||||
await capture(fileArg, outFile, 1280, 720);
|
||||
console.log('Saved capture to', outFile);
|
||||
// If baseline exists in repo root under baselines/, compare
|
||||
const baselineA = path.resolve(process.cwd(), 'baselines/prejoin_regen_1280x720.png');
|
||||
const baselineB = path.resolve(process.cwd(), 'baselines/prejoin_browserless_1280x720.png');
|
||||
let result = { captured: outFile, compared: false };
|
||||
if (fs.existsSync(baselineA)) {
|
||||
const diffOut = path.join(outDir, 'prejoin_diff_1280x720.png');
|
||||
const metrics = compare(outFile, baselineA, diffOut);
|
||||
result.compared = true; result.baseline = baselineA; result.diff = diffOut; result.metrics = metrics;
|
||||
console.log('Compared with', baselineA, 'metrics=', metrics);
|
||||
} else if (fs.existsSync(baselineB)) {
|
||||
const diffOut = path.join(outDir, 'prejoin_diff_1280x720.png');
|
||||
const metrics = compare(outFile, baselineB, diffOut);
|
||||
result.compared = true; result.baseline = baselineB; result.diff = diffOut; result.metrics = metrics;
|
||||
console.log('Compared with', baselineB, 'metrics=', metrics);
|
||||
} else {
|
||||
console.log('No baseline found (checked baselines/prejoin_regen_1280x720.png and baselines/prejoin_browserless_1280x720.png)');
|
||||
}
|
||||
fs.writeFileSync(path.join('/tmp','prejoin_capture_report.json'), JSON.stringify(result, null, 2));
|
||||
console.log('Report written to /tmp/prejoin_capture_report.json');
|
||||
} catch (err) {
|
||||
console.error('Failed:', err);
|
||||
process.exit(2);
|
||||
}
|
||||
})();
|
||||
|
||||
57
scripts/capture_regions_playwright.mjs
Normal file
57
scripts/capture_regions_playwright.mjs
Normal file
@ -0,0 +1,57 @@
|
||||
import fs from 'fs';
|
||||
import path from 'path';
|
||||
import { chromium } from 'playwright';
|
||||
|
||||
async function captureRegions(url) {
|
||||
const browser = await chromium.launch({ headless: true, args: ['--no-sandbox'] });
|
||||
const page = await browser.newPage({ viewport: { width: 1280, height: 720 } });
|
||||
await page.goto(url, { waitUntil: 'networkidle' });
|
||||
await page.waitForTimeout(500);
|
||||
|
||||
const regions = [
|
||||
{ name: 'video_preview', selector: '.video-preview' },
|
||||
{ name: 'mic_status', selector: '.mic-status' },
|
||||
{ name: 'controls', selector: '.controls' },
|
||||
{ name: 'form', selector: 'form' }
|
||||
];
|
||||
|
||||
const outDir = '/tmp';
|
||||
const report = { url, captures: [] };
|
||||
|
||||
for (const r of regions) {
|
||||
try {
|
||||
const el = await page.$(r.selector);
|
||||
if (!el) {
|
||||
console.warn('Selector not found', r.selector);
|
||||
report.captures.push({ name: r.name, selector: r.selector, found: false });
|
||||
continue;
|
||||
}
|
||||
const box = await el.boundingBox();
|
||||
const outPath = path.join(outDir, `prejoin_region_${r.name}.png`);
|
||||
// use element screenshot to crop
|
||||
await el.screenshot({ path: outPath });
|
||||
report.captures.push({ name: r.name, selector: r.selector, found: true, box, path: outPath });
|
||||
} catch (e) {
|
||||
console.error('Failed capture for', r.selector, e);
|
||||
report.captures.push({ name: r.name, selector: r.selector, found: false, error: String(e) });
|
||||
}
|
||||
}
|
||||
|
||||
await browser.close();
|
||||
const reportPath = path.join(outDir, 'prejoin_regions_report.json');
|
||||
fs.writeFileSync(reportPath, JSON.stringify(report, null, 2));
|
||||
console.log('Wrote report', reportPath);
|
||||
return reportPath;
|
||||
}
|
||||
|
||||
(async () => {
|
||||
try {
|
||||
const url = process.argv[2] || 'http://127.0.0.1:8000/docs/prejoin_template.html';
|
||||
const rp = await captureRegions(url);
|
||||
console.log('Done, report at', rp);
|
||||
} catch (e) {
|
||||
console.error('Error running captureRegions', e);
|
||||
process.exit(2);
|
||||
}
|
||||
})();
|
||||
|
||||
44
scripts/compare_regions.cjs
Normal file
44
scripts/compare_regions.cjs
Normal file
@ -0,0 +1,44 @@
|
||||
#!/usr/bin/env node
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const { PNG } = require('pngjs');
|
||||
let pixelmatch = require('pixelmatch');
|
||||
if (pixelmatch && typeof pixelmatch !== 'function' && pixelmatch.default && typeof pixelmatch.default === 'function') pixelmatch = pixelmatch.default;
|
||||
|
||||
const names = ['video_preview','mic_status','controls','form'];
|
||||
const out = [];
|
||||
for (const n of names) {
|
||||
const base = `/tmp/baseline_prejoin_region_${n}.png`;
|
||||
const cur = `/tmp/prejoin_region_${n}.png`;
|
||||
if (!fs.existsSync(base) && !fs.existsSync(cur)) {
|
||||
out.push({ region: n, foundBaseline: false, foundCurrent: false });
|
||||
continue;
|
||||
}
|
||||
if (!fs.existsSync(base)) {
|
||||
out.push({ region: n, foundBaseline: false, foundCurrent: true, current: cur });
|
||||
continue;
|
||||
}
|
||||
if (!fs.existsSync(cur)) {
|
||||
out.push({ region: n, foundBaseline: true, foundCurrent: false, baseline: base });
|
||||
continue;
|
||||
}
|
||||
try {
|
||||
const A = PNG.sync.read(fs.readFileSync(base));
|
||||
const B = PNG.sync.read(fs.readFileSync(cur));
|
||||
if (A.width !== B.width || A.height !== B.height) {
|
||||
out.push({ region: n, error: 'dim_mismatch', baseline: base, current: cur, baseSize: [A.width,A.height], curSize: [B.width,B.height] });
|
||||
continue;
|
||||
}
|
||||
const diff = new PNG({ width: A.width, height: A.height });
|
||||
const mismatched = pixelmatch(A.data, B.data, diff.data, A.width, A.height, { threshold: 0.1 });
|
||||
const diffPath = `/tmp/prejoin_region_diff_${n}.png`;
|
||||
fs.writeFileSync(diffPath, PNG.sync.write(diff));
|
||||
out.push({ region: n, baseline: base, current: cur, diff: diffPath, mismatched, total: A.width*A.height, ratio: mismatched/(A.width*A.height) });
|
||||
} catch (e) {
|
||||
out.push({ region: n, error: String(e) });
|
||||
}
|
||||
}
|
||||
const report = { generatedAt: new Date().toISOString(), report: out };
|
||||
fs.writeFileSync('/tmp/prejoin_regions_compare_report.json', JSON.stringify(report, null, 2));
|
||||
console.log('Wrote /tmp/prejoin_regions_compare_report.json');
|
||||
console.log(JSON.stringify(report, null, 2));
|
||||
53
scripts/visual_test_prejoin.cjs
Normal file
53
scripts/visual_test_prejoin.cjs
Normal file
@ -0,0 +1,53 @@
|
||||
#!/usr/bin/env node
|
||||
const { spawnSync } = require('child_process');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
|
||||
const args = process.argv.slice(2);
|
||||
let url = 'http://127.0.0.1:8000/docs/prejoin_template.html';
|
||||
for (let i=0;i<args.length;i++){
|
||||
if ((args[i]==='--url' || args[i]==='-u') && args[i+1]) { url = args[i+1]; i++; }
|
||||
}
|
||||
|
||||
console.log('[visual-test] using URL:', url);
|
||||
|
||||
// 1) run capture
|
||||
console.log('[visual-test] capturing regions...');
|
||||
let r = spawnSync('node', ['scripts/capture_regions_playwright.mjs', url], { stdio: 'inherit' });
|
||||
if (r.error) { console.error('[visual-test] capture failed', r.error); process.exit(2); }
|
||||
if (r.status !== 0) { console.error('[visual-test] capture script exited with', r.status); /* continue to comparison if images may exist */ }
|
||||
|
||||
// 2) run compare
|
||||
console.log('[visual-test] comparing regions...');
|
||||
let c = spawnSync('node', ['scripts/compare_regions.cjs'], { stdio: 'inherit' });
|
||||
if (c.error) { console.error('[visual-test] compare failed', c.error); process.exit(2); }
|
||||
if (c.status !== 0) { console.error('[visual-test] compare script exited with', c.status); }
|
||||
|
||||
// 3) read report
|
||||
const reportPath = '/tmp/prejoin_regions_compare_report.json';
|
||||
if (!fs.existsSync(reportPath)) { console.error('[visual-test] report not found at', reportPath); process.exit(2); }
|
||||
const report = JSON.parse(fs.readFileSync(reportPath,'utf8'));
|
||||
console.log('[visual-test] report generated at', report.generatedAt);
|
||||
|
||||
// 4) evaluate metrics against threshold
|
||||
const THRESHOLD = 0.001; // fail if any region has ratio > 0.1%
|
||||
let failed = false;
|
||||
for (const item of report.report) {
|
||||
if (item.ratio && item.ratio > THRESHOLD) {
|
||||
console.error(`[visual-test] REGION ${item.region} exceeded threshold: ratio=${item.ratio} mismatched=${item.mismatched} total=${item.total}`);
|
||||
failed = true;
|
||||
} else {
|
||||
console.log(`[visual-test] REGION ${item.region}: ratio=${item.ratio || 0} mismatched=${item.mismatched || 0}/${item.total || 0}`);
|
||||
}
|
||||
}
|
||||
|
||||
const outJson = '/tmp/prejoin_visual_test_summary.json';
|
||||
fs.writeFileSync(outJson, JSON.stringify({ url, threshold: THRESHOLD, generatedAt: new Date().toISOString(), report }, null, 2));
|
||||
console.log('[visual-test] summary written to', outJson);
|
||||
if (failed) {
|
||||
console.error('[visual-test] visual test FAILED');
|
||||
process.exit(3);
|
||||
}
|
||||
console.log('[visual-test] visual test PASSED');
|
||||
process.exit(0);
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user