import { test, expect, type Page } from '@playwright/test'; // v0.9.0 — single-screen 3-pane shell + left Assets pane against the live origin. // The panes render from the shell's single /api/state poll (lib/useGameState.ts); time // controls POST to /api/sim and refetch immediately, so UI assertions settle within a tick. interface SimState { clockMs: number; timeScale: number; paused: boolean; } interface StateBody { account: { cash: number; reputation: number }; simState: SimState; planes: Array>; contracts: Array>; } async function getState(page: Page): Promise { const res = await page.request.get('/api/state'); expect(res.status()).toBe(200); return (await res.json()) as StateBody; } // Leave the live instance in a sane state for the other specs (scale 1, running) — same // pattern as e2e/sim.spec.ts. test.afterEach(async ({ page }) => { const res = await page.request.post('/api/sim', { data: { timeScale: 1, paused: false } }); expect(res.status()).toBe(200); }); test('left pane renders wallet, reputation and the starter fleet at Frankfurt', async ({ page }) => { await page.goto('/'); await expect(page.getByTestId('left-pane')).toBeVisible(); // Wallet: cash > 0 (formatted EUR) — poll until the first snapshot lands. const cashEl = page.getByTestId('wallet-cash'); await expect(cashEl).toBeVisible({ timeout: 15_000 }); const cashText = (await cashEl.textContent()) ?? ''; expect(Number(cashText.replace(/[^0-9.]/g, ''))).toBeGreaterThan(0); // Reputation present and numeric. await expect(page.getByTestId('reputation')).toBeVisible(); expect(Number((await page.getByTestId('reputation').textContent()) ?? 'NaN')).toBeGreaterThanOrEqual(0); // Fleet: the starter ATR 72-600 at Frankfurt (EDDF). const row = page.getByTestId('fleet-list').locator('.plane-row').first(); await expect(row, 'starter plane row appears once the first snapshot lands').toBeVisible({ timeout: 15_000 }); await expect(row).toContainText('ATR 72-600'); await expect(row).toContainText('Frankfurt'); // Sim clock renders (UTC wall-clock of the game time). await expect(page.getByTestId('sim-clock')).toContainText(/UTC/); }); test('time controls: pause freezes the sim and the power-of-two slider sets timeScale', async ({ page }) => { await page.goto('/'); const pauseBtn = page.getByTestId('pause-toggle'); await expect(pauseBtn).toBeVisible({ timeout: 15_000 }); // Pause via the UI button — assert against the authoritative /api/state… await pauseBtn.click(); await expect.poll(async () => (await getState(page)).simState.paused, { timeout: 10_000 }).toBe(true); // …and in the pane itself (the refetch after POST flips the button to Resume). await expect(pauseBtn).toHaveText('Resume'); // Scale via the power-of-two slider. End jumps to max (t=1 → 64×): the label updates // optimistically on input, and release flushes the debounced POST immediately. const slider = page.getByTestId('time-scale-slider'); await slider.focus(); await page.keyboard.press('End'); await expect(page.getByTestId('time-scale-value')).toHaveText('64×', { timeout: 10_000 }); await expect.poll(async () => (await getState(page)).simState.timeScale, { timeout: 10_000 }).toBe(64); // Home jumps to min (t=0 → 1×). await page.keyboard.press('Home'); await expect(page.getByTestId('time-scale-value')).toHaveText('1×', { timeout: 10_000 }); await expect.poll(async () => (await getState(page)).simState.timeScale, { timeout: 10_000 }).toBe(1); // Game clock shows seconds: YYYY-MM-DD HH:MM:SS UTC. await expect(page.getByTestId('sim-clock')).toContainText(/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2} UTC/); // Restore the live instance to scale 1 + unpaused (afterEach enforces this too). const restore = await page.request.post('/api/sim', { data: { timeScale: 1, paused: false } }); expect(restore.status()).toBe(200); const after = await getState(page); expect(after.simState.timeScale).toBe(1); expect(after.simState.paused).toBe(false); }); test('right pane placeholder exists with the Jobs & Market heading', async ({ page }) => { await page.goto('/'); const right = page.getByTestId('right-pane'); await expect(right).toBeVisible(); await expect(right).toContainText('Jobs & Market'); });