test: deterministic e2e waits, remove retries - #1077
Conversation
✅ Deploy Preview for blockstack ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
There was a problem hiding this comment.
Pull request overview
Improves Playwright e2e determinism by replacing fixed delays and retry loops with state-driven waits (Redux state, DOM conditions, and a buffered in-page event bus), and disables Playwright-level retries so flakiness is surfaced as actionable failures.
Changes:
- Add a buffered
window.__e2e_eventsbus in visual-regression builds and emit a per-framespriteOptionRenderedevent from the main loop. - Replace many
waitForTimeout+ custom retry helpers with deterministic waits (waitForFunction,waitForAnimationFrames, dialog/spinner gating, and API-driven room changes). - Set Playwright
retries: 0and switch traces toretain-on-failureto aid debugging without reruns.
Reviewed changes
Copilot reviewed 33 out of 34 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| src/game/mainLoop/MainLoop.ts | Emits the new e2e render event in visual-regression mode. |
| src/game/mainLoop/e2eEventBus.ts | Introduces a buffered event bus for race-free e2e waiting. |
| playwright.config.ts | Disables retries and retains traces on failures. |
| e2eAugmentations.d.ts | Adds Window.__e2e_events typing for tests and runtime. |
| e2e/upscaleResponds.spec.ts | Replaces sleeps with waits on Redux-derived upscale changes. |
| e2e/testUtils/setSpriteOption.ts | Uses cursor + event wait for sprite option render; removes retries. |
| e2e/testUtils/screenshots.ts | Removes retries and waits for dialog + spinner to settle. |
| e2e/testUtils/menuNavigation.ts | Removes retry helper usage; adds state-based dialog transition waits. |
| e2e/testUtils/infrastructure.ts | Removes retryWithRecovery helper. |
| e2e/testUtils/gameStateQueries.ts | Adds cursor/event utilities and replaces render-event waits with level-triggered waits. |
| e2e/testUtils/gameInteractions.ts | Makes life/death and character switching waits deterministic. |
| e2e/testUtils/captureSaveFixtures.ts | Replaces repeated clicking with loading-banner gating for crowns. |
| e2e/testUtils/bootPlaytestCampaign.ts | Freezes game speed before first tick via init-script store interception. |
| e2e/settingsReflectInGame.spec.ts | Removes retries; gates freezing and HUD rebuild with deterministic waits. |
| e2e/scrollAndKeyRebind.spec.ts | Waits for playable + store mutation instead of fixed delays. |
| e2e/savesAndGameOver.spec.ts | Waits for save persistence via store state. |
| e2e/saveCompatLoad.spec.ts | Waits for a room to become current + frames to tick before asserting no error. |
| e2e/roomSnapshots.spec.ts | Switches room navigation to game API and removes screenshot retry/bounce logic. |
| e2e/reincarnationStack.spec.ts | Uses store/lives waits instead of timeouts; improves dialog pop loop. |
| e2e/quitGameFlow.spec.ts | Waits for reincarnation point + room changes via store/game state. |
| e2e/playtestNoSave.spec.ts | Replaces sleeps with character-change state waits. |
| e2e/persistOnReload.spec.ts | Waits for playable/room changes deterministically across reloads. |
| e2e/offlineCampaignCache.spec.ts | Uses level-triggered “current room” wait instead of render event. |
| e2e/menuSnapshotsMainMenu.spec.ts | Removes navigation retries and relies on deterministic dialog wait. |
| e2e/menuSnapshotsInGame.spec.ts | Removes retries; relies on dialog waits and actionability waits. |
| e2e/menuSnapshotsDialogs.spec.ts | Removes retries; relies on deterministic main-menu wait. |
| e2e/mainMenuClosability.spec.ts | Replaces sleeps with frame waits to validate menu open/close behaviour. |
| e2e/lutSnapshot.spec.ts | Removes fixed delay; relies on screenshot assertion stability. |
| e2e/lifeAndDeath.spec.ts | Replaces sleeps with grounded/room-change/reincarnation-point waits. |
| e2e/gameRendersAfterRendererReset.spec.ts | Removes sleeps; relies on screenshot assertion stability + frame waits. |
| e2e/gameComplete.spec.ts | Uses in-page predicates with waitForFunction for key-hold gating. |
| e2e/doughnuttedMonsters.spec.ts | Replaces sleeps with animation-frame waits for rerenders. |
| e2e/crownsDialogs.spec.ts | Waits for current playable instead of fixed startup delays. |
| e2e/cameraRotationSweep.spec.ts | Navigates rooms via game API + level-triggered waits; documents baseline-critical delays. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| await page.waitForSelector(`dialog[data-dialog-id="${dialogId}"]`, { | ||
| timeout: 15_000 * osSlowness, | ||
| }); | ||
| await page | ||
| .getByRole("status") | ||
| .waitFor({ state: "detached", timeout: 15_000 * osSlowness }); |
| const deadline = performance.now() + timeoutMs; | ||
| const attach = () => { | ||
| const bus = window.__e2e_events; | ||
| if (bus === undefined) { | ||
| if (performance.now() > deadline) { | ||
| reject(new Error("e2e event bus never appeared on window")); | ||
| return; | ||
| } | ||
| requestAnimationFrame(attach); | ||
| return; | ||
| } | ||
| bus | ||
| .waitFor("spriteOptionRendered", { | ||
| afterId, | ||
| timeoutMs, | ||
| match: (detail) => | ||
| detail.spriteOption.name === expected.name && | ||
| detail.spriteOption.uncolourised === expected.uncolourised, | ||
| }) | ||
| .then(() => resolve(), reject); | ||
| }; |
| const poll = () => { | ||
| const currentRoomId = window._e2e_gamePageGameAi?.currentRoom?.id; | ||
| if (currentRoomId !== undefined) { | ||
| resolve(currentRoomId); | ||
| return; | ||
| } | ||
| if (performance.now() > deadline) { | ||
| reject(new Error("no room became current before the timeout")); | ||
| return; | ||
| } | ||
| requestAnimationFrame(poll); | ||
| }; | ||
| poll(); |
📡 real network cost to readyTrue wire bytes from cold cache to
runs varied by up to 23 B (h2 header-compression noise, within the 512 B tolerance) - the minimum is reported vs main · v27.0.0-3-g7488677d9📋 to menu: 2 file(s) changed, 55 identical
🎮 to game: 1 file(s) changed, 76 identical
🗺️ open map: no per-file changes (4 files identical) 🛠️ editor: 3 file(s) changed, 26 identical
vs production · v27.0.0📋 to menu: 2 file(s) changed, 55 identical
🎮 to game: 1 file(s) changed, 76 identical
🗺️ open map: no per-file changes (4 files identical) 🛠️ editor: 3 file(s) changed, 26 identical
vs v26.0.0📋 to menu: 39 file(s) changed, 22 identical
🎮 to game: 8 file(s) changed, 71 identical
🗺️ open map: 3 file(s) changed, 2 identical
🛠️ editor: 14 file(s) changed, 18 identical
💾 total built size on diskEvery file in the built site, loaded or not, compressed as served.
vs main · v27.0.0-3-g7488677d96 file(s) changed, 294 identical
vs production · v27.0.06 file(s) changed, 294 identical
vs v26.0.083 file(s) changed, 223 identical
📋 per-request breakdown (every url, size and timing) is in the run logs |
06556b7 to
ef9a15f
Compare
ef9a15f to
8969521
Compare
Generated by Claude Code