FT fixes for polish-9 spec changes — CI #338 surfaced 4 stale assertions; sig-gate Brief race exposed by removing implicit wait
CI pipeline #338 caught 4 FT failures cascading from yesterday's polish-9 + applet realignment commits (955bdc7,652cef0). All four are stale assertions in FT code — no production code changes needed. ITs were already updated in the original commits; missed the parallel FT updates. **(1) `test_gear_btn_opens_menu_with_nvm_only`** (test_game_my_sea.py:1851) — NVM btn changed `<a class="btn" href="...">` → `<button onclick="location.href=...">` (per [[feedback-btn-vs-anchor-font-family]] sans-serif fix). FT was reading `href` attr (returns None on buttons → `TypeError: argument of type 'NoneType' is not iterable`). Switched to read `onclick` attr (w. `or ""` guard against None). **(2) `test_del_btn_is_disabled_until_hand_complete`** (test_game_my_sea.py:982 → renamed) — DEL btn now un-disables on the FIRST draw, not at hand completion (per the new state-machine spec, `_setHasDrawn(true)` fires on first deposit + AUTO DRAW POST-commit). Renamed → `test_del_btn_is_disabled_until_first_draw`; inverted the mid-draw assertion (was: still-disabled after 1 draw → now: un-disables immediately after 1 draw); kept the post-completion check (DEL stays enabled). **(3) `test_carte_blanche_equip_and_multi_slot_gatekeeper`** (test_trinket_carte_blanche.py:89) — TWO issues here, only the first was symptomatic in CI: - Step 2 used `#id_kit_free_token` on /gameboard/ as a "non-trinket, no mini-tooltip" demo target. Free Token moved off Game Kit applet to Wallet applet per the equippables-only spec; no non-equippable icon left on Game Kit to demo w. Dropped step 2 entirely — the test's primary thing (Carte multi-slot equip flow at steps 3+) is intact. - SECOND-ORDER issue uncovered when (1) above stopped masking it: the deleted step 2 used to provide a ~5+ second wait (find Free Token + hover + wait for tooltip portal). That wait was enough for the auto-firing `.my-sea-sign-gate-brief` (slides in on /gameboard/ for users w/o a sig via the My Sea applet's `{% include _my_sea_sign_gate_brief.html %}` branch) to settle. Without the wait, the Brief is mid-slide when step 8 tries to click `id_create_game_btn` → `ElementClickInterceptedException` (Brief obscures button). Added explicit `.my-sea-sign-gate-brief .btn-cancel` wait-then-click between steps 1 + 3 to dismiss the Brief before proceeding. **(4) `test_game_kit_panel_shows_token_inventory`** (test_gameboard.py:74) — TWO issues here too: - Step 7's `#id_kit_free_token` Free Token tooltip assertion. Same removal as (3). Replaced w. a NEGATIVE assertion that the element does NOT exist on Game Kit (regression guard against accidentally re-adding non-equippable items). - SECOND-ORDER again: step 9's `#id_kit_card_deck` check was a stale assertion that predated the `apps/lyric/models.py:540` `unlocked_decks.add(earthman)` post_save signal. `id_kit_card_deck` is the `{% empty %}`-branch placeholder, only rendered when `deck_variants` is empty. `capman@test.io` (the test fixture user) gets Earthman auto-unlocked → the concrete `id_kit_earthman_deck` renders instead. This was a latent stale assertion that only surfaced now because step 7's Free Token failure used to short-circuit the test before it reached step 9. Switched check to `id_kit_earthman_deck`. Pattern worth noting for future cross-cutting refactors: when a test step has a side-effect wait (`wait_for(... tooltip displayed ...)`), removing it can unmask sig-gate / palette / Brief banners that auto-slide in on page load. The Brief race in (3) wasn't a NEW bug introduced by polish-9; it was always there, masked by the timing of the removed step. Same for the stale `id_kit_card_deck` assertion — predates the signal change; only surfaced when the failure cascade moved past it. Discipline note for this session: user explicitly overrode [[feedback-ft-run-discipline]] when "specifically working on FTs, new or old" — ran each fix locally by full dotted path to verify before committing. All 4 green locally (8-16s each). Code architected by Disco DeDisco <discodedisco@outlook.com> Git commit message Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -969,23 +969,26 @@ class MySeaCardDrawTest(FunctionalTest):
|
|||||||
|
|
||||||
# ── Test 6 ───────────────────────────────────────────────────────────────
|
# ── Test 6 ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
def test_del_btn_is_disabled_until_hand_complete(self):
|
def test_del_btn_is_disabled_until_first_draw(self):
|
||||||
"""Iter-4c — DEL btn renders `.btn-disabled` server-side until
|
"""User spec 2026-05-26: DEL btn renders `.btn-disabled` only
|
||||||
the hand is complete (per spec: the 24h free-draw quota is
|
UNTIL the first card lands (was: until hand complete). The 24h
|
||||||
committed at first-card-draw, can't be refunded by an early
|
free-draw quota is still committed at first-card-draw + can't be
|
||||||
DEL). Once the hand fills, JS removes `.btn-disabled` from DEL."""
|
refunded by an early DEL, but the user can DEL the in-progress
|
||||||
|
hand to start over within the cycle. JS `_setHasDrawn(true)` fires
|
||||||
|
on the first deposit + at the AUTO DRAW POST-commit moment."""
|
||||||
picker = self._enter_picker_phase()
|
picker = self._enter_picker_phase()
|
||||||
delbtn = picker.find_element(By.CSS_SELECTOR, "#id_sea_del")
|
delbtn = picker.find_element(By.CSS_SELECTOR, "#id_sea_del")
|
||||||
|
# Pre-draw — disabled.
|
||||||
self.assertIn("btn-disabled", delbtn.get_attribute("class"))
|
self.assertIn("btn-disabled", delbtn.get_attribute("class"))
|
||||||
self._draw_one(picker, "levity")
|
self._draw_one(picker, "levity")
|
||||||
# Mid-draw — still disabled.
|
# First card landed — DEL un-disables immediately.
|
||||||
self.assertIn("btn-disabled", delbtn.get_attribute("class"))
|
|
||||||
self._draw_one(picker, "levity")
|
|
||||||
self._draw_one(picker, "gravity")
|
|
||||||
# Hand complete — DEL un-disables (clicking now opens guard portal).
|
|
||||||
self.wait_for(
|
self.wait_for(
|
||||||
lambda: self.assertNotIn("btn-disabled", delbtn.get_attribute("class"))
|
lambda: self.assertNotIn("btn-disabled", delbtn.get_attribute("class"))
|
||||||
)
|
)
|
||||||
|
# Subsequent draws + completion — DEL stays enabled.
|
||||||
|
self._draw_one(picker, "levity")
|
||||||
|
self._draw_one(picker, "gravity")
|
||||||
|
self.assertNotIn("btn-disabled", delbtn.get_attribute("class"))
|
||||||
|
|
||||||
# ── Test 7 ───────────────────────────────────────────────────────────────
|
# ── Test 7 ───────────────────────────────────────────────────────────────
|
||||||
|
|
||||||
@@ -1846,9 +1849,12 @@ class MySeaGearBtnTest(FunctionalTest):
|
|||||||
lambda: self.browser.find_element(By.ID, "id_my_sea_menu")
|
lambda: self.browser.find_element(By.ID, "id_my_sea_menu")
|
||||||
)
|
)
|
||||||
self.assertEqual(menu.value_of_css_property("display"), "block")
|
self.assertEqual(menu.value_of_css_property("display"), "block")
|
||||||
# NVM link present, DEL + BYE deliberately absent.
|
# NVM btn present, DEL + BYE deliberately absent. NVM is a `<button
|
||||||
|
# onclick="location.href=...">` (NOT an `<a class="btn">`) per 2026-
|
||||||
|
# 05-26 fix — anchors inherit body's serif font, buttons stay sans-
|
||||||
|
# serif. Check the onclick attr for the nav target instead of href.
|
||||||
nvm = menu.find_element(By.CSS_SELECTOR, ".btn-cancel")
|
nvm = menu.find_element(By.CSS_SELECTOR, ".btn-cancel")
|
||||||
self.assertIn("/gameboard/", nvm.get_attribute("href"))
|
self.assertIn("/gameboard/", nvm.get_attribute("onclick") or "")
|
||||||
self.assertEqual(
|
self.assertEqual(
|
||||||
len(menu.find_elements(By.CSS_SELECTOR, ".btn-danger")), 0,
|
len(menu.find_elements(By.CSS_SELECTOR, ".btn-danger")), 0,
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -70,21 +70,26 @@ class GameboardNavigationTest(FunctionalTest):
|
|||||||
self.assertIn("Coin-on-a-String", coin_tooltip)
|
self.assertIn("Coin-on-a-String", coin_tooltip)
|
||||||
self.assertIn("Admit 1 Entry", coin_tooltip)
|
self.assertIn("Admit 1 Entry", coin_tooltip)
|
||||||
self.assertIn("and another after that", coin_tooltip)
|
self.assertIn("and another after that", coin_tooltip)
|
||||||
# 7. Assert 1× Free Token (complimentary) present in kit
|
# 7. (REMOVED 2026-05-26) Free Token used to live in Game Kit applet
|
||||||
free_token = self.browser.find_element(By.ID, "id_kit_free_token")
|
# — moved to /dashboard/'s My Wallet applet per spec ("only equippables
|
||||||
# 8. Hover over it; assert tooltip shows name, entry text & expiry date
|
# in Game Kit"). Free Token tooltip coverage now lives on /dashboard/.
|
||||||
ActionChains(self.browser).move_to_element(free_token).perform()
|
# 8. NEGATIVE assertion: Free Token element must NOT exist on Game Kit
|
||||||
self.wait_for(
|
# — regression guard against accidentally re-adding non-equippable
|
||||||
lambda: self.assertTrue(
|
# items here.
|
||||||
self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
|
self.assertEqual(
|
||||||
|
len(self.browser.find_elements(By.ID, "id_kit_free_token")), 0,
|
||||||
|
"Free Token must NOT render in the Game Kit applet — it's not "
|
||||||
|
"equippable; lives in My Wallet applet on /dashboard/",
|
||||||
)
|
)
|
||||||
)
|
# 9. Assert card deck + dice set present. capman has Earthman unlocked
|
||||||
free_tooltip = self.browser.find_element(By.ID, "id_tooltip_portal").text
|
# via the post_save signal (`apps/lyric/models.py:540`), so the
|
||||||
self.assertIn("Free Token", free_tooltip)
|
# `id_kit_card_deck` empty-state placeholder doesn't render — the
|
||||||
self.assertIn("Admit 1 Entry", free_tooltip)
|
# concrete `id_kit_earthman_deck` does instead. (The `id_kit_card_
|
||||||
self.assertIn("Expires", free_tooltip)
|
# deck` placeholder was previously asserted here, which was a stale
|
||||||
# 9. Assert card deck & dice set placeholder present
|
# check predating the auto-unlock signal — caught 2026-05-26 by the
|
||||||
self.browser.find_element(By.ID, "id_kit_card_deck")
|
# CI run that surfaced it after the Free Token relocation moved this
|
||||||
|
# test's failure point past step 7.)
|
||||||
|
self.browser.find_element(By.ID, "id_kit_earthman_deck")
|
||||||
self.browser.find_element(By.ID, "id_kit_dice_set")
|
self.browser.find_element(By.ID, "id_kit_dice_set")
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -85,15 +85,27 @@ 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"))
|
||||||
|
|
||||||
# 2. Hover over Free Token — no mini tooltip (not a trinket, no data-token-id)
|
# 1a. Dismiss the My-Sea sign-gate Brief that auto-fires for new users
|
||||||
el = self.browser.find_element(By.ID, "id_kit_free_token")
|
# without a sig. The Brief slides in via `Brief.showBanner` on DOM-
|
||||||
ActionChains(self.browser).move_to_element(el).perform()
|
# 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.
|
||||||
self.wait_for(
|
self.wait_for(
|
||||||
lambda: self.browser.find_element(By.ID, "id_tooltip_portal").is_displayed()
|
lambda: self.browser.find_element(By.CSS_SELECTOR, ".my-sea-sign-gate-brief")
|
||||||
)
|
|
||||||
self.assertFalse(
|
|
||||||
self.browser.find_element(By.ID, "id_mini_tooltip_portal").is_displayed()
|
|
||||||
)
|
)
|
||||||
|
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
|
||||||
|
# applet here + served as the "non-trinket, no mini-tooltip" example.
|
||||||
|
# Per user spec, Free + Tithe tokens moved to /dashboard/'s Wallet
|
||||||
|
# applet — Game Kit now holds ONLY equippables (trinkets, decks,
|
||||||
|
# dice), all of which carry data-token-id or data-deck-id + show
|
||||||
|
# the mini-tooltip. No non-equippable icon left on Game Kit to
|
||||||
|
# demo the no-mini case w. The rest of the test (Carte Blanche
|
||||||
|
# multi-slot equip flow) is the primary thing being verified.
|
||||||
# Coin-on-a-String IS equippable (has data-token-id) — mini tooltip shows
|
# Coin-on-a-String IS equippable (has data-token-id) — mini tooltip shows
|
||||||
coin_el = self.browser.find_element(By.ID, "id_kit_coin_on_a_string")
|
coin_el = self.browser.find_element(By.ID, "id_kit_coin_on_a_string")
|
||||||
ActionChains(self.browser).move_to_element(coin_el).perform()
|
ActionChains(self.browser).move_to_element(coin_el).perform()
|
||||||
|
|||||||
Reference in New Issue
Block a user