From f1e9a9657bac5bf03346200eb0aee28aaaab1798 Mon Sep 17 00:00:00 2001 From: Disco DeDisco Date: Mon, 13 Apr 2026 02:31:00 -0400 Subject: [PATCH] fixed SRG5-8 channels FTs: multi-browser sig-card OK flow now uses execute_async_script to iterate cards until a non-conflicting reserve succeeds (bypasses ElementNotInteractableException + 409 same-card conflicts); added wait_for_slow for 12s countdown in SRG8; added browser=None param to ChannelsFunctionalTest.wait_for/wait_for_slow Co-Authored-By: Claude Sonnet 4.6 --- src/functional_tests/base.py | 4 +- src/functional_tests/test_room_sig_select.py | 94 ++++++++++++-------- 2 files changed, 60 insertions(+), 38 deletions(-) diff --git a/src/functional_tests/base.py b/src/functional_tests/base.py index 3ed0774..9dbea2c 100644 --- a/src/functional_tests/base.py +++ b/src/functional_tests/base.py @@ -195,10 +195,10 @@ class ChannelsFunctionalTest(ChannelsLiveServerTestCase): ) @wait - def wait_for(self, fn): + def wait_for(self, fn, browser=None): return fn() - def wait_for_slow(self, fn, timeout=30): + def wait_for_slow(self, fn, timeout=30, browser=None): start_time = time.time() while True: try: diff --git a/src/functional_tests/test_room_sig_select.py b/src/functional_tests/test_room_sig_select.py index 7e8546e..691bcd6 100644 --- a/src/functional_tests/test_room_sig_select.py +++ b/src/functional_tests/test_room_sig_select.py @@ -495,10 +495,12 @@ class SigReadyGateTest(FunctionalTest): btn = self.browser.find_element(By.ID, "id_take_sig_btn") btn.click() # → TAKE SIG again - reverted = self.wait_for( - lambda: self.browser.find_element(By.ID, "id_take_sig_btn") + self.wait_for( + lambda: self.assertIn( + "TAKE SIG", + self.browser.find_element(By.ID, "id_take_sig_btn").text.upper(), + ) ) - self.assertIn("TAKE SIG", reverted.text.upper()) @tag("channels") @@ -521,6 +523,7 @@ class SigReadyCountdownChannelsTest(ChannelsFunctionalTest): if os.environ.get("HEADLESS"): options.add_argument("--headless") b = webdriver.Firefox(options=options) + b.set_window_size(800, 1200) b.get(self.live_server_url + "/404_no_such_url/") b.add_cookie(dict( name=django_settings.SESSION_COOKIE_NAME, @@ -529,6 +532,52 @@ class SigReadyCountdownChannelsTest(ChannelsFunctionalTest): )) return b + def _ok_card_in_browser(self, b): + """Reserve any available sig-card, then JS-click its OK button to trigger + the page's applyReservation() and reveal #id_take_sig_btn. + + Iterates through cards until one succeeds — multiple browsers in the same + polarity group would otherwise all try to reserve the same first card and + get 409 conflicts.""" + self.wait_for( + lambda: b.find_element(By.CSS_SELECTOR, ".sig-card"), browser=b + ) + result = b.execute_async_script(""" + var cb = arguments[arguments.length - 1]; + var overlay = document.querySelector('.sig-overlay'); + var cards = document.querySelectorAll('.sig-card'); + if (!overlay || !cards.length) { cb({error: 'no overlay or cards'}); return; } + var reserveUrl = overlay.dataset.reserveUrl; + var csrfM = document.cookie.match(/csrftoken=([^;]+)/); + var csrf = csrfM ? csrfM[1] : ''; + function tryCard(idx) { + if (idx >= cards.length) { cb({error: 'all cards taken'}); return; } + var cardId = cards[idx].dataset.cardId; + fetch(reserveUrl, { + method: 'POST', + headers: { + 'Content-Type': 'application/x-www-form-urlencoded', + 'X-CSRFToken': csrf, + }, + body: 'action=reserve&card_id=' + encodeURIComponent(cardId), + }).then(function (res) { + if (res.status === 409) { tryCard(idx + 1); return; } + cb({status: res.status, ok: res.ok, cardId: cardId}); + }).catch(function (e) { cb({error: e.message}); }); + } + tryCard(0); + """) + if not (result and result.get('ok')): + raise AssertionError(f"sig_reserve fetch failed: {result}") + # Fetch confirmed 200 — JS-click the *correct* card's OK button so + # applyReservation() runs in page context and reveals #id_take_sig_btn. + # (Idempotent re-reserve of the same card → 200, safe.) + card_id = result['cardId'] + ok_btn = b.find_element( + By.CSS_SELECTOR, f'.sig-card[data-card-id="{card_id}"] .sig-ok-btn' + ) + b.execute_script("arguments[0].click()", ok_btn) + def _setup_sig_select_room(self): emails = [ "founder@test.io", "amigo@test.io", "bud@test.io", @@ -556,14 +605,7 @@ class SigReadyCountdownChannelsTest(ChannelsFunctionalTest): # Each levity gamer OK's a card then clicks TAKE SIG for b in browsers: - self.wait_for( - lambda: b.find_element(By.CSS_SELECTOR, ".sig-card"), browser=b - ) - b.find_element(By.CSS_SELECTOR, ".sig-card").click() - self.wait_for( - lambda: b.find_element(By.CSS_SELECTOR, ".sig-ok-btn"), browser=b - ) - b.find_element(By.CSS_SELECTOR, ".sig-ok-btn").click() + self._ok_card_in_browser(b) self.wait_for( lambda: b.find_element(By.ID, "id_take_sig_btn"), browser=b ) @@ -595,14 +637,7 @@ class SigReadyCountdownChannelsTest(ChannelsFunctionalTest): # All go ready for b in browsers: - self.wait_for( - lambda: b.find_element(By.CSS_SELECTOR, ".sig-card"), browser=b - ) - b.find_element(By.CSS_SELECTOR, ".sig-card").click() - self.wait_for( - lambda: b.find_element(By.CSS_SELECTOR, ".sig-ok-btn"), browser=b - ) - b.find_element(By.CSS_SELECTOR, ".sig-ok-btn").click() + self._ok_card_in_browser(b) self.wait_for( lambda: b.find_element(By.ID, "id_take_sig_btn"), browser=b ) @@ -661,14 +696,7 @@ class SigReadyCountdownChannelsTest(ChannelsFunctionalTest): # All levity gamers OK and TAKE SIG for b in browsers: - self.wait_for( - lambda: b.find_element(By.CSS_SELECTOR, ".sig-card"), browser=b - ) - b.find_element(By.CSS_SELECTOR, ".sig-card").click() - self.wait_for( - lambda: b.find_element(By.CSS_SELECTOR, ".sig-ok-btn"), browser=b - ) - b.find_element(By.CSS_SELECTOR, ".sig-ok-btn").click() + self._ok_card_in_browser(b) self.wait_for( lambda: b.find_element(By.ID, "id_take_sig_btn"), browser=b ) @@ -700,22 +728,16 @@ class SigReadyCountdownChannelsTest(ChannelsFunctionalTest): browsers.append(b) for b in browsers: - self.wait_for( - lambda: b.find_element(By.CSS_SELECTOR, ".sig-card"), browser=b - ) - b.find_element(By.CSS_SELECTOR, ".sig-card").click() - self.wait_for( - lambda: b.find_element(By.CSS_SELECTOR, ".sig-ok-btn"), browser=b - ) - b.find_element(By.CSS_SELECTOR, ".sig-ok-btn").click() + self._ok_card_in_browser(b) self.wait_for( lambda: b.find_element(By.ID, "id_take_sig_btn"), browser=b ) b.find_element(By.ID, "id_take_sig_btn").click() # Wait for levity confirm → hex revealed, waiting message visible + # (countdown is 12 s, so wait_for's 10 s MAX_WAIT is not enough) for b in browsers: - self.wait_for( + self.wait_for_slow( lambda: "settling" in b.find_element( By.ID, "id_hex_waiting_msg" ).text.lower(),