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 12 - Debug Tab & Cheats // ========================================================================= test.describe('12 - Debug Tab & Cheats', () => { test.beforeEach(async ({ page }) => { await startFreshGame(page); }); test('navigate to Debug 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, 'debug'); expect(visited).toBe(true); await waitForMs(page, 500); const reactErrors = errors.filter(e => e.includes('React') || e.includes('Minified') || e.includes('Error #') ); expect(reactErrors, `React errors in Debug: ${JSON.stringify(reactErrors)}`).toHaveLength(0); }); test('Debug tab shows Game State section by default', async ({ page }) => { await waitForMs(page, 500); const visited = await clickTab(page, 'debug'); if (visited) { const bodyText = await page.textContent('body') || ''; expect(bodyText).toContain('Game State'); } }); test('Debug tab has Mana Debug buttons', async ({ page }) => { await waitForMs(page, 500); const visited = await clickTab(page, 'debug'); if (visited) { const fillBtn = page.getByTestId('debug-mana-fill'); await expect(fillBtn).toBeVisible({ timeout: 5000 }); } }); }); // ========================================================================= // SECTION 13 - Deep Bug Hunting with Debug Bridge // ========================================================================= test.describe('13 - Deep Bug Hunting (Debug Bridge)', () => { test.beforeEach(async ({ page }) => { await startFreshGame(page); await waitForBridge(page); }); test('mana regen values in ManaDisplay are correct', async ({ page }) => { await waitForMs(page, 1000); const bodyText = await page.textContent('body') || ''; // Check that mana regen shows positive values for Transference const matches = bodyText.match(/\+[\d.]+(\/hr)?/g); console.log(`HUNT: Found regen patterns: ${JSON.stringify(matches)}`); // Should have at least one positive regen value expect(matches && matches.length > 0).toBe(true); }); test('mana values stay consistent after multiple ticks', async ({ page }) => { await waitForMs(page, 500); // Run 100 ticks via the bridge await page.evaluate(() => { (window as any).__TEST__.runTicks(100); }); await waitForMs(page, 500); // Game should still be running (no crash) const bodyAfter = await page.textContent('body') || ''; expect(bodyAfter).toBeTruthy(); console.log('HUNT: Game still running after 100 ticks ✓'); }); test('debug bridge can read all store states', async ({ page }) => { const storeKeys = await page.evaluate(() => { const t = (window as any).__TEST__; return { hasGameStore: !!t.useGameStore, hasManaStore: !!t.useManaStore, hasCombatStore: !!t.useCombatStore, hasCraftingStore: !!t.useCraftingStore, hasAttunementStore: !!t.useAttunementStore, hasPrestigeStore: !!t.usePrestigeStore, hasDisciplineStore: !!t.useDisciplineStore, hasUIStore: !!t.useUIStore, hasRunTicks: typeof t.runTicks === 'function', }; }); expect(storeKeys.hasGameStore).toBe(true); expect(storeKeys.hasManaStore).toBe(true); expect(storeKeys.hasCombatStore).toBe(true); expect(storeKeys.hasCraftingStore).toBe(true); expect(storeKeys.hasAttunementStore).toBe(true); expect(storeKeys.hasPrestigeStore).toBe(true); expect(storeKeys.hasDisciplineStore).toBe(true); expect(storeKeys.hasUIStore).toBe(true); expect(storeKeys.hasRunTicks).toBe(true); }); test('debug bridge runTicks advances game time', async ({ page }) => { const dayBefore = await page.evaluate(() => (window as any).__TEST__.useGameStore.getState().day ); await page.evaluate(() => { (window as any).__TEST__.runTicks(1200); // ~1 day }); const dayAfter = await page.evaluate(() => (window as any).__TEST__.useGameStore.getState().day ); expect(dayAfter).toBeGreaterThanOrEqual(dayBefore); }); }); // ========================================================================= // SECTION 14 - All Tabs Navigation Stress Test // ========================================================================= test.describe('14 - All Tabs Navigation Stress Test', () => { test.beforeEach(async ({ page }) => { await startFreshGame(page); }); test('all navigations work in sequence without crash', async ({ page }) => { const errors: string[] = []; page.on('console', (msg) => { if (msg.type() === 'error') errors.push(msg.text()); }); await waitForMs(page, 500); const tabs = [ 'stats', 'disciplines', 'debug', 'attunements', 'achievements', 'prestige', 'equipment', 'golemancy', 'pacts', 'spire', 'crafting', ]; const visitedTabs: string[] = []; const crashTabs: string[] = []; for (const tabName of tabs) { const tab = page.getByRole('tab', { name: new RegExp(tabName, 'i') }); if (await tab.isVisible({ timeout: 2000 })) { const preErrors = [...errors]; await tab.click(); await waitForMs(page, 300); const newErrors = errors.filter(e => !preErrors.includes(e)); const reactErrors = newErrors.filter(e => e.includes('React') || e.includes('Minified') || e.includes('Error #') ); if (reactErrors.length > 0) { crashTabs.push(tabName); } visitedTabs.push(tabName); } } console.log(`HUNT: Visited tabs: ${visitedTabs.join(', ')}`); console.log(`HUNT: Tabs with React errors: ${crashTabs.join(', ')}`); // All tabs should be visitable without React errors expect(crashTabs, `Tabs with React errors: ${JSON.stringify(crashTabs)}`).toHaveLength(0); // Should have visited all 11 tabs expect(visitedTabs.length).toBe(11); }); }); });