From 6a623c738abd61aed368988c4e0fc8afbea6ebf9 Mon Sep 17 00:00:00 2001 From: Kamil Krzywda Date: Mon, 28 Sep 2026 21:35:15 +0000 Subject: [PATCH] e2e: world map smoke tests + jump persistence across container restart --- e2e/smoke.spec.ts | 28 ++++++++++++++++++++++++++++ e2e/state.spec.ts | 18 ++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/e2e/smoke.spec.ts b/e2e/smoke.spec.ts index e912262..a6d22fa 100644 --- a/e2e/smoke.spec.ts +++ b/e2e/smoke.spec.ts @@ -1,5 +1,9 @@ import { expect, test } from '@playwright/test'; +// Mirrors the /api/world response shape (lib/game/save.ts) — kept local so the spec stays self-contained. +interface WorldSystemDto { id: number; name: string; x: number; y: number; discovered: boolean; current: boolean } +interface WorldState { systems: WorldSystemDto[]; edges: [number, number][]; turn: number } + test('landing page loads with the SpaceWanderer title', async ({ page }) => { await page.goto('/'); await expect(page.getByRole('heading', { name: 'SpaceWanderer' })).toBeVisible(); @@ -21,6 +25,30 @@ test('tabs switch panels client-side (event log terminal)', async ({ page }) => await expect(page.getByText('SHIP LOG // PLACEHOLDER FEED')).toBeVisible(); }); +test('world map shows the generated starting system and its neighbours', async ({ page }) => { + await page.goto('/'); + // Live chart title carries the turn counter; a fresh save sits at the start with six jump routes. + await expect(page.getByText(/GALACTIC CHART \/\/ JUMP ROUTES — TURN \d+/)).toBeVisible(); + const jumpButtons = page.getByRole('button', { name: /\/\/ JUMP$/ }); + await expect(jumpButtons.first()).toBeVisible(); + await expect(jumpButtons).toHaveCount(6); +}); + +test('jumping to an adjacent system updates the world state and turn counter', async ({ page }) => { + const before = (await (await page.request.get('/api/world')).json()) as WorldState; + const currentId = before.systems.find((s) => s.current)!.id; + const edge = before.edges.find(([a, b]) => a === currentId || b === currentId)!; + const targetId = edge[0] === currentId ? edge[1] : edge[0]; + const targetName = before.systems.find((s) => s.id === targetId)!.name; + + await page.goto('/'); + await page.getByRole('button', { name: `${targetName.toUpperCase()} // JUMP` }).click(); + + const after = (await (await page.request.get('/api/world')).json()) as WorldState; + expect(after.turn).toBe(before.turn + 1); + expect(after.systems.find((s) => s.current)?.id).toBe(targetId); +}); + test('GET /api/version reports the app version as JSON', async ({ page }) => { const res = await page.request.get('/api/version'); expect(res.status()).toBe(200); diff --git a/e2e/state.spec.ts b/e2e/state.spec.ts index 0e0a046..3e22db7 100644 --- a/e2e/state.spec.ts +++ b/e2e/state.spec.ts @@ -8,6 +8,10 @@ import { expect, test } from '@playwright/test'; const repoRoot = path.join(__dirname, '..'); const composeArgs = process.env.E2E_COMPOSE_ARGS ?? ''; +// Mirrors the /api/world response shape (lib/game/save.ts) — kept local so the spec stays self-contained. +interface WorldSystemDto { id: number; name: string; x: number; y: number; discovered: boolean; current: boolean } +interface WorldState { systems: WorldSystemDto[]; edges: [number, number][]; turn: number } + test('app survives a container restart (stack + volume persistence)', async ({ page }) => { test.setTimeout(180_000); @@ -17,6 +21,15 @@ test('app survives a container restart (stack + volume persistence)', async ({ p const before = (await resBefore.json()) as { version: string }; expect(before.version).toMatch(/^\d+\.\d+\.\d+$/); + // Game-state baseline: jump once so there is real progress that must survive the restart. + const worldBefore = (await (await page.request.get('/api/world')).json()) as WorldState; + const currentId = worldBefore.systems.find((s) => s.current)!.id; + const edge = worldBefore.edges.find(([a, b]) => a === currentId || b === currentId)!; + const targetId = edge[0] === currentId ? edge[1] : edge[0]; + const jumpRes = await page.request.post('/api/jump', { data: { systemId: targetId } }); + expect(jumpRes.status()).toBe(200); + const worldJumped = (await jumpRes.json()) as WorldState; + // Restart the app container mid-run — the named DB volume must survive it. execSync(`docker compose ${composeArgs} restart space-wanderer`, { cwd: repoRoot, stdio: 'pipe' }); @@ -37,6 +50,11 @@ test('app survives a container restart (stack + volume persistence)', async ({ p expect(after).toBeDefined(); expect(after!.version).toBe(before.version); // same build, not a re-seeded stack + // Game state survived: same turn counter and jumped-to current system as before the restart. + const worldAfter = (await (await page.request.get('/api/world')).json()) as WorldState; + expect(worldAfter.turn).toBe(worldJumped.turn); + expect(worldAfter.systems.find((s) => s.current)?.id).toBe(targetId); + // The page itself serves again with the landing title. await page.goto('/'); await expect(page.getByRole('heading', { name: 'SpaceWanderer' })).toBeVisible();