diff --git a/tests/soak/runner.ts b/tests/soak/runner.ts index 0ed9d83..43bc432 100644 --- a/tests/soak/runner.ts +++ b/tests/soak/runner.ts @@ -16,8 +16,9 @@ import { createLogger } from './core/logger'; import { SessionPool } from './core/session-pool'; import { RoomCoordinator } from './core/room-coordinator'; import { DashboardServer } from './dashboard/server'; +import { Screencaster } from './core/screencaster'; import { getScenario, listScenarios } from './scenarios'; -import type { DashboardReporter, ScenarioContext } from './core/types'; +import type { DashboardReporter, ScenarioContext, Session } from './core/types'; function noopDashboard(): DashboardReporter { return { @@ -107,37 +108,9 @@ async function main(): Promise { logger, }); const coordinator = new RoomCoordinator(); + const screencaster = new Screencaster(logger); - // Optional dashboard server - let dashboardServer: DashboardServer | null = null; - let dashboard: DashboardReporter = noopDashboard(); - if (watch === 'dashboard') { - const port = Number(config.dashboardPort ?? 7777); - dashboardServer = new DashboardServer(port, logger, { - onStartStream: (key) => { - logger.info('stream_start_requested', { sessionKey: key }); - // Wired in Task 23 - }, - onStopStream: (key) => { - logger.info('stream_stop_requested', { sessionKey: key }); - }, - }); - await dashboardServer.start(); - dashboard = dashboardServer.reporter(); - const url = `http://localhost:${port}`; - console.log(`Dashboard: ${url}`); - try { - const opener = - process.platform === 'darwin' - ? 'open' - : process.platform === 'win32' - ? 'start' - : 'xdg-open'; - spawn(opener, [url], { stdio: 'ignore', detached: true }).unref(); - } catch { - // If auto-open fails, the URL is already printed. - } - } else if (watch === 'tiled') { + if (watch === 'tiled') { logger.warn('tiled_not_yet_implemented'); console.warn('Watch mode "tiled" not yet implemented (Task 24). Falling back to none.'); } @@ -151,11 +124,64 @@ async function main(): Promise { process.on('SIGINT', () => onSignal('SIGINT')); process.on('SIGTERM', () => onSignal('SIGTERM')); + let dashboardServer: DashboardServer | null = null; + let dashboard: DashboardReporter = noopDashboard(); let exitCode = 0; try { const sessions = await pool.acquire(accounts); logger.info('sessions_acquired', { count: sessions.length }); + // Build a session lookup for click-to-watch + const sessionsByKey = new Map(); + for (const s of sessions) sessionsByKey.set(s.key, s); + + // Dashboard with screencaster handlers now that sessions exist + if (watch === 'dashboard') { + const port = Number(config.dashboardPort ?? 7777); + dashboardServer = new DashboardServer(port, logger, { + onStartStream: (key) => { + const session = sessionsByKey.get(key); + if (!session) { + logger.warn('stream_start_unknown_session', { sessionKey: key }); + return; + } + screencaster + .start(key, session.page, (jpegBase64) => { + dashboardServer!.broadcast({ type: 'frame', sessionKey: key, jpegBase64 }); + }) + .catch((err) => + logger.error('screencast_start_failed', { + sessionKey: key, + error: err instanceof Error ? err.message : String(err), + }), + ); + }, + onStopStream: (key) => { + screencaster.stop(key).catch(() => { + // best-effort — errors already logged inside Screencaster + }); + }, + onDisconnect: () => { + screencaster.stopAll().catch(() => {}); + }, + }); + await dashboardServer.start(); + dashboard = dashboardServer.reporter(); + const url = `http://localhost:${port}`; + console.log(`Dashboard: ${url}`); + try { + const opener = + process.platform === 'darwin' + ? 'open' + : process.platform === 'win32' + ? 'start' + : 'xdg-open'; + spawn(opener, [url], { stdio: 'ignore', detached: true }).unref(); + } catch { + // If auto-open fails, the URL is already printed. + } + } + const ctx: ScenarioContext = { config, sessions, @@ -191,6 +217,7 @@ async function main(): Promise { }); exitCode = 1; } finally { + await screencaster.stopAll(); await pool.release(); if (dashboardServer) { await dashboardServer.stop();