test(e2e): add Playwright spec for /play Bevy canvas route
play_canvas.spec.js covers the window.__FERROUS_DEBUG__ bridge on the /play route (five tests): bridge availability + seed param, draw3 URL param, applyLegalMove/undo round-trip, failureReport schema, and autonomous autoplay invariant batch across 7 seeds. All tests drive exclusively through the debug bridge — no DOM selectors, because the Bevy canvas is a single <canvas> element with no HTML controls. Also update SESSION_HANDOFF.md to reflect post-v0.35.1 work (10 commits since 2026-05-18 handoff), new e2e architecture notes, and HiDPI fix doc. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,81 @@
|
||||
const { test, expect } = require("@playwright/test");
|
||||
|
||||
async function gotoReadyPlay(page, seed = 42, draw3 = false) {
|
||||
const suffix = draw3 ? "&draw3=" : "";
|
||||
await page.goto(`/play?seed=${seed}${suffix}`);
|
||||
await page.waitForFunction(
|
||||
() =>
|
||||
typeof window.__FERROUS_DEBUG__ === "object" &&
|
||||
window.__FERROUS_DEBUG__.seed() !== null,
|
||||
null,
|
||||
{ timeout: 30_000 }
|
||||
);
|
||||
}
|
||||
|
||||
test("play loads and exposes debug bridge", async ({ page }) => {
|
||||
await gotoReadyPlay(page, 42);
|
||||
|
||||
const seed = await page.evaluate(() => window.__FERROUS_DEBUG__.seed());
|
||||
expect(seed).toBe(42);
|
||||
|
||||
const legalMoves = await page.evaluate(() => window.__FERROUS_DEBUG__.legalMoves());
|
||||
expect(Array.isArray(legalMoves)).toBeTruthy();
|
||||
expect(legalMoves.length).toBeGreaterThan(0);
|
||||
});
|
||||
|
||||
test("play respects draw3 URL param", async ({ page }) => {
|
||||
await gotoReadyPlay(page, 77, true);
|
||||
|
||||
const snap = await page.evaluate(() => window.__FERROUS_DEBUG__.snapshot());
|
||||
expect(snap).not.toBeNull();
|
||||
expect(snap.draw_mode).toBe("DrawThree");
|
||||
});
|
||||
|
||||
test("play debug bridge apply and undo work", async ({ page }) => {
|
||||
await gotoReadyPlay(page, 42);
|
||||
|
||||
const baseline = await page.evaluate(() => window.__FERROUS_DEBUG__.moveHistory().length);
|
||||
|
||||
const applied = await page.evaluate(() => window.__FERROUS_DEBUG__.applyLegalMove(0));
|
||||
expect(applied?.ok).toBeTruthy();
|
||||
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => window.__FERROUS_DEBUG__.moveHistory().length))
|
||||
.toBe(baseline + 1);
|
||||
|
||||
const undone = await page.evaluate(() => window.__FERROUS_DEBUG__.undo());
|
||||
expect(undone?.ok).toBeTruthy();
|
||||
|
||||
await expect
|
||||
.poll(() => page.evaluate(() => window.__FERROUS_DEBUG__.moveHistory().length))
|
||||
.toBe(baseline);
|
||||
});
|
||||
|
||||
test("play failure report contains replay diagnostics", async ({ page }) => {
|
||||
await gotoReadyPlay(page, 42);
|
||||
|
||||
const report = await page.evaluate(() => window.__FERROUS_DEBUG__.failureReport());
|
||||
expect(report).not.toBeNull();
|
||||
expect(typeof report.seed).toBe("number");
|
||||
expect(Array.isArray(report.moveHistory)).toBeTruthy();
|
||||
expect(Array.isArray(report.legalMoves)).toBeTruthy();
|
||||
expect(report.currentState).toBeTruthy();
|
||||
expect(report.invariants).toBeTruthy();
|
||||
});
|
||||
|
||||
test("play autonomous autoplay keeps invariants stable across seed batch", async ({ page }) => {
|
||||
test.setTimeout(120_000);
|
||||
const seeds = [0, 1, 2, 5, 13, 42, 77];
|
||||
|
||||
for (const seed of seeds) {
|
||||
await gotoReadyPlay(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();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user