room-stage FTs: realign 4 fails to new my-sea NVM + spread-modal + landscape kit-bag UX — TDD
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/ (changed5cade51: 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.a39053dmisdiagnosed 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 <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -565,6 +565,7 @@ class MySeaSpreadFormTest(FunctionalTest):
|
|||||||
the hidden `<input id="id_sea_spread">` initial value + on
|
the hidden `<input id="id_sea_spread">` initial value + on
|
||||||
`.my-sea-cross[data-spread]`."""
|
`.my-sea-cross[data-spread]`."""
|
||||||
picker = self._enter_picker_phase()
|
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")
|
hidden = picker.find_element(By.CSS_SELECTOR, "#id_sea_spread")
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
hidden.get_attribute("value"), "situation-action-outcome",
|
hidden.get_attribute("value"), "situation-action-outcome",
|
||||||
@@ -1929,7 +1930,13 @@ class MySeaGearBtnTest(FunctionalTest):
|
|||||||
len(menu.find_elements(By.CSS_SELECTOR, ".btn-abandon")), 0,
|
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.create_pre_authenticated_session(self.email)
|
||||||
self.browser.get(self.live_server_url + "/gameboard/my-sea/gate/")
|
self.browser.get(self.live_server_url + "/gameboard/my-sea/gate/")
|
||||||
gear = self.wait_for(
|
gear = self.wait_for(
|
||||||
@@ -1946,6 +1953,6 @@ class MySeaGearBtnTest(FunctionalTest):
|
|||||||
).click()
|
).click()
|
||||||
self.wait_for(
|
self.wait_for(
|
||||||
lambda: self.assertRegex(
|
lambda: self.assertRegex(
|
||||||
self.browser.current_url, r"/gameboard/$"
|
self.browser.current_url, r"/gameboard/my-sea/$"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -85,21 +85,16 @@ class CarteBlancheTest(FunctionalTest):
|
|||||||
self.browser.get(self.live_server_url + "/gameboard/")
|
self.browser.get(self.live_server_url + "/gameboard/")
|
||||||
self.wait_for(lambda: self.browser.find_element(By.ID, "id_game_kit"))
|
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
|
# 1a. Clear the My-Sea sign-gate Brief that auto-fires for new users
|
||||||
# without a sig. The Brief slides in via `Brief.showBanner` on DOM-
|
# without a sig — it slides in via `Brief.showBanner` on DOM-ready and
|
||||||
# ready + obscures the create-game-btn (step 8 below). The prior
|
# can obscure the create-game-btn (step 8 below). Earlier revisions
|
||||||
# version of the test had a 5+ second hover/wait at step 2 (Free
|
# hard-WAITED for the banner (wait_for, then wait_for_slow), but its
|
||||||
# Token tooltip — now removed) that masked the race; without that
|
# appearance is a DOM-ready-vs-note.js-load race: under CI contention
|
||||||
# wait, we have to explicitly dismiss the banner before proceeding.
|
# it sometimes never lands in-window, so the wait threw NoSuchElement
|
||||||
# `wait_for_slow` (60s ceiling) — under CI contention the Brief's
|
# (the recurring CI #344/#345/#346 carte fail). We don't assert the
|
||||||
# DOM-ready handler can land past wait_for's 10s default; CI #345
|
# Brief here — we just need it gone. dismiss_brief_if_present DOM-
|
||||||
# hit this exact NoSuchElement timeout.
|
# removes it if present and no-ops if absent: robust to the race.
|
||||||
self.wait_for_slow(
|
self.dismiss_brief_if_present()
|
||||||
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()
|
|
||||||
|
|
||||||
# 2. (REMOVED 2026-05-26) Free Token used to live in the Game Kit
|
# 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.
|
# applet here + served as the "non-trinket, no mini-tooltip" example.
|
||||||
@@ -190,16 +185,18 @@ class CarteBlancheTest(FunctionalTest):
|
|||||||
|
|
||||||
def open_kit_and_select_carte():
|
def open_kit_and_select_carte():
|
||||||
self.browser.find_element(By.ID, "id_kit_btn").click()
|
self.browser.find_element(By.ID, "id_kit_btn").click()
|
||||||
self.wait_for(
|
token = self.wait_for(
|
||||||
lambda: self.browser.find_element(
|
lambda: self.browser.find_element(
|
||||||
By.CSS_SELECTOR,
|
By.CSS_SELECTOR,
|
||||||
f'#id_kit_bag_dialog [data-token-type="{Token.CARTE}"]',
|
f'#id_kit_bag_dialog [data-token-type="{Token.CARTE}"]',
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
self.browser.find_element(
|
# JS-click: in landscape (CI default viewport) the kit-bag dialog
|
||||||
By.CSS_SELECTOR,
|
# is a vertical bar that slides in via a max-width transition, so a
|
||||||
f'#id_kit_bag_dialog [data-token-type="{Token.CARTE}"]',
|
# Selenium .click races the animation and can't scroll the token
|
||||||
).click()
|
# 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():
|
def deposit_carte():
|
||||||
self.browser.find_element(By.CSS_SELECTOR, "button.token-rails").click()
|
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.create_pre_authenticated_session("blanche@test.io")
|
||||||
self.browser.get(self.live_server_url + "/gameboard/")
|
self.browser.get(self.live_server_url + "/gameboard/")
|
||||||
self.wait_for(lambda: self.browser.find_element(By.ID, "id_game_kit"))
|
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
|
# DON the Carte Blanche via tooltip portal
|
||||||
carte_el = self.browser.find_element(By.ID, "id_kit_carte_blanche")
|
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.wait_for(lambda: self.assertIn("/gate/", self.browser.current_url))
|
||||||
|
|
||||||
self.browser.find_element(By.ID, "id_kit_btn").click()
|
self.browser.find_element(By.ID, "id_kit_btn").click()
|
||||||
self.wait_for(
|
token = self.wait_for(
|
||||||
lambda: self.browser.find_element(
|
lambda: self.browser.find_element(
|
||||||
By.CSS_SELECTOR,
|
By.CSS_SELECTOR,
|
||||||
f'#id_kit_bag_dialog [data-token-type="{Token.CARTE}"]',
|
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.browser.find_element(By.CSS_SELECTOR, "button.token-rails").click()
|
||||||
self.wait_for(
|
self.wait_for(
|
||||||
lambda: self.browser.find_element(By.CSS_SELECTOR, ".token-slot.claimed")
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".token-slot.claimed")
|
||||||
|
|||||||
Reference in New Issue
Block a user