const { test, expect } = require("@playwright/test"); async function gotoReadyGame(page, seed = 42) { await page.goto(`/play-classic?seed=${seed}`); const resumeOverlay = page.locator("#resume-overlay:not(.hidden)"); if (await resumeOverlay.isVisible().catch(() => false)) { await page.evaluate(() => localStorage.removeItem("fs_game_save")); await page.reload(); } await page.waitForFunction( () => typeof window.__FERROUS_DEBUG__ === "object" && window.__FERROUS_DEBUG__.seed() !== null ); } test("hud and core controls render for gameplay", async ({ page }) => { await gotoReadyGame(page, 42); await expect(page.locator("#hud-score")).toHaveText(/Score:\s*\d+/); await expect(page.locator("#hud-moves")).toHaveText(/Moves:\s*\d+/); await expect(page.locator("#hud-timer")).toHaveText(/\d+:\d{2}/); await expect(page.locator("#hud-stock")).toHaveText(/Stock:\s*\d+/); await expect(page.locator("#btn-undo")).toBeVisible(); await expect(page.locator("#btn-new")).toBeVisible(); await expect(page.locator("#chk-draw3")).toBeVisible(); await expect(page.locator("#btn-theme")).toBeVisible(); await expect(page.locator("#board")).toBeVisible(); await expect(page.locator("#card-area .slot[data-pile='stock']")).toBeVisible(); }); test("stock click + undo button behaves like player flow", async ({ page }) => { await gotoReadyGame(page, 42); const baselineHistoryLen = await page.evaluate( () => window.__FERROUS_DEBUG__.moveHistory().length ); const stockBox = await page.locator("#card-area .slot[data-pile='stock']").boundingBox(); expect(stockBox).not.toBeNull(); await page.mouse.click( stockBox.x + stockBox.width / 2, stockBox.y + stockBox.height / 2 ); await expect .poll(async () => await page.evaluate(() => window.__FERROUS_DEBUG__.moveHistory().length)) .toBe(baselineHistoryLen + 1); await page.locator("#btn-undo").click(); await expect .poll(async () => await page.evaluate(() => window.__FERROUS_DEBUG__.moveHistory().length)) .toBe(baselineHistoryLen); }); test("draw-mode toggle affects replay payload draw_mode", async ({ page }) => { await gotoReadyGame(page, 123); await page.locator("#chk-draw3").check(); await expect(page.locator("#chk-draw3")).toBeChecked(); const applyResult = await page.evaluate(() => window.__FERROUS_DEBUG__.applyMove({ kind: "stock_click" }) ); expect(applyResult?.ok).toBeTruthy(); await expect .poll(async () => await page.evaluate(() => window.__FERROUS_DEBUG__.replayPayload() !== null)) .toBe(true); const payload = await page.evaluate(() => window.__FERROUS_DEBUG__.replayPayload()); expect(payload.draw_mode).toBe("DrawThree"); expect(payload.schema_version).toBe(2); expect(Array.isArray(payload.moves)).toBeTruthy(); expect(payload.moves.length).toBeGreaterThan(0); }); test("autonomous play keeps invariants stable across seed batch", async ({ page }) => { test.setTimeout(120_000); const seeds = [0, 1, 2, 3, 4, 5, 7, 11, 13, 17, 23, 29, 31, 42, 77, 99]; for (const seed of seeds) { await gotoReadyGame(page, seed); const run = await page.evaluate(() => window.__FERROUS_DEBUG__.runAutoplay({ maxSteps: 220, maxVisitsPerState: 2, policy: "loop_aware", }) ); expect(run.ok, `seed ${seed} failed: ${JSON.stringify(run)}`).toBeTruthy(); } });