import { test, expect } from '@playwright/test'; /** * Fully deterministic end-to-end test. The Playwright webServer boots the real * stack (Vite :5173 → Express :4321) with LLM_STUB=1, so the backend uses the * deterministic turn simulator in server/stub.ts: the player nation's `military` * stat is set to (current + 5, clamped) each turn and exactly one event is * emitted (action → military, chat → diplomatic, jump → political). There is no * live LLM and no flakiness — every assertion below is an exact, predicted value. * * Beyond the core simulation loop it also asserts the polished command-center * UI: the dossier's animated stat bars, the map legend (generated from the * region map), and the new event cards (type icon + label + colored outcome * badge). The map itself is a canvas, so we assert on its DOM legend, not pixels. */ test.describe('History — deterministic simulation (LLM_STUB=1)', () => { test('scenario → nation → action → jump, with predicted stat changes', async ({ page }) => { await page.goto('/'); // Home: the scenario grid loads with all 5 presets. const presetCards = page.getByTestId('preset-card'); await expect(presetCards.first()).toBeVisible(); await expect(presetCards).toHaveCount(5); // Start the first scenario (World War II, 1939). await presetCards.first().getByRole('button', { name: 'Play' }).click(); // Nation select: the world brief is shown, then we lead the United Kingdom. await expect(page.getByTestId('world-background')).toBeVisible(); await page .getByTestId('nation-card-uk') .getByRole('button', { name: 'Play as United Kingdom' }) .click(); // Game screen mounts: map, command deck, and event feed all present; turn 1. await expect(page.getByTestId('world-map')).toBeVisible(); await expect(page.getByTestId('action-panel')).toBeVisible(); await expect(page.getByTestId('event-feed-panel')).toBeVisible(); await expect(page.getByTestId('turn-count')).toHaveText('1'); // The dossier shows the player's four stat bars (value-colored, animated). await expect(page.getByTestId('stat-military')).toBeVisible(); await expect(page.getByTestId('stat-economy')).toBeVisible(); await expect(page.getByTestId('stat-stability')).toBeVisible(); await expect(page.getByTestId('stat-population')).toBeVisible(); // The player (UK) starts at military 80, with no events yet. const military = page.getByTestId('stat-military'); await expect(military).toHaveText('80'); await expect(page.getByTestId('event-item')).toHaveCount(0); // The map legend is auto-generated from the region map: all 7 nations of the // scenario own at least one region, including the player (United Kingdom). await expect(page.getByTestId('map-legend-item')).toHaveCount(7, { timeout: 15000 }); await expect(page.getByTestId('map-legend-item').filter({ hasText: 'United Kingdom' })).toBeVisible(); // Take an action: the stub bumps UK military to 85 and emits a military event. await page.getByTestId('action-textarea').fill('Mobilize the home guard and fortify Dover'); await page.getByTestId('action-submit').click(); await expect(page.getByTestId('turn-count')).toHaveText('2', { timeout: 20000 }); await expect(military).toHaveText('85', { timeout: 20000 }); await expect(page.getByTestId('event-item')).toHaveCount(1); await expect( page.getByTestId('event-feed').getByText('Mobilize the home guard and fortify Dover').first(), ).toBeVisible(); // The new event card carries its type (icon + label) and a colored outcome badge. await expect(page.getByTestId('event-item').first().getByTestId('event-type-icon')).toBeVisible(); await expect(page.getByTestId('event-item').first().getByTestId('event-type')).toHaveText('military'); await expect(page.getByTestId('event-item').first().getByTestId('event-outcome')).toHaveText('success'); // Jump forward (no action): a "world moves on" political event, military → 90. await page.getByTestId('jump-forward').click(); await expect(page.getByTestId('turn-count')).toHaveText('3', { timeout: 20000 }); await expect(military).toHaveText('90', { timeout: 20000 }); await expect(page.getByTestId('event-item')).toHaveCount(2); await expect(page.getByTestId('event-feed').getByText('The world moves on').first()).toBeVisible(); // Newest first: the jump event (political / neutral) sits on top, and the // action event (military / success) is second — both with their outcome badges. await expect(page.getByTestId('event-item').first().getByTestId('event-type')).toHaveText('political'); await expect(page.getByTestId('event-item').first().getByTestId('event-outcome')).toHaveText('neutral'); await expect(page.getByTestId('event-item').nth(1).getByTestId('event-outcome')).toHaveText('success'); }); });