import { chromium } from '@playwright/test'; import { execFileSync } from 'node:child_process'; import { mkdirSync } from 'node:fs'; import { resolve } from 'node:path'; const COMPOSE_ARGS = [ 'compose', '-f', 'docker-compose.yml', '-f', 'tests/docker-compose.test.yml', ]; const HEALTH_URL = 'http://localhost:3000/api/health'; export const AUTH_DIR = resolve(__dirname, '.auth'); export const AUTH_FILE = resolve(AUTH_DIR, 'admin.json'); const MAX_WAIT_MS = 180_000; const POLL_INTERVAL_MS = 3_000; async function waitForHealth(): Promise { const start = Date.now(); let attempt = 0; while (Date.now() - start < MAX_WAIT_MS) { attempt++; try { const res = await fetch(HEALTH_URL); if (res.status === 200) { console.log(`[global-setup] App is healthy (attempt ${attempt})`); return; } console.log(`[global-setup] Health check attempt ${attempt}: HTTP ${res.status}, retrying...`); } catch (err: unknown) { const msg = err instanceof Error ? err.message : String(err); console.log(`[global-setup] Health check attempt ${attempt}: ${msg}, retrying in ${POLL_INTERVAL_MS / 1000}s...`); } await new Promise((r) => setTimeout(r, POLL_INTERVAL_MS)); } console.error('[global-setup] Health check timed out. Container logs:'); try { execFileSync('docker', [...COMPOSE_ARGS, 'logs', '--tail=50'], { stdio: 'inherit', cwd: process.cwd(), env: { ...process.env, CLICKHOUSE_PASSWORD: 'test-clickhouse-password-2026' } }); } catch { /* ignore */ } throw new Error(`App did not become healthy within ${MAX_WAIT_MS}ms`); } async function seedAuthState(): Promise { console.log('[global-setup] Seeding auth state via browser login...'); mkdirSync(AUTH_DIR, { recursive: true }); const browser = await chromium.launch(); const page = await browser.newPage(); try { await page.goto('http://localhost:3000/login'); await page.getByRole('textbox', { name: /username/i }).fill('testadmin'); await page.getByRole('textbox', { name: /password/i }).fill('TestPassword2026!'); await page.getByRole('button', { name: /sign in/i }).click(); // Wait for redirect away from /login await page.waitForURL((url) => !url.pathname.includes('/login'), { timeout: 15_000 }); console.log(`[global-setup] Login succeeded, landed on: ${page.url()}`); await page.context().storageState({ path: AUTH_FILE }); console.log('[global-setup] Auth state saved to', AUTH_FILE); } finally { await browser.close(); } } export default async function globalSetup() { console.log('[global-setup] Starting Docker Compose test stack...'); execFileSync('docker', [ ...COMPOSE_ARGS, 'up', '-d', '--build', '--wait', '--wait-timeout', '120', ], { stdio: 'inherit', cwd: process.cwd(), env: { ...process.env, CLICKHOUSE_PASSWORD: 'test-clickhouse-password-2026' }, }); console.log('[global-setup] Containers up. Waiting for /api/health...'); await waitForHealth(); await seedAuthState(); console.log('[global-setup] Done.'); }