e2e: world map smoke tests + jump persistence across container restart
This commit is contained in:
@@ -1,5 +1,9 @@
|
|||||||
import { expect, test } from '@playwright/test';
|
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 }) => {
|
test('landing page loads with the SpaceWanderer title', async ({ page }) => {
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
await expect(page.getByRole('heading', { name: 'SpaceWanderer' })).toBeVisible();
|
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();
|
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 }) => {
|
test('GET /api/version reports the app version as JSON', async ({ page }) => {
|
||||||
const res = await page.request.get('/api/version');
|
const res = await page.request.get('/api/version');
|
||||||
expect(res.status()).toBe(200);
|
expect(res.status()).toBe(200);
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ import { expect, test } from '@playwright/test';
|
|||||||
const repoRoot = path.join(__dirname, '..');
|
const repoRoot = path.join(__dirname, '..');
|
||||||
const composeArgs = process.env.E2E_COMPOSE_ARGS ?? '';
|
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('app survives a container restart (stack + volume persistence)', async ({ page }) => {
|
||||||
test.setTimeout(180_000);
|
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 };
|
const before = (await resBefore.json()) as { version: string };
|
||||||
expect(before.version).toMatch(/^\d+\.\d+\.\d+$/);
|
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.
|
// Restart the app container mid-run — the named DB volume must survive it.
|
||||||
execSync(`docker compose ${composeArgs} restart space-wanderer`, { cwd: repoRoot, stdio: 'pipe' });
|
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).toBeDefined();
|
||||||
expect(after!.version).toBe(before.version); // same build, not a re-seeded stack
|
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.
|
// The page itself serves again with the landing title.
|
||||||
await page.goto('/');
|
await page.goto('/');
|
||||||
await expect(page.getByRole('heading', { name: 'SpaceWanderer' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'SpaceWanderer' })).toBeVisible();
|
||||||
|
|||||||
Reference in New Issue
Block a user