const fs = require("node:fs"); const os = require("node:os"); const path = require("node:path"); const { spawn } = require("node:child_process"); const repoRoot = path.resolve(__dirname, ".."); const baseUrl = process.env.SELENIUM_BASE_URL || "http://127.0.0.1:5095"; const healthUrl = new URL("/api/health", baseUrl).toString(); const smokeScript = process.argv[2] || "tests/e2e/smoke.js"; const tempDbPath = path.join(os.tmpdir(), `rpgroller-selenium-${Date.now()}-${Math.random().toString(16).slice(2)}.db`); function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } async function waitForHealthCheck() { for (let attempt = 0; attempt < 60; attempt += 1) { try { const response = await fetch(healthUrl); if (response.ok) { return; } } catch { } await delay(500); } throw new Error(`Application failed to start on ${baseUrl}.`); } function spawnProcess(command, args, options) { return new Promise((resolve, reject) => { const child = spawn(command, args, options); child.once("error", reject); resolve(child); }); } async function run() { const app = await spawnProcess( "dotnet", ["run", "--project", "RpgRoller/RpgRoller.csproj", "--verbosity", "minimal", "--urls", baseUrl], { cwd: repoRoot, stdio: "inherit", env: { ...process.env, ConnectionStrings__RpgRoller: `Data Source=${tempDbPath}` } } ); try { await waitForHealthCheck(); const smoke = await spawnProcess("node", [smokeScript], { cwd: repoRoot, stdio: "inherit", env: { ...process.env, SELENIUM_BASE_URL: baseUrl } }); const exitCode = await new Promise((resolve, reject) => { smoke.once("error", reject); smoke.once("exit", resolve); }); if (exitCode !== 0) { throw new Error(`Selenium smoke exited with code ${exitCode}.`); } } finally { app.kill("SIGTERM"); await delay(500); if (!app.killed) { app.kill("SIGKILL"); } if (fs.existsSync(tempDbPath)) { fs.rmSync(tempDbPath, { force: true }); } } } run().catch((error) => { console.error(error.stack || error); process.exitCode = 1; });