From 1c799d35ca3ebd5b8ed81f9f401856c99927cc97 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Wed, 27 May 2026 02:00:35 -0400 Subject: [PATCH] =?UTF-8?q?room-stage=20FTs:=20realign=204=20fails=20to=20?= =?UTF-8?q?new=20my-sea=20NVM=20+=20spread-modal=20+=20landscape=20kit-bag?= =?UTF-8?q?=20UX=20=E2=80=94=20TDD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI #346 test-FTs-room had 4 consistent fails (failed on both the first run AND the retry, so real, not flakes). All 4 are test-side — the shipped features are correct; the FTs lagged behind deliberate UX changes + a race they never needed to depend on. test_game_my_sea.py - test_nvm_navigates_back_to_gameboard → renamed test_nvm_navigates_back_to_ my_sea_hex; asserts /gameboard/my-sea/$ now. NVM on the gatekeeper navigates to the table hex, not out to /gameboard/ (changed 5cade51: gatekeeper + picker NVM → hex; only landing + sign-gate eject to /gameboard/). Sibling test_gear_btn_opens_menu_with_nvm_only still passes (only checks the onclick contains /gameboard/). - test_default_spread_is_situation_action_outcome → add _open_spread_modal(self). The spread combobox moved into #id_sea_spread_modal (burger Sea sub-btn sprint); .sea-select-current .text returned '' while the modal was hidden. Mirrors the already-updated sibling test_picking_spread_swaps_*. test_trinket_carte_blanche.py (the recurring #344/#345/#346 carte fail) - Sign-gate Brief: replace the hard wait_for_slow(find .my-sea-sign-gate-brief) + NVM-click with dismiss_brief_if_present(). The Brief fires via Brief.showBanner on DOM-ready; its appearance is a DOM-ready-vs-note.js-load race, so under CI contention it sometimes never lands in-window and ANY hard wait throws NoSuchElement. a39053d misdiagnosed this as a timeout (→ wait_for_slow); it is not. The test never asserts the Brief — it only clears it to unblock a later click. dismiss_brief_if_present removes it if present + no-ops if absent: robust to the race. - Kit-bag token select: JS-click the #id_kit_bag_dialog CARTE token. In landscape (CI default viewport) the kit-bag dialog is a vertical bar that slides in via a max-width transition (burger landscape refactor), so a Selenium .click races the animation + can't scroll the token into the overflow container ("could not be scrolled into view"). execute_script fires the bound handler directly. Applied in both carte tests (open_kit_and_select_carte + the in-use attribution flow); token-rails stays a normal click (it lives on the gate page, not in the dialog). Verification: all 4 methods green locally (landscape viewport) — test_carte_in_use_game_kit_shows_room_attribution 10.8s; the multi-slot carte + both my-sea methods 35.5s. Code architected by Disco DeDisco Git commit message Co-Authored-By: Claude Opus 4.7 --- src/functional_tests/test_game_my_sea.py | 11 ++++- .../test_trinket_carte_blanche.py | 48 ++++++++++--------- 2 files changed, 35 insertions(+), 24 deletions(-) diff --git a/src/functional_tests/test_game_my_sea.py b/src/functional_tests/test_game_my_sea.py index c5aa615..fb64d1b 100644 --- a/src/functional_tests/test_game_my_sea.py +++ b/src/functional_tests/test_game_my_sea.py @@ -565,6 +565,7 @@ class MySeaSpreadFormTest(FunctionalTest): the hidden `` initial value + on `.my-sea-cross[data-spread]`.""" picker = self._enter_picker_phase() + _open_spread_modal(self) # .sea-select-current lives in the spread modal hidden = picker.find_element(By.CSS_SELECTOR, "#id_sea_spread") self.assertEqual( hidden.get_attribute("value"), "situation-action-outcome", @@ -1929,7 +1930,13 @@ class MySeaGearBtnTest(FunctionalTest): len(menu.find_elements(By.CSS_SELECTOR, ".btn-abandon")), 0, ) - def test_nvm_navigates_back_to_gameboard(self): + def test_nvm_navigates_back_to_my_sea_hex(self): + # NVM on the my-sea gatekeeper navigates back to the my-sea table hex + # (/gameboard/my-sea/), NOT out to /gameboard/ — changed 5cade51 + # (gatekeeper + picker NVM → table hex; only the landing + sign-gate + # phases eject to /gameboard/). Sibling test_gear_btn_opens_menu_with_ + # nvm_only still passes since it only checks the onclick *contains* + # /gameboard/. self.create_pre_authenticated_session(self.email) self.browser.get(self.live_server_url + "/gameboard/my-sea/gate/") gear = self.wait_for( @@ -1946,6 +1953,6 @@ class MySeaGearBtnTest(FunctionalTest): ).click() self.wait_for( lambda: self.assertRegex( - self.browser.current_url, r"/gameboard/$" + self.browser.current_url, r"/gameboard/my-sea/$" ) ) diff --git a/src/functional_tests/test_trinket_carte_blanche.py b/src/functional_tests/test_trinket_carte_blanche.py index 02edb6c..f6e271b 100644 --- a/src/functional_tests/test_trinket_carte_blanche.py +++ b/src/functional_tests/test_trinket_carte_blanche.py @@ -85,21 +85,16 @@ class CarteBlancheTest(FunctionalTest): self.browser.get(self.live_server_url + "/gameboard/") self.wait_for(lambda: self.browser.find_element(By.ID, "id_game_kit")) - # 1a. Dismiss the My-Sea sign-gate Brief that auto-fires for new users - # without a sig. The Brief slides in via `Brief.showBanner` on DOM- - # ready + obscures the create-game-btn (step 8 below). The prior - # version of the test had a 5+ second hover/wait at step 2 (Free - # Token tooltip — now removed) that masked the race; without that - # wait, we have to explicitly dismiss the banner before proceeding. - # `wait_for_slow` (60s ceiling) — under CI contention the Brief's - # DOM-ready handler can land past wait_for's 10s default; CI #345 - # hit this exact NoSuchElement timeout. - self.wait_for_slow( - lambda: self.browser.find_element(By.CSS_SELECTOR, ".my-sea-sign-gate-brief") - ) - self.browser.find_element( - By.CSS_SELECTOR, ".my-sea-sign-gate-brief .btn-cancel" - ).click() + # 1a. Clear the My-Sea sign-gate Brief that auto-fires for new users + # without a sig — it slides in via `Brief.showBanner` on DOM-ready and + # can obscure the create-game-btn (step 8 below). Earlier revisions + # hard-WAITED for the banner (wait_for, then wait_for_slow), but its + # appearance is a DOM-ready-vs-note.js-load race: under CI contention + # it sometimes never lands in-window, so the wait threw NoSuchElement + # (the recurring CI #344/#345/#346 carte fail). We don't assert the + # Brief here — we just need it gone. dismiss_brief_if_present DOM- + # removes it if present and no-ops if absent: robust to the race. + self.dismiss_brief_if_present() # 2. (REMOVED 2026-05-26) Free Token used to live in the Game Kit # applet here + served as the "non-trinket, no mini-tooltip" example. @@ -190,16 +185,18 @@ class CarteBlancheTest(FunctionalTest): def open_kit_and_select_carte(): self.browser.find_element(By.ID, "id_kit_btn").click() - self.wait_for( + token = self.wait_for( lambda: self.browser.find_element( By.CSS_SELECTOR, f'#id_kit_bag_dialog [data-token-type="{Token.CARTE}"]', ) ) - self.browser.find_element( - By.CSS_SELECTOR, - f'#id_kit_bag_dialog [data-token-type="{Token.CARTE}"]', - ).click() + # JS-click: in landscape (CI default viewport) the kit-bag dialog + # is a vertical bar that slides in via a max-width transition, so a + # Selenium .click races the animation and can't scroll the token + # into view inside the overflow container ("could not be scrolled + # into view"). execute_script fires the bound handler directly. + self.browser.execute_script("arguments[0].click()", token) def deposit_carte(): self.browser.find_element(By.CSS_SELECTOR, "button.token-rails").click() @@ -353,6 +350,9 @@ class CarteBlancheTest(FunctionalTest): self.create_pre_authenticated_session("blanche@test.io") self.browser.get(self.live_server_url + "/gameboard/") self.wait_for(lambda: self.browser.find_element(By.ID, "id_game_kit")) + # Clear the sign-gate Brief if it fired (see the multi-slot test) so it + # can't obscure the New Game applet's create-game-btn below. + self.dismiss_brief_if_present() # DON the Carte Blanche via tooltip portal carte_el = self.browser.find_element(By.ID, "id_kit_carte_blanche") @@ -379,12 +379,16 @@ class CarteBlancheTest(FunctionalTest): self.wait_for(lambda: self.assertIn("/gate/", self.browser.current_url)) self.browser.find_element(By.ID, "id_kit_btn").click() - self.wait_for( + token = self.wait_for( lambda: self.browser.find_element( By.CSS_SELECTOR, f'#id_kit_bag_dialog [data-token-type="{Token.CARTE}"]', ) - ).click() + ) + # JS-click — landscape kit-bag dialog slide-in animation (see the + # multi-slot test's open_kit_and_select_carte); bypasses the "could + # not be scrolled into view". + self.browser.execute_script("arguments[0].click()", token) self.browser.find_element(By.CSS_SELECTOR, "button.token-rails").click() self.wait_for( lambda: self.browser.find_element(By.CSS_SELECTOR, ".token-slot.claimed")