import { spawn } from 'node:child_process'; import { dirname, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; const projectRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); const serverSpecs = [ { name: 'viewer-2d', args: ['run', 'dev:2d'] }, { name: 'viewer-3d', args: ['run', 'dev:3d'] }, { name: 'viewer-2d-subpath', args: ['--workspace', '@hmwebviewer/viewer-2d-sample', 'run', 'serve:subpath:e2e'], }, { name: 'viewer-3d-subpath', args: ['--workspace', '@hmwebviewer/viewer-3d', 'run', 'serve:subpath:e2e'], }, ]; const servers = []; let stopping = false; function stopServers() { if (stopping) return; stopping = true; for (const server of servers) { if (!server.pid) continue; try { // npm과 그 아래 Vite process를 같은 process group 단위로 종료합니다. process.kill(-server.pid, 'SIGTERM'); } catch { // 이미 종료된 server는 별도 처리하지 않습니다. } } } function startServer({ name, args }) { return new Promise((resolveReady, rejectReady) => { const child = spawn('npm', args, { cwd: projectRoot, detached: true, env: { ...process.env, NO_PROXY: '127.0.0.1,localhost', no_proxy: '127.0.0.1,localhost', }, stdio: ['ignore', 'pipe', 'pipe'], }); servers.push(child); let ready = false; const timeout = setTimeout(() => { rejectReady(new Error(`${name} server가 90초 안에 준비되지 않았습니다.`)); }, 90_000); const inspect = (chunk) => { const output = chunk.toString(); process.stderr.write(`[${name}] ${output}`); if (!ready && output.includes('Local:')) { ready = true; clearTimeout(timeout); resolveReady(); } }; child.stdout.on('data', inspect); child.stderr.on('data', inspect); child.once('exit', (code, signal) => { clearTimeout(timeout); if (!ready) { rejectReady( new Error(`${name} server가 준비 전에 종료됐습니다. code=${code} signal=${signal}`), ); } }); }); } process.once('SIGINT', () => { stopServers(); process.exit(130); }); process.once('SIGTERM', () => { stopServers(); process.exit(143); }); try { await Promise.all(serverSpecs.map(startServer)); const playwright = spawn( resolve(projectRoot, 'node_modules/.bin/playwright'), ['test', ...process.argv.slice(2)], { cwd: projectRoot, env: { ...process.env, E2E_SERVERS_READY: '1', NO_PROXY: '127.0.0.1,localhost', no_proxy: '127.0.0.1,localhost', }, stdio: 'inherit', }, ); const exitCode = await new Promise((resolveExit) => { playwright.once('exit', (code, signal) => { if (signal) resolveExit(1); else resolveExit(code ?? 1); }); }); process.exitCode = exitCode; } catch (error) { console.error(error); process.exitCode = 1; } finally { stopServers(); }