FT flake mitigations triggered by pipelines #302/#303 — three independent fixes consolidated in one commit; test_admin_tarot._login_to_admin now waits on "Site administration" body substring before returning, same shape as 054b0aa's test_admin.py fix — the helper used to click submit + return immediately, letting the three TarotAdminTest tests race their subsequent browser.get(/admin/epic/tarotcard/) against the in-flight POST → 302 → admin home navigation so on a slow CI runner the new GET cancelled the unfinished POST, the session cookie was never set, the browser landed back on /admin/login/?next=…, and the downstream assertion saw the login-page body ('Earthman Deck' not found in 'Django administration\nEmail:\nPassword:' in #303, plus a NoSuchElementException for "The Schiz" on test_admin_earthman_card_detail's link-text click) — wait_for w. assertIn retries til the post-login page actually renders so the rest of the helper's callers start from a settled state ; test_jasmine swaps wait_for(check_results)wait_for_slow(check_results, timeout=60) — the spec suite has grown well past the 10s MAX_WAIT the default wait_for decorator affords + under CI contention (parallel Selenium workers competing for CPU on the same droplet) Jasmine's .jasmine-overall-result was still reporting "Running..." at 10s when the assertion fired w. (no detail) failure list (#303); check_results body is unchanged — "Running..." doesn't match the 0 failures regex so it falls into the failure branch + raises AssertionError, which wait_for_slow naturally retries til the result settles or 60s elapses ; base._make_browser wraps webdriver.Firefox(options=options) in try/except WebDriverException w. one sleep+retry when "geckodriver" appears in the error message — covers the spawn race under --parallel where multiple workers hit the same binary mid-permission-set + one of them gets 'geckodriver' executable may have wrong permissions (1 test in #302 + 1 test in #303, different test classes each time, confirming infra not test-logic); narrow filter on "geckodriver" so a genuine install fault still fails fast — both attempts would surface the same error in <1s ; deferred option filed to memory (project_ci_remove_pip_install_deferred.md) — dropping the pip install -r requirements.dev.txt line from each FT step would save ~5 min/pipeline (CI image drifted from requirements.dev.txt since a21e6aa so the install actually downloads 30+ packages every step instead of the intended "already satisfied" no-op verify) but loses the dep-drift safety net; declined for now, revisit when wall-clock pain > safety value — TDD
Some checks failed
ci/woodpecker/push/pyswiss Pipeline was successful
ci/woodpecker/push/main Pipeline failed

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:
Disco DeDisco
2026-05-13 12:53:49 -04:00
parent db10f345e4
commit ad0041db74
3 changed files with 27 additions and 2 deletions

View File

@@ -42,6 +42,16 @@ class FunctionalTest(StaticLiveServerTestCase):
options = webdriver.FirefoxOptions() options = webdriver.FirefoxOptions()
if os.environ.get("HEADLESS"): if os.environ.get("HEADLESS"):
options.add_argument("--headless") options.add_argument("--headless")
try:
browser = webdriver.Firefox(options=options)
except WebDriverException as e:
# Geckodriver spawn race under --parallel: another worker may be
# mid-spawn and the binary briefly reports "wrong permissions".
# One sleep+retry absorbs the race; a genuine install fault will
# fail both attempts. See pipeline #302/#303.
if "geckodriver" not in str(e):
raise
time.sleep(1)
browser = webdriver.Firefox(options=options) browser = webdriver.Firefox(options=options)
browser.set_window_size(width, height) browser.set_window_size(width, height)
return browser return browser

View File

@@ -55,6 +55,14 @@ class TarotAdminTest(FunctionalTest):
self.browser.find_element(By.ID, "id_username").send_keys("admin@example.com") self.browser.find_element(By.ID, "id_username").send_keys("admin@example.com")
self.browser.find_element(By.ID, "id_password").send_keys("correct-password") self.browser.find_element(By.ID, "id_password").send_keys("correct-password")
self.browser.find_element(By.CSS_SELECTOR, "input[type=submit]").click() self.browser.find_element(By.CSS_SELECTOR, "input[type=submit]").click()
# Wait for the POST → 302 → admin home to actually land before
# returning — otherwise the caller's `browser.get(...)` can race
# the in-flight form submit, hit /admin/login/?next=... and never
# recover. Same flake class as 054b0aa fixed in test_admin.py.
self.wait_for(lambda: self.assertIn(
"Site administration",
self.browser.find_element(By.TAG_NAME, "body").text,
))
# ------------------------------------------------------------------ # # ------------------------------------------------------------------ #
# Test 1a — admin home lists Tarot cards + Deck variants under Epic # # Test 1a — admin home lists Tarot cards + Deck variants under Epic #

View File

@@ -20,4 +20,11 @@ class JasmineTest(FunctionalTest):
detail = "\n".join(f.text for f in failures) if failures else "(no detail)" detail = "\n".join(f.text for f in failures) if failures else "(no detail)"
self.fail(f"{result.text}\nFailing specs:\n{detail}") self.fail(f"{result.text}\nFailing specs:\n{detail}")
self.wait_for(check_results) # wait_for_slow w. 60s ceiling — the spec suite has grown well past the
# 10s MAX_WAIT the default wait_for affords. Under CI contention
# (parallel Selenium workers competing for CPU) Jasmine's still
# reporting "Running..." at 10s; pipeline #303 caught this. The
# check_results body keeps raising AssertionError ("Running..." doesn't
# match the 0-failures regex) so wait_for_slow naturally retries til
# the result settles or the timeout fires.
self.wait_for_slow(check_results, timeout=60)