Files
galaxy-game/ui/frontend/tests/e2e/inspector-ship-group.spec.ts
T
Ilia Denisov 3694847792 ui/phase-19: seed an authenticated session in the synthetic-report e2e
The root +layout.svelte redirects anonymous traffic to /login, so a
fresh CI browser context never gets to render the lobby's
DEV-gated synthetic-report section — the previous spec relied on
leftover session state in the local browser and silently broke on
clean runners (local-ci run 23).

Bootstrap the session through /__debug/store before navigating to
/lobby: load a device keypair, set a deterministic device session
id. The synthetic flow itself still bypasses the gateway entirely;
the seed only ensures `session.status === "authenticated"` so the
layout guard lets the lobby through.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-10 13:39:08 +02:00

164 lines
3.8 KiB
TypeScript

// Phase 19 end-to-end smoke against the synthetic-report path. Loads
// a hand-crafted JSON with a Tancordia-style mix of planets and ship
// groups through the DEV-only file picker on `/lobby`, lets the
// in-game shell layout swap into synthetic mode, and asserts the map
// canvas mounts. Detailed click / hit-test fidelity for ship-group
// variants lives in the unit tests (`tests/state-binding-groups.test.ts`
// and `tests/inspector-ship-group.test.ts`); this spec catches the
// glue: lobby loader → in-memory registry → layout bypass → renderer
// boot.
import { expect, test, type Page } from "@playwright/test";
interface DebugSurface {
ready: true;
loadSession(): Promise<unknown>;
setDeviceSessionId(id: string): Promise<void>;
}
declare global {
interface Window {
__galaxyDebug?: DebugSurface;
}
}
// Seed an authenticated session through `/__debug/store` so the
// root layout's redirect-to-login guard passes. The synthetic flow
// itself does not talk to the gateway, but the session check still
// runs at every navigation.
async function seedSession(page: Page): Promise<void> {
await page.goto("/__debug/store");
await expect(page.getByTestId("debug-store-ready")).toBeVisible();
await page.waitForFunction(() => window.__galaxyDebug?.ready === true);
await page.evaluate(async () => {
await window.__galaxyDebug!.loadSession();
await window.__galaxyDebug!.setDeviceSessionId(
"phase-19-synthetic-session",
);
});
}
const SYNTHETIC_REPORT_FIXTURE = {
turn: 39,
mapWidth: 200,
mapHeight: 200,
mapPlanets: 4,
race: "Earthlings",
player: [
{
name: "Earthlings",
drive: 5,
weapons: 3,
shields: 2,
cargo: 1,
population: 1000,
industry: 1000,
planets: 2,
relation: "-",
votes: 5,
extinct: false,
},
],
localPlanet: [
{
number: 1,
name: "Earth",
x: 50,
y: 100,
size: 1000,
population: 1000,
industry: 1000,
resources: 10,
production: "Capital",
capital: 0,
material: 0,
colonists: 100,
freeIndustry: 1000,
},
{
number: 2,
name: "Mars",
x: 150,
y: 100,
size: 500,
population: 500,
industry: 500,
resources: 5,
production: "Capital",
capital: 0,
material: 0,
colonists: 50,
freeIndustry: 500,
},
],
otherPlanet: [],
uninhabitedPlanet: [],
unidentifiedPlanet: [
{ number: 3, x: 50, y: 50 },
{ number: 4, x: 150, y: 50 },
],
localShipClass: [
{
name: "Frontier",
drive: 5,
armament: 0,
weapons: 0,
shields: 0,
cargo: 1,
mass: 12,
},
],
localGroup: [
{
id: "11111111-2222-3333-4444-555555555555",
number: 2,
class: "Frontier",
tech: { drive: 5, weapons: 0, shields: 0, cargo: 1 },
cargo: "-",
load: 0,
destination: 1,
speed: 0,
mass: 12,
state: "In_Orbit",
},
],
otherGroup: [],
incomingGroup: [
{
origin: 4,
destination: 1,
distance: 50,
speed: 25,
mass: 4,
},
],
unidentifiedGroup: [],
localFleet: [],
};
test("synthetic-report loader navigates from lobby to map and renders", async ({
page,
}) => {
await seedSession(page);
await page.goto("/lobby");
await expect(page.getByTestId("lobby-synthetic-section")).toBeVisible();
const file = page.getByTestId("lobby-synthetic-file");
await file.setInputFiles({
name: "phase19.json",
mimeType: "application/json",
buffer: Buffer.from(JSON.stringify(SYNTHETIC_REPORT_FIXTURE)),
});
await page.waitForURL(/\/games\/synthetic-[^/]+\/map$/, {
timeout: 5_000,
});
// The renderer canvas mounts inside the active-view host. Even if
// the WebGL/WebGPU backend is unavailable in CI, the layout still
// reaches `ready` once the report is loaded — the assertion is
// gentle on purpose so the spec doesn't flake on headless renders.
const canvas = page.locator("canvas");
await expect(canvas.first()).toBeVisible({ timeout: 10_000 });
});