feat(e2e): add window.__FERROUS_DEBUG__ bridge to /play for automation
Build and Deploy / build-and-push (push) Successful in 4m42s
Web E2E / web-e2e (push) Successful in 4m10s

play.html now loads solitaire_wasm.js alongside the Bevy canvas and
exposes the same window.__FERROUS_DEBUG__ object as /play-classic.
The bridge runs an independent SolitaireGame (WASM logic layer) seeded
from ?seed= / ?draw3= URL params; Bevy renders the visual game in
parallel without coupling.

Methods exposed: seed, state, legalMoves, moveHistory, snapshot,
applyLegalMove, applyMove, draw, undo, serialize, fromSaved, newGame,
failureReport, replayPayload, runAutoplay — matching the /play-classic
contract so the shared Playwright harness targets either route without
modification.

cycle_metrics.js: add --route play-classic|play flag (default
play-classic). Routes to /${route}?seed=N. The resume-overlay clear
step is skipped for /play since the Bevy build uses localStorage-backed
WasmStorage, not a #resume-overlay element.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
funman300
2026-06-02 13:41:07 -07:00
parent 8b262afcd2
commit 2cf728210e
2 changed files with 208 additions and 9 deletions
+13 -8
View File
@@ -103,6 +103,9 @@ async function main() {
const policyArg = readArg("--policy", "loop_aware");
const policy = policyArg === "baseline" ? "baseline" : "loop_aware";
const outPath = readArg("--out", "/tmp/playwright-cycle-metrics.json");
// --route play-classic (default) or --route play
const routeArg = readArg("--route", "play-classic");
const route = routeArg === "play" ? "play" : "play-classic";
const maxCycleRateAll = parseOptionalNumber("--max-cycle-rate-all");
const maxCycleRateDraw1 = parseOptionalNumber("--max-cycle-rate-draw1");
const maxCycleRateDraw3 = parseOptionalNumber("--max-cycle-rate-draw3");
@@ -155,17 +158,19 @@ async function main() {
}
});
await page.goto(`${baseUrl}/play-classic?seed=${seed}${suffix}`, {
await page.goto(`${baseUrl}/${route}?seed=${seed}${suffix}`, {
waitUntil: "domcontentloaded",
});
const resumeVisible = await page
.locator("#resume-overlay:not(.hidden)")
.isVisible()
.catch(() => false);
if (resumeVisible) {
await page.evaluate(() => localStorage.removeItem("fs_game_save"));
await page.reload({ waitUntil: "domcontentloaded" });
if (route === "play-classic") {
const resumeVisible = await page
.locator("#resume-overlay:not(.hidden)")
.isVisible()
.catch(() => false);
if (resumeVisible) {
await page.evaluate(() => localStorage.removeItem("fs_game_save"));
await page.reload({ waitUntil: "domcontentloaded" });
}
}
await page.waitForFunction(