Skip to content

test: deterministic e2e waits, remove retries - #1077

Open
jimhigson wants to merge 1 commit into
mainfrom
e2e-remove-retries
Open

test: deterministic e2e waits, remove retries#1077
jimhigson wants to merge 1 commit into
mainfrom
e2e-remove-retries

Conversation

@jimhigson

Copy link
Copy Markdown
Owner

Generated by Claude Code

Copilot AI lite review requested due to automatic review settings August 13, 2026 02:56
@netlify

netlify Bot commented Aug 13, 2026

Copy link
Copy Markdown

Deploy Preview for blockstack ready!

Name Link
🔨 Latest commit 8969521
🔍 Latest deploy log https://app.netlify.com/projects/blockstack/deploys/6a973e8e41913e000805b659
😎 Deploy Preview https://deploy-preview-1077--blockstack.netlify.app
📱 Preview on mobile
Toggle QR Code...

QR Code

Use your smartphone camera to open QR code link.

To edit notification comments on pull requests, go to your Netlify project configuration.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_events bus in visual-regression builds and emit a per-frame spriteOptionRendered event 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: 0 and switch traces to retain-on-failure to 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.

Comment on lines +200 to +205
await page.waitForSelector(`dialog[data-dialog-id="${dialogId}"]`, {
timeout: 15_000 * osSlowness,
});
await page
.getByRole("status")
.waitFor({ state: "detached", timeout: 15_000 * osSlowness });
Comment thread e2e/testUtils/gameStateQueries.ts Outdated
Comment on lines 169 to 189
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);
};
Comment on lines +212 to +224
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();
@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

📡 real network cost to ready

True wire bytes from cold cache to performance.mark, network settled.

PR vs main · v27.0.0-3-g7488677d9 vs production · v27.0.0 vs v26.0.0
📋 to menu 285.8 kB +174 B
📈 +0.1%
was 285.6 kB
+174 B
📈 +0.1%
was 285.6 kB
-10.8 kB
📉 -3.6%
was 296.6 kB
🎮 to game 663.0 kB +103 B
📈 +0.0%
was 662.9 kB
+126 B
📈 +0.0%
was 662.8 kB
+6.8 kB
📈 +1.0%
was 656.1 kB
Σ 948.7 kB +277 B
📈 +0.0%
was 948.5 kB
+300 B
📈 +0.0%
was 948.4 kB
-3.9 kB
📉 -0.4%
was 952.7 kB
🗺️ open map 10.0 kB 🤷 ±0 B
±0.0%
was 10.0 kB
🤷 ±0 B
±0.0%
was 10.0 kB
-282 B
📉 -2.7%
was 10.3 kB
total (game) 958.8 kB +291 B
📈 +0.0%
was 958.5 kB
+314 B
📈 +0.0%
was 958.4 kB
-4.2 kB
📉 -0.4%
was 963.0 kB
🔄 cold restart — editor
🛠️ editor 1.13 MB +190 B
📈 +0.0%
was 1.13 MB
+236 B
📈 +0.0%
was 1.13 MB
-2.0 kB
📉 -0.2%
was 1.14 MB

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
file PR main delta
/assets/appClock.js 🆕 1.0 kB +1.0 kB
/assets/store.js 140.6 kB 141.5 kB -943 B (-0.7%)
🎮 to game: 1 file(s) changed, 76 identical
file PR main delta
/assets/lutForSpriteOption.js 617 B 531 B ⚠️ +86 B (+16.2%)

🗺️ open map: no per-file changes (4 files identical)

🛠️ editor: 3 file(s) changed, 26 identical
file PR main delta
/editor/assets/index.js 205.7 kB 205.5 kB +232 B (+0.1%)
/editor/assets/RoomEditingArea.js 11.8 kB 11.8 kB +83 B (+0.7%)
/editor/assets/editor.api2.js 693.2 kB 693.2 kB -79 B (-0.0%)

vs production · v27.0.0

📋 to menu: 2 file(s) changed, 55 identical
file PR production delta
/assets/appClock.js 🆕 1.0 kB +1.0 kB
/assets/store.js 140.6 kB 141.5 kB -943 B (-0.7%)
🎮 to game: 1 file(s) changed, 76 identical
file PR production delta
/assets/lutForSpriteOption.js 617 B 531 B ⚠️ +86 B (+16.2%)

🗺️ open map: no per-file changes (4 files identical)

🛠️ editor: 3 file(s) changed, 26 identical
file PR production delta
/editor/assets/index.js 205.7 kB 205.5 kB +232 B (+0.1%)
/editor/assets/editor.api2.js 693.2 kB 693.2 kB -79 B (-0.0%)
/editor/assets/RoomEditingArea.js 11.8 kB 11.8 kB +60 B (+0.5%)

vs v26.0.0

📋 to menu: 39 file(s) changed, 22 identical
file PR v26.0.0 delta
/assets/App.js 12.1 kB 19.9 kB 🎉 -7.9 kB (-39.5%)
/assets/index.css 9.1 kB 15.2 kB 🎉 -6.1 kB (-39.9%)
/assets/openExternalLink.js 1.8 kB 393 B 🆘 +1.4 kB (+361.3%)
/assets/Switch.js 🗑️ 1.4 kB -1.4 kB
/assets/sprites.webp 61.1 kB 62.5 kB -1.3 kB (-2.1%)
/assets/Button.js 🗑️ 1.1 kB -1.1 kB
/assets/CurrentKeyAssignments.js 🆕 1.1 kB +1.1 kB
/assets/appClock.js 🆕 1.0 kB +1.0 kB
/assets/blockstack-head-over-heels.woff2 4.1 kB 4.9 kB 👏 -900 B (-17.8%)
/assets/MainMenuHeading.js 🆕 845 B +845 B
/assets/Tip.js 🗑️ 725 B -725 B
/assets/dialogClasses.js 🆕 549 B +549 B
/assets/index.js 16.2 kB 15.8 kB +392 B (+2.4%)
/assets/Spinner.js 🆕 391 B +391 B
/assets/Border.js 🆕 388 B +388 B
/assets/useFacingTowardsPointer.js 🆕 339 B +339 B
/assets/PlayAudio.js 🆕 311 B +311 B
/assets/ColourCycleText.js 🆕 307 B +307 B
/assets/IdleTracker.js 🆕 298 B +298 B
/assets/cn.js 🆕 272 B +272 B
/assets/package.js 🆕 248 B +248 B
/assets/MenuItems.js 1.4 kB 1.6 kB 👏 -242 B (-14.4%)
/assets/GameApiContext.js 🆕 238 B +238 B
/assets/playableTailwindSpriteClassname.js 705 B 908 B 🎉 -203 B (-22.4%)
/assets/store.js 140.6 kB 140.8 kB -200 B (-0.1%)
/assets/mitt.js 🆕 193 B +193 B
/assets/useDispatchActionCallback.js 158 B 345 B 🏆 -187 B (-54.2%)
/assets/gameInfo.js 🆕 180 B +180 B
/assets/DialogPortal.js 🆕 151 B +151 B
/assets/cycleSpritesOptionThunk.js 🆕 137 B +137 B
/assets/MenuItemSeparator.js 🗑️ 122 B -122 B
/assets/needRefreshDialogColourClasses.js 🆕 118 B +118 B
/assets/mainMenuDialogColourClasses.js 🆕 116 B +116 B
/assets/markdownDialogColourClasses.js 🆕 112 B +112 B
/assets/sureWantEditorDialogColourClasses.js 🆕 108 B +108 B
/assets/quitGameConfirmDialogColourClasses.js 🆕 106 B +106 B
/assets/offerReincarnationDialogColourClasses.js 🆕 100 B +100 B
/assets/mapDialogColourClasses.js 🆕 94 B +94 B
/assets/proclaimEmperorDialogColourClasses.js 🆕 72 B +72 B
🎮 to game: 8 file(s) changed, 71 identical
file PR v26.0.0 delta
/assets/gameColours.js 🆕 28.5 kB +28.5 kB
/assets/RoomRenderer.js 🗑️ 24.3 kB -24.3 kB
/assets/voronoiLut.js 🗑️ 1.0 kB -1.0 kB
/assets/cheatRoomIdFromUrlHash.js 21.1 kB 20.1 kB 🔍 +1022 B (+5.0%)
/assets/palette.js 🆕 1018 B +1018 B
/assets/swopPlayablesIfInput.js 2.1 kB 1.1 kB 🆘 +997 B (+85.4%)
/assets/gameMain.js 24.8 kB 24.3 kB +475 B (+1.9%)
/assets/lutForSpriteOption.js 617 B 508 B 🚨 +109 B (+21.5%)
🗺️ open map: 3 file(s) changed, 2 identical
file PR v26.0.0 delta
/assets/SanitiseForClassName.js 🗑️ 905 B -905 B
/assets/Tip.js 🆕 523 B +523 B
/assets/MapDialog.js 9.0 kB 8.9 kB +101 B (+1.1%)
🛠️ editor: 14 file(s) changed, 18 identical
file PR v26.0.0 delta
/editor/assets/index.css 11.0 kB 18.5 kB 🎉 -7.5 kB (-40.5%)
/editor/assets/index.js 205.7 kB 198.7 kB +7.1 kB (+3.6%)
/editor/assets/levelEditorSelectorHooks.js 🗑️ 4.8 kB -4.8 kB
/editor/assets/getMovableVector.js 🆕 4.6 kB +4.6 kB
/editor/assets/sprites.webp 61.2 kB 62.5 kB -1.3 kB (-2.1%)
/editor/assets/useTip.js 9.6 kB 8.3 kB ⚠️ +1.3 kB (+15.5%)
/editor/assets/blockstack-head-over-heels.woff2 4.1 kB 4.9 kB 👏 -900 B (-17.8%)
/editor/assets/ajv.js 29.6 kB 30.2 kB -661 B (-2.1%)
/editor/assets/RoomEditingArea.js 11.8 kB 11.5 kB +322 B (+2.7%)
/editor/assets/getMovableVector.css 🆕 247 B +247 B
/editor/assets/levelEditorSelectorHooks.css 🗑️ 247 B -247 B
/editor/assets/editor.api2.js 693.2 kB 693.4 kB -229 B (-0.0%)
/editor/assets/LevelEditor.js 43.0 kB 42.9 kB +91 B (+0.2%)
/editor/assets/twClass.js 🗑️ 62 B -62 B

💾 total built size on disk

Every file in the built site, loaded or not, compressed as served.

PR vs main · v27.0.0-3-g7488677d9 vs production · v27.0.0 vs v26.0.0
on disk 3.59 MB +428 B
📈 +0.0%
was 3.59 MB
+428 B
📈 +0.0%
was 3.59 MB
+77.8 kB
📈 +2.2%
was 3.52 MB

vs main · v27.0.0-3-g7488677d9

6 file(s) changed, 294 identical
file PR main delta
assets/appClock.js 🆕 1015 B +1015 B
assets/store.js 140.5 kB 141.4 kB -943 B (-0.7%)
editor/assets/index.js 205.6 kB 205.4 kB +232 B (+0.1%)
assets/lutForSpriteOption.js 585 B 499 B ⚠️ +86 B (+17.2%)
editor/assets/RoomEditingArea.js 11.8 kB 11.7 kB +83 B (+0.7%)
editor/assets/editor.api2.js 692.8 kB 692.8 kB -79 B (-0.0%)

vs production · v27.0.0

6 file(s) changed, 294 identical
file PR production delta
assets/appClock.js 🆕 1015 B +1015 B
assets/store.js 140.5 kB 141.4 kB -943 B (-0.7%)
editor/assets/index.js 205.6 kB 205.4 kB +232 B (+0.1%)
assets/lutForSpriteOption.js 585 B 499 B ⚠️ +86 B (+17.2%)
editor/assets/RoomEditingArea.js 11.8 kB 11.7 kB +83 B (+0.7%)
editor/assets/editor.api2.js 692.8 kB 692.8 kB -79 B (-0.0%)

vs v26.0.0

83 file(s) changed, 223 identical
file PR v26.0.0 delta
assets/spritesDebug.webp 🆕 32.6 kB +32.6 kB
editor/assets/spritesDebug.webp 🆕 32.6 kB +32.6 kB
assets/gameColours.js 🆕 28.5 kB +28.5 kB
assets/RoomRenderer.js 🗑️ 24.2 kB -24.2 kB
assets/App.js 12.0 kB 19.9 kB 🎉 -7.9 kB (-39.5%)
editor/assets/index.css 11.0 kB 18.5 kB 🎉 -7.5 kB (-40.5%)
editor/assets/index.js 205.6 kB 198.5 kB +7.1 kB (+3.6%)
assets/index.css 9.1 kB 15.1 kB 🎉 -6.1 kB (-40.0%)
editor/assets/levelEditorSelectorHooks.js 🗑️ 4.8 kB -4.8 kB
editor/assets/getMovableVector.js 🆕 4.6 kB +4.6 kB
assets/ControlOptionsDialog.js 🆕 3.3 kB +3.3 kB
assets/openExternalLink.js 1.7 kB 361 B 🆘 +1.4 kB (+393.4%)
assets/OptionsDialog.js 🆕 1.4 kB +1.4 kB
assets/sprites.webp 61.1 kB 62.4 kB -1.3 kB (-2.1%)
editor/assets/sprites.webp 61.1 kB 62.4 kB -1.3 kB (-2.1%)
editor/assets/useTip.js 9.5 kB 8.3 kB ⚠️ +1.3 kB (+15.6%)
assets/FrameTimingStats.js 🆕 1.2 kB +1.2 kB
assets/CommunityGamesDialog.js 🆕 1.2 kB +1.2 kB
assets/QuitGameConfirmDialog.js 🆕 1.1 kB +1.1 kB
assets/CurrentKeyAssignments.js 🆕 1.1 kB +1.1 kB
assets/ScoreDialog.js 🆕 1.0 kB +1.0 kB
assets/enableDetailedFrameTimingLog.js 🆕 1.0 kB +1.0 kB
assets/DisplayOptionsDialog.js 🆕 1.0 kB +1.0 kB
assets/cheatRoomIdFromUrlHash.js 21.1 kB 20.1 kB 🔍 +1022 B (+5.0%)
assets/appClock.js 🆕 1015 B +1015 B
assets/swopPlayablesIfInput.js 2.1 kB 1.1 kB 🆘 +997 B (+87.8%)
assets/voronoiLut.js 🗑️ 993 B -993 B
assets/palette.js 🆕 986 B +986 B
assets/blockstack-head-over-heels.woff2 4.0 kB 4.9 kB 👏 -900 B (-18.0%)
editor/assets/blockstack-head-over-heels.woff2 4.0 kB 4.9 kB 👏 -900 B (-18.0%)
assets/SanitiseForClassName.js 🗑️ 873 B -873 B
assets/MainMenuHeading.js 🆕 813 B +813 B
assets/useKeyboardShortcut.js 🆕 745 B +745 B
assets/OfferReincarnationDialog.js 🆕 697 B +697 B
assets/SureWantEditorDialog.js 🆕 679 B +679 B
assets/ProclaimEmperorDialog.js 🆕 677 B +677 B
editor/assets/ajv.js 29.6 kB 30.2 kB -661 B (-2.1%)
assets/AboutDialog.js 🆕 629 B +629 B
assets/SoundDialog.js 🆕 615 B +615 B
assets/Button.js 505 B 1.1 kB 🏆 -612 B (-54.8%)
sw.js 3.1 kB 2.6 kB 🚨 +583 B (+22.1%)
assets/InstallDialog.js 🆕 579 B +579 B
assets/InputPresetDialog.js 🆕 565 B +565 B
assets/dialogClasses.js 🆕 517 B +517 B
assets/EmulatedResolutionDialog.js 🆕 488 B +488 B
assets/gameMain.js 24.7 kB 24.3 kB +475 B (+1.9%)
assets/index.js 16.2 kB 15.8 kB +383 B (+2.4%)
assets/Spinner.js 🆕 359 B +359 B
assets/Border.js 🆕 356 B +356 B
editor/assets/RoomEditingArea.js 11.8 kB 11.5 kB +345 B (+2.9%)
assets/useFacingTowardsPointer.js 🆕 307 B +307 B
assets/PlayAudio.js 🆕 279 B +279 B
assets/ColourCycleText.js 🆕 275 B +275 B
assets/IdleTracker.js 🆕 266 B +266 B
assets/Cheats.js 6.2 kB 6.0 kB +258 B (+4.2%)
assets/MenuItems.js 1.4 kB 1.6 kB 👏 -242 B (-14.7%)
assets/cn.js 🆕 240 B +240 B
editor/assets/editor.api2.js 692.8 kB 693.0 kB -229 B (-0.0%)
assets/package.js 🆕 216 B +216 B
assets/GameApiContext.js 🆕 206 B +206 B
assets/playableTailwindSpriteClassname.js 673 B 876 B 🎉 -203 B (-23.2%)
assets/store.js 140.5 kB 140.7 kB -200 B (-0.1%)
editor/assets/getMovableVector.css 🆕 192 B +192 B
editor/assets/levelEditorSelectorHooks.css 🗑️ 192 B -192 B
assets/useDispatchActionCallback.js 126 B 313 B 🏆 -187 B (-59.7%)
assets/Tip.js 491 B 670 B 🎉 -179 B (-26.7%)
assets/mitt.js 🆕 161 B +161 B
assets/gameInfo.js 🆕 148 B +148 B
assets/Switch.js 1.2 kB 1.4 kB 👏 -132 B (-9.5%)
assets/DialogPortal.js 🆕 119 B +119 B
assets/lutForSpriteOption.js 585 B 476 B 🚨 +109 B (+22.9%)
assets/cycleSpritesOptionThunk.js 🆕 105 B +105 B
assets/MapDialog.js 8.9 kB 8.8 kB +101 B (+1.1%)
editor/assets/LevelEditor.js 42.9 kB 42.8 kB +91 B (+0.2%)
assets/needRefreshDialogColourClasses.js 🆕 86 B +86 B
assets/mainMenuDialogColourClasses.js 🆕 84 B +84 B
assets/markdownDialogColourClasses.js 🆕 80 B +80 B
assets/SpritesPage.js 4.5 kB 4.6 kB -77 B (-1.7%)
assets/sureWantEditorDialogColourClasses.js 🆕 76 B +76 B
assets/quitGameConfirmDialogColourClasses.js 🆕 74 B +74 B
assets/offerReincarnationDialogColourClasses.js 🆕 68 B +68 B
assets/mapDialogColourClasses.js 🆕 62 B +62 B
assets/MarkdownDialog.js 678 B 625 B 🔍 +53 B (+8.5%)

📋 per-request breakdown (every url, size and timing) is in the run logs
measured by true-site-size

@jimhigson
jimhigson force-pushed the e2e-remove-retries branch 18 times, most recently from 06556b7 to ef9a15f Compare August 18, 2026 08:00
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants