From add751dc36d2d5be3bbcc616d876899ef78ab6be Mon Sep 17 00:00:00 2001 From: Armin Samii Date: Wed, 23 Sep 2026 14:14:33 -0400 Subject: [PATCH 1/3] fix logout --- static/visualizer/theme.css | 25 +++++++++++++++++++++++++ templates/visualizer/uploadFile.html | 10 +++++++--- 2 files changed, 32 insertions(+), 3 deletions(-) diff --git a/static/visualizer/theme.css b/static/visualizer/theme.css index d8c0a793..90eeb176 100644 --- a/static/visualizer/theme.css +++ b/static/visualizer/theme.css @@ -264,6 +264,31 @@ h1, h2, h3, h4, h5, h6 { color: var(--rcv-brand); } +.nav-logout-form { + display: inline; + margin: 0; +} + +.nav-logout-button { + padding: 0; + border: 0; + background: none; + font: inherit; + color: var(--rcv-brand); + cursor: pointer; +} + +.nav-logout-button:hover { + color: var(--rcv-brand-deep); + text-decoration: underline; +} + +.nav-logout-button:focus-visible { + outline: 2px solid var(--rcv-brand); + outline-offset: 2px; + border-radius: 3px; +} + /* ------------------------------------------------------------------ */ /* Visualization tab bar (the /v/ page puts its tabs in the header) */ /* ------------------------------------------------------------------ */ diff --git a/templates/visualizer/uploadFile.html b/templates/visualizer/uploadFile.html index 3db0d727..cae30f9e 100644 --- a/templates/visualizer/uploadFile.html +++ b/templates/visualizer/uploadFile.html @@ -6,11 +6,15 @@ {% endblock %} {% block navlinks %} - {% endblock %} {% block maincontent %} From 7a174d22e5f6fb6ba60f168aac1f8b5dc2b394d4 Mon Sep 17 00:00:00 2001 From: Armin Samii Date: Fri, 25 Sep 2026 17:13:50 -0400 Subject: [PATCH 2/3] Validate the upload form by id, not the first form on the page The logout link is now a form in the header, so the data-entry validator was serializing it (just a CSRF token) instead of the upload form, and every validation failed with "Error #20". Co-Authored-By: Claude Fable 5.1 --- static/datatable/src/datatable.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/static/datatable/src/datatable.ts b/static/datatable/src/datatable.ts index cfae52be..c33ba92a 100644 --- a/static/datatable/src/datatable.ts +++ b/static/datatable/src/datatable.ts @@ -453,8 +453,9 @@ export default class RcvisDataTable { this.toJson(this.table.getData())); } - // Prepare the mock form - const form = document.getElementsByTagName('form')[0]; + // Prepare the mock form: the upload form by id, since the page has other + // forms too (e.g. the logout form in the header) + const form = document.getElementById('form'); const data = $(form).serializeArray(); // AJAX to check if it would survive all serverside processing From a5e6af9d072e4a34f70f559f950b29f8539bb9c3 Mon Sep 17 00:00:00 2001 From: Armin Samii Date: Fri, 25 Sep 2026 17:40:20 -0400 Subject: [PATCH 3/3] Fix the no-op page-reload wait in test_create_scrapable_page EC.staleness_of returns a callable and never raises, so wrapping it in _ensure_eventually_asserts waited for nothing. The test then read the old page's validation message before the navigation happened. Use WebDriverWait so it actually waits for the reload. Co-Authored-By: Claude Fable 5.1 --- electionpage/tests.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/electionpage/tests.py b/electionpage/tests.py index 1a75a71c..f946bb39 100644 --- a/electionpage/tests.py +++ b/electionpage/tests.py @@ -26,6 +26,7 @@ from selenium.common.exceptions import NoSuchElementException, StaleElementReferenceException from selenium.webdriver.common.by import By from selenium.webdriver.support import expected_conditions as EC +from selenium.webdriver.support.ui import WebDriverWait from common.testUtils import TestHelpers from electionpage.models import ElectionPage, ScrapableElectionPage, SingleSourceElectionPage @@ -195,8 +196,8 @@ def submit_with_num_elections_and_get_error(num, expectReload): self.browser.find_element(By.ID, "submit").click() if expectReload: - self._ensure_eventually_asserts( - lambda: EC.staleness_of(bodyElement)) + # Wait for the navigation: the old page's body goes stale + WebDriverWait(self.browser, timeout=10).until(EC.staleness_of(bodyElement)) try: return self.browser.find_element(