import { test, expect, type Page } from '@playwright/test'; test.use({ baseURL: 'http://localhost:3000/', }); // ─── Helpers ───────────────────────────────────────────────────────────────── async function waitForMs(page: Page, ms: number) { await page.waitForTimeout(ms); } async function startFreshGame(page: Page) { await page.goto('/'); await page.evaluate(() => localStorage.clear()); await page.reload(); await page.waitForLoadState('networkidle'); await waitForMs(page, 3000); } async function waitForBridge(page: Page) { for (let attempt = 0; attempt < 30; attempt++) { const ready = await page.evaluate(() => !!(window as any).__TEST__); if (ready) return; await waitForMs(page, 1000); } throw new Error('Debug bridge (window.__TEST__) not available after 30s'); } async function clickTab(page: Page, label: string) { const tab = page.getByRole('tab', { name: new RegExp(label, 'i') }).first(); if (await tab.isVisible({ timeout: 2000 })) { await tab.click(); await waitForMs(page, 400); return true; } return false; } // ─── Test Suite ────────────────────────────────────────────────────────────── test.describe('Mana Loop - Comprehensive Playtest', () => { // ========================================================================= // SECTION 1: Basic UI & Starting State // ========================================================================= test.describe('1 - Basic UI & Starting State', () => { test.beforeEach(async ({ page }) => { await startFreshGame(page); }); test('game loads without console errors', async ({ page }) => { const errors: string[] = []; page.on('console', (msg) => { if (msg.type() === 'error') errors.push(msg.text()); }); await waitForMs(page, 2000); const reactErrors = errors.filter(e => e.includes('React') || e.includes('Minified') || e.includes('Error #') || e.includes('Maximum update depth') ); expect(reactErrors, `React errors found: ${JSON.stringify(reactErrors)}`).toHaveLength(0); }); test('ManaDisplay is visible and shows Transference mana', async ({ page }) => { await waitForMs(page, 500); // Transference starts unlocked but with 0 current mana. // The element section only shows elements with current > 0. // Click Gather a few times to get raw mana, then check the display. const gatherBtn = page.getByRole('button', { name: /gather/i }).first(); await expect(gatherBtn).toBeVisible({ timeout: 10000 }); // Gather some mana for (let i = 0; i < 5; i++) { await gatherBtn.click(); await waitForMs(page, 100); } // The raw mana display should be visible const bodyText = await page.textContent('body') || ''; expect(bodyText).toContain('mana'); }); test('TimeDisplay shows correct starting time', async ({ page }) => { await waitForMs(page, 500); const bodyText = await page.textContent('body'); expect(bodyText).toContain('Day 1'); }); test('Activity log is present and shows start message', async ({ page }) => { await waitForMs(page, 500); const bodyText = await page.textContent('body'); expect(bodyText).toBeTruthy(); }); }); // ========================================================================= // SECTION 2 - Stats Tab // ========================================================================= test.describe('2 - Stats Tab', () => { test.beforeEach(async ({ page }) => { await startFreshGame(page); }); test('navigate to Stats tab', async ({ page }) => { await waitForMs(page, 500); const visited = await clickTab(page, 'stats'); expect(visited).toBe(true); const bodyText = await page.textContent('body'); expect(bodyText).toBeTruthy(); }); test('Stats tab shows mana-related stats', async ({ page }) => { await waitForMs(page, 500); const visited = await clickTab(page, 'stats'); if (visited) { const bodyText = await page.textContent('body') || ''; // Stats tab should show some mana-related content expect(bodyText.length).toBeGreaterThan(100); } }); }); // ========================================================================= // SECTION 3 - Spire / Climbing // ========================================================================= test.describe('3 - Spire / Climbing', () => { test.beforeEach(async ({ page }) => { await startFreshGame(page); }); test('navigate to Spire tab without crash', async ({ page }) => { const errors: string[] = []; page.on('console', (msg) => { if (msg.type() === 'error') errors.push(msg.text()); }); await waitForMs(page, 500); const visited = await clickTab(page, 'spire'); expect(visited).toBe(true); await waitForMs(page, 500); const reactErrors = errors.filter(e => e.includes('Maximum update depth') || e.includes('Error #185') ); expect(reactErrors, `Spire tab errors: ${JSON.stringify(reactErrors)}`).toHaveLength(0); }); test('Climb the Spire button is visible on main page', async ({ page }) => { await waitForMs(page, 500); const climbBtn = page.getByRole('button', { name: /climb the spire/i }).first(); await expect(climbBtn).toBeVisible({ timeout: 10000 }); }); }); });