import { execSync } from 'node:child_process'; import path from 'node:path'; import { expect, test } from '@playwright/test'; // Repo root — docker-compose.yml lives here; `docker compose restart` runs on // this host (the test runner), browsers stay remote. CI points E2E_COMPOSE_ARGS at the // isolated stack file + project (-f docker-compose.ci.yml -p space-wanderer-ci) instead of the default one. const repoRoot = path.join(__dirname, '..'); const composeArgs = process.env.E2E_COMPOSE_ARGS ?? ''; test('app survives a container restart (stack + volume persistence)', async ({ page }) => { test.setTimeout(180_000); // Baseline: the app serves and reports its version. const resBefore = await page.request.get('/api/version'); expect(resBefore.status()).toBe(200); const before = (await resBefore.json()) as { version: string }; expect(before.version).toMatch(/^\d+\.\d+\.\d+$/); // Restart the app container mid-run — the named DB volume must survive it. execSync(`docker compose ${composeArgs} restart space-wanderer`, { cwd: repoRoot, stdio: 'pipe' }); // Wait for the app to come back serving. let after: { version: string } | undefined; await expect .poll(async () => { try { const res = await page.request.get('/api/version'); if (res.status() === 200) after = (await res.json()) as { version: string }; return !!after; } catch { return false; // connection refused / 502 while the container starts } }, { timeout: 120_000, message: 'waiting for /api/version after docker compose restart' }) .toBe(true); expect(after).toBeDefined(); expect(after!.version).toBe(before.version); // same build, not a re-seeded stack // The page itself serves again with the landing title. await page.goto('/'); await expect(page.getByRole('heading', { name: 'SpaceWanderer' })).toBeVisible(); });