sendou.ink/e2e/global-setup.ts
2026-01-08 20:56:04 +02:00

186 lines
5.5 KiB
TypeScript

import { type ChildProcess, execSync, spawn } from "node:child_process";
import fs from "node:fs";
import type { FullConfig } from "@playwright/test";
import { E2E_BASE_PORT } from "~/utils/playwright";
const WORKER_COUNT = Number(process.env.E2E_WORKERS) || 4;
const DEBUG = process.env.E2E_DEBUG === "true";
const SERVER_PROCESSES: ChildProcess[] = [];
const MINIO_MARKER_FILE = ".e2e-minio-started";
declare global {
var __E2E_SERVERS__: ChildProcess[];
}
async function waitForMinio(timeout = 60000): Promise<boolean> {
const start = Date.now();
while (Date.now() - start < timeout) {
try {
const response = await fetch("http://127.0.0.1:9000/minio/health/live");
if (response.ok) {
return true;
}
} catch {
// MinIO not ready yet
}
await new Promise((resolve) => setTimeout(resolve, 1000));
}
return false;
}
async function ensureMinioRunning(): Promise<boolean> {
// Check if MinIO is already running
try {
const response = await fetch("http://127.0.0.1:9000/minio/health/live");
if (response.ok) {
// biome-ignore lint/suspicious/noConsole: CLI script output
console.log("MinIO is already running");
return false;
}
} catch {
// MinIO not running, we need to start it
}
// biome-ignore lint/suspicious/noConsole: CLI script output
console.log("Starting MinIO...");
execSync("docker compose up -d minio", { stdio: "inherit" });
const isReady = await waitForMinio();
if (!isReady) {
throw new Error("MinIO failed to start within timeout");
}
// biome-ignore lint/suspicious/noConsole: CLI script output
console.log("MinIO is ready");
fs.writeFileSync(MINIO_MARKER_FILE, "");
return true;
}
function killProcessOnPort(port: number): void {
try {
// Try to find and kill any process on this port (macOS/Linux)
execSync(`lsof -ti :${port} | xargs -r kill -9 2>/dev/null || true`, {
stdio: "pipe",
});
} catch {
// Ignore errors - port might already be free
}
}
async function waitForServer(port: number, timeout = 120000): Promise<void> {
const start = Date.now();
while (Date.now() - start < timeout) {
try {
const response = await fetch(`http://localhost:${port}/`);
if (response.ok || response.status === 404) {
// 404 is fine - server is up, just no route at /
return;
}
} catch {
// Server not ready yet
}
await new Promise((resolve) => setTimeout(resolve, 1000));
}
throw new Error(`Server on port ${port} did not start within ${timeout}ms`);
}
async function globalSetup(_config: FullConfig) {
// biome-ignore lint/suspicious/noConsole: CLI script output
console.log(`\nStarting e2e test setup with ${WORKER_COUNT} workers...`);
// Start MinIO if not already running
await ensureMinioRunning();
// Build the app once with E2E test flag so VITE_E2E_TEST_RUN is embedded
// Use port 6173 as the base - tests will rewrite URLs as needed
// biome-ignore lint/suspicious/noConsole: CLI script output
console.log("Building the application...");
execSync("npm run build", {
stdio: "inherit",
env: {
...process.env,
VITE_E2E_TEST_RUN: "true",
VITE_SITE_DOMAIN: `http://localhost:${E2E_BASE_PORT}`,
},
});
// Prepare databases and start servers for each worker
const serverPromises: Promise<void>[] = [];
// Kill any existing processes on our ports before starting
// biome-ignore lint/suspicious/noConsole: CLI script output
console.log("Cleaning up any existing processes on e2e ports...");
for (let i = 0; i < WORKER_COUNT; i++) {
killProcessOnPort(E2E_BASE_PORT + i);
}
// Wait briefly for ports to be released
await new Promise((resolve) => setTimeout(resolve, 500));
for (let i = 0; i < WORKER_COUNT; i++) {
const port = E2E_BASE_PORT + i;
const dbPath = `db-test-e2e-${i}.sqlite3`;
// Ensure database exists with migrations
if (!fs.existsSync(dbPath)) {
// biome-ignore lint/suspicious/noConsole: CLI script output
console.log(`Setting up database for worker ${i}: ${dbPath}`);
execSync(`DB_PATH=${dbPath} npm run migrate up`, { stdio: "inherit" });
}
// Start server
// biome-ignore lint/suspicious/noConsole: CLI script output
console.log(`Starting server for worker ${i} on port ${port}...`);
const serverProcess = spawn("npm", ["start"], {
env: {
...process.env,
DB_PATH: dbPath,
PORT: String(port),
DISCORD_CLIENT_ID: "123",
DISCORD_CLIENT_SECRET: "secret",
SESSION_SECRET: "secret",
VITE_SITE_DOMAIN: `http://localhost:${port}`,
VITE_E2E_TEST_RUN: "true",
STORAGE_END_POINT: "http://127.0.0.1:9000",
STORAGE_ACCESS_KEY: "minio-user",
STORAGE_SECRET: "minio-password",
STORAGE_REGION: "us-east-1",
STORAGE_BUCKET: "sendou",
},
detached: false,
});
SERVER_PROCESSES.push(serverProcess);
if (DEBUG) {
serverProcess.stdout?.on("data", (data) => {
// biome-ignore lint/suspicious/noConsole: CLI script output
console.log(`[Worker ${i}] ${data.toString()}`);
});
serverProcess.stderr?.on("data", (data) => {
// biome-ignore lint/suspicious/noConsole: CLI script output
console.error(`[Worker ${i} ERROR] ${data.toString()}`);
});
}
serverPromises.push(
waitForServer(port).then(() => {
// biome-ignore lint/suspicious/noConsole: CLI script output
console.log(`Server for worker ${i} is ready on port ${port}`);
}),
);
}
// Wait for all servers to be ready
await Promise.all(serverPromises);
// Store server processes globally for teardown
global.__E2E_SERVERS__ = SERVER_PROCESSES;
// biome-ignore lint/suspicious/noConsole: CLI script output
console.log("\nAll servers started successfully!\n");
}
export default globalSetup;