From 8db0efff2989de38bba77673073f636d11e46219 Mon Sep 17 00:00:00 2001 From: retoor Date: Sun, 16 Aug 2026 02:02:34 +0200 Subject: [PATCH] Fix Devii open-trigger race and close two flaky e2e waits DeviiTerminal bound [data-devii-open] click listeners only after the async /devii/session fetch resolved, silently dropping early clicks. Switch to a single delegated document listener bound in the constructor, matching the ModalManager/dp-lightbox pattern. The steal-confirm and comment-vote e2e tests asserted DOM state right after a click with no wait for the triggering POST to land, racing the server under CI load. Wrap those clicks in page.expect_response. Co-Authored-By: Claude Sonnet 5 --- devplacepy/static/js/DeviiTerminal.js | 18 +++++++++++------- tests/e2e/game/farm.py | 6 ++++-- tests/e2e/post.py | 5 ++++- 3 files changed, 19 insertions(+), 10 deletions(-) diff --git a/devplacepy/static/js/DeviiTerminal.js b/devplacepy/static/js/DeviiTerminal.js index e757f782..2a53930e 100644 --- a/devplacepy/static/js/DeviiTerminal.js +++ b/devplacepy/static/js/DeviiTerminal.js @@ -10,6 +10,7 @@ export class DeviiTerminal { this.avatar = null; this.bindEscapeTrigger(); this.bindDoubleClickTrigger(); + this.bindOpenTrigger(); this.ready = this.init(); } @@ -29,16 +30,19 @@ export class DeviiTerminal { this.element.setAttribute("avatar", "devii"); document.body.appendChild(this.element); - this.bindTriggers(); + this.maybeAutoOpen(); } - bindTriggers() { - document.querySelectorAll("[data-devii-open]").forEach((trigger) => { - trigger.addEventListener("click", (event) => { - event.preventDefault(); - this.open(trigger.dataset.deviiPrompt || ""); - }); + bindOpenTrigger() { + document.addEventListener("click", (event) => { + const trigger = event.target.closest("[data-devii-open]"); + if (!trigger) return; + event.preventDefault(); + this.open(trigger.dataset.deviiPrompt || ""); }); + } + + maybeAutoOpen() { const stored = this.element && this.element.hasStoredState && this.element.hasStoredState(); if (document.querySelector("[data-devii-autoopen]") && !stored) { this.open(); diff --git a/tests/e2e/game/farm.py b/tests/e2e/game/farm.py index 2583fc66..3dae899c 100644 --- a/tests/e2e/game/farm.py +++ b/tests/e2e/game/farm.py @@ -128,7 +128,8 @@ def test_steal_takes_build_and_pays_thief(bob): steal.click() confirm_btn = page.locator(".dialog-confirm") confirm_btn.wait_for(state="visible") - confirm_btn.click() + with page.expect_response(lambda r: "/steal" in r.url and r.request.method == "POST"): + confirm_btn.click() expect(page.locator("form[data-game-action='steal']")).to_have_count(0) page.wait_for_timeout(400) assert _farm_coins("bob_test") == 18 @@ -165,7 +166,8 @@ def test_victim_notification_names_the_raider_and_the_amount(bob): steal.click() confirm_btn = page.locator(".dialog-confirm") confirm_btn.wait_for(state="visible") - confirm_btn.click() + with page.expect_response(lambda r: "/steal" in r.url and r.request.method == "POST"): + confirm_btn.click() expect(page.locator("form[data-game-action='steal']")).to_have_count(0) page.wait_for_timeout(400) note = get_table("notifications").find_one( diff --git a/tests/e2e/post.py b/tests/e2e/post.py index cc1747ff..814c1b92 100644 --- a/tests/e2e/post.py +++ b/tests/e2e/post.py @@ -114,7 +114,10 @@ def test_comment_voted_state_persists(alice): expect( page.locator(".comment-text:has-text('Comment whose vote should persist')") ).to_be_visible() - page.locator(".comment-vote-btn").first.click() + with page.expect_response( + lambda r: "/votes/" in r.url and r.request.method == "POST" + ): + page.locator(".comment-vote-btn").first.click() expect(page.locator(".comment-vote-btn").first).to_have_class( re.compile(r"\bvoted\b") )