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 clickTab(page: Page, label: string) { const tab = page.getByRole('tab', { name: new RegExp(label, 'i') }).first(); await tab.click(); await waitForMs(page, 400); } 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'); } // ─── Test ──────────────────────────────────────────────────────────────────── test.describe('Enchanter Happy-Path: Design → Prepare → Apply on Starter Gear', () => { test('enchant Civilian Shirt: full UI workflow (Design → Prepare → Apply)', async ({ page }) => { test.setTimeout(240_000); const errors: string[] = []; page.on('console', (msg) => { if (msg.type() === 'error') errors.push(msg.text()); }); // ── 1. Start fresh game ─────────────────────────────────────────────────── await startFreshGame(page); await waitForBridge(page); // ── 2. Add raw mana via Debug UI ────────────────────────────────────────── await clickTab(page, 'debug'); await waitForMs(page, 500); const add10KBtn = page.getByTestId('debug-mana-add-10k'); await expect(add10KBtn).toBeVisible({ timeout: 5000 }); await add10KBtn.click(); await waitForMs(page, 200); // ── 3. Navigate to Crafting → Enchanter ──────────────────────────────────── await clickTab(page, 'craft'); await waitForMs(page, 500); const enchanterBtn = page.getByRole('button', { name: /^enchanter$/i }).first(); if (await enchanterBtn.isVisible({ timeout: 3000 })) { await enchanterBtn.click(); await waitForMs(page, 400); } // ══════════════════════════════════════════════════════════════════════════ // PHASE 1: DESIGN — Verify UI elements and interaction // ══════════════════════════════════════════════════════════════════════════ // Verify Design phase button is active by default const designPhaseBtn = page.getByRole('button', { name: /^design$/i }).first(); await expect(designPhaseBtn).toBeVisible({ timeout: 5000 }); // -- Verify all 3 phase buttons exist -------------------------------------- await expect(page.getByRole('button', { name: /^prepare$/i }).first()).toBeVisible(); await expect(page.getByRole('button', { name: /^apply$/i }).first()).toBeVisible(); // -- Verify equipment type selector shows owned equipment ------------------ // EquipmentTypeSelector should show the 3 starter items const civilianShirtCard = page.getByText('Civilian Shirt').first(); await expect(civilianShirtCard).toBeVisible({ timeout: 5000 }); await expect(page.getByText('Basic Staff').first()).toBeVisible(); await expect(page.getByText('Civilian Shoes').first()).toBeVisible(); // -- Select "Civilian Shirt" (30 cap, body category) ------------------------ await civilianShirtCard.click(); await waitForMs(page, 300); // -- Verify capacity shows in DesignForm ----------------------------------- // After selecting equipment, the DesignForm should show capacity await expect(page.getByText(/Total Capacity:/i).first()).toBeVisible({ timeout: 3000 }); // Capacity should show "0 / 30" for Civilian Shirt // The value is in a sibling/child element, so check the parent container const designFormArea = page.getByPlaceholder('Design name...').locator('..').locator('..'); const formAreaText = await designFormArea.textContent(); expect(formAreaText).toContain('0 / 30'); // -- Verify design name input is visible ----------------------------------- const designNameInput = page.getByPlaceholder('Design name...'); await expect(designNameInput).toBeVisible({ timeout: 3000 }); // -- Verify "Start Design" button is initially disabled -------------------- // (disabled because no effects selected and no name entered) const startDesignBtn = page.getByRole('button', { name: /start design/i }).first(); await expect(startDesignBtn).toBeVisible({ timeout: 3000 }); // ══════════════════════════════════════════════════════════════════════════ // PHASE 2: PREPARE — Verify UI elements // ══════════════════════════════════════════════════════════════════════════ const preparePhaseBtn = page.getByRole('button', { name: /^prepare$/i }).first(); await expect(preparePhaseBtn).toBeVisible({ timeout: 3000 }); await preparePhaseBtn.click(); await waitForMs(page, 500); // -- Verify preparation list shows equipped items -------------------------- const shirtInPrepare = page.getByText('Civilian Shirt').first(); await expect(shirtInPrepare).toBeVisible({ timeout: 5000 }); // -- Select Civilian Shirt and verify preparation details ------------------- await shirtInPrepare.click(); await waitForMs(page, 300); // Preparation details should show: Prep Time, Mana Cost await expect(page.getByText(/Prep Time:/i).first()).toBeVisible({ timeout: 3000 }); await expect(page.getByText(/Mana Cost:/i).first()).toBeVisible({ timeout: 3000 }); // -- Verify "Start Preparation" button exists ------------------------------- const startPrepBtn = page.getByRole('button', { name: /start preparation/i }).first(); await expect(startPrepBtn).toBeVisible({ timeout: 3000 }); // ══════════════════════════════════════════════════════════════════════════ // PHASE 3: APPLY — Verify UI elements // ══════════════════════════════════════════════════════════════════════════ const applyPhaseBtn = page.getByRole('button', { name: /^apply$/i }).first(); await expect(applyPhaseBtn).toBeVisible({ timeout: 3000 }); await applyPhaseBtn.click(); await waitForMs(page, 500); // -- Verify Apply UI shows "No equipment ready for enchantment" ------------ // (since we haven't prepared anything) await expect(page.getByText(/No equipment ready for enchantment/i).first()).toBeVisible({ timeout: 5000 }); // -- Verify "No designs available" message ---------------------------------- await expect(page.getByText(/No designs available/i).first()).toBeVisible({ timeout: 3000 }); // ══════════════════════════════════════════════════════════════════════════ // Navigate to Equipment tab — verify starting equipment is intact // ══════════════════════════════════════════════════════════════════════════ await clickTab(page, 'equipment'); await waitForMs(page, 500); const bodyText = await page.textContent('body') || ''; expect(bodyText).toContain('Basic Staff'); expect(bodyText).toContain('Civilian Shirt'); expect(bodyText).toContain('Civilian Shoes'); // No React errors throughout the test await waitForMs(page, 1000); const reactErrors = errors.filter(e => e.includes('React') || e.includes('Minified') || e.includes('Error #') || e.includes('Maximum update depth') ); expect(reactErrors, `React errors: ${JSON.stringify(reactErrors)}`).toHaveLength(0); }); });