Unpin selenium to fix CI flakiness against current Chrome - #3955
Conversation
The testing requirements capped selenium at <=4.2.0 (2022), which predates Selenium Manager. CI installs the current stable Chrome (now 151) via an unpinned browser-actions/setup-chrome, and selenium 4.2 cannot reliably provision or drive it, producing scattered StaleElementReferenceException / TimeoutException failures across unrelated browser integration tests on every push and PR. Require selenium>=4.11.0 (mature Selenium Manager auto-provisions a matching chromedriver) up to the current latest 4.46.0.
Unpinning selenium exposed two deterministic breaks the 4.2.0 cap had hidden: - browser.py set the 'marionette' Firefox capability, which modern selenium/geckodriver reject with InvalidArgumentException (marionette is the implicit, only protocol now). Removed it. - Three test modules used the find_element(s)_by_* helper methods that selenium removed in 4.3. Migrated them to find_element(s)(By.*, ...).
|
Follow-up commit
Remaining |
selenium 4.3 changed move_to_element_with_offset to measure the offset from the element's center instead of its top-left corner. The dash_duo drag/click helpers (click_at_coord_fractions, zoom_in_graph_by_ratio) and the dcc page object helpers passed top-left-based fractional offsets (width*fx, height*fy), so under modern selenium they overshot past the element edge and raised MoveTargetOutOfBoundsException — failing the slider drag/step tests and the graph tooltip center-hover test. Convert the proportional offsets to center-relative (width*(fx-0.5)) and cast to int (W3C actions require integer pixels). Small fixed-pixel offsets (5, 8) are left as-is: they stay within any element regardless of origin.
dash_duo's _wait_for helpers raise selenium's TimeoutException(str(message)). Modern selenium's WebDriverException.__init__ calls super().__init__() with no args, so the message lives on .msg and .args is empty — test_duo's err.value.args[0] assertions raised IndexError. Read .msg, selenium's stable message accessor.
The step backgrounded Xvfb with a bare '&', so it inherited the step's stdout/stderr pipe to the Actions runner. Xvfb never exits, so that pipe never reached EOF and the runner blocked on the step indefinitely (intermittent 'Setup virtual display' hangs across the browser-test jobs). Redirect Xvfb's output to /dev/null and disown it so the step's pipe closes and the step completes immediately.
The redirect/disown alone did not stop the hang: the real culprit is
'apt-get update && apt-get install -y xvfb', which intermittently blocks on the
runner's dpkg/apt lock (apt-daily / unattended-upgrades). xvfb is already
preinstalled on the GitHub Ubuntu runners ('xvfb is already the newest
version'), so the install is pure risk. Just start the preinstalled Xvfb; if it
were ever absent the step fails fast instead of hanging.
CI verified — systemic flakiness fixedFull run + targeted re-runs of the flaky jobs. The two structural causes of the cross-PR flakiness are resolved:
Remaining reds are pre-existing flakes, not regressionsOn re-run, the DCC and Main Dash failures ( Recommend merging this PR (it strictly improves CI) and tracking the async-callback test failure + the racy per-test flakes in a separate issue. |
test_(async_)cbsc001/cbsc008 assert an exact one-callback-per-keystroke count, but the renderer coalesces same-identity callbacks still queued in its 'requested' state (requestedCallbacks.ts) into a single request. Two keystrokes landing in that batching window collapse into one invocation, so the count undershoots. The Lock choreography the tests used to serialize typing no longer holds now that async callbacks execute concurrently, and React 19's more aggressive event batching plus faster Chrome typing pushed the failure rate to ~90% locally — routinely exhausting the flaky retries. Gate each keystroke on the previous callback having executed (wait until the counter reflects it) so a keystroke's callback always leaves the 'requested' queue before the next is sent and can never be coalesced. This makes the exact-count assertion correct by construction; drop the Lock, the per-keystroke sleeps, and the @flaky retries.
…meout Two changes so a stuck test/server can no longer hang a whole CI step (the 'Run Async Callback Tests' step was wedging for the full job timeout): - ThreadedRunner.stop() Flask path called self.thread.join() with no timeout. If the injected SystemExit fails to unwind a worker stuck in a C call, that join blocks teardown forever. Bound it with stop_timeout (FastAPI and Quart paths already join with a timeout); the following until_not then fails fast instead of hanging. - Add pytest-timeout (requirements/ci.txt, installed via the [ci] extra in every test job) and set a 180s per-test cap in pytest.ini. Any remaining hang now fails with a full thread stack dump naming the test, instead of stalling the step until the job-level timeout.
✅ Full green runRun 32283425251 passed with zero failed jobs after the anti-hang + gating fixes. The two systemic problems are gone:
Remaining occasional reds ( Optional follow-up (not in this PR): add |
test_tdrp004_navigate_selected_cells read the derived-prop display cells with one-shot find_element().get_attribute() while keystrokes were still firing. props_container re-renders wholesale on every table-prop change, so the element went stale between find and read, failing Table Group 1 consistently once selenium was unpinned. Add a wait_prop() helper that re-finds the element each poll and waits for the value to settle, and use it for the tab-navigation assertions.
|



Problem
Recent pushes and PRs have been going red intermittently across the browser-based integration tests. The failures are scattered across unrelated Selenium tests (
test_persistence,test_csp,test_multi_output,test_derived_props, async callbacks, table server tests…), a different subset each run, all surfacing asStaleElementReferenceException/TimeoutException. That pattern is environmental flakiness, not a bad merge (which would fail the same test deterministically).Root cause
requirements/testing.txtpinnedselenium>=3.141.0,<=4.2.0(selenium 4.2.0 is from 2022).browser-actions/setup-chrome@v1withchrome-version: stable— unpinned — so CI now installs Chrome 151.Two amplifiers: the dependabot pip bump that would have raised selenium never landed on
dev, and the recent React 18/19 test matrix roughly doubled the browser shards, so a single flake reddens the whole run more often.Fix
Bump the pin to
selenium>=4.11.0,<=4.46.0. The>=4.11.0floor guarantees a mature Selenium Manager that auto-provisions a chromedriver matching whatever stable Chrome CI installs (this is whyinstall-chromedriver: falsein the setup step remains correct).Compatibility checks
find_element_by_*APIs anywhere indash/(those were dropped in selenium 4.3).webdriver.Chrome(options=...)/webdriver.Remote(command_executor=..., options=...).dash/testing/browser.pyuses, plus Selenium Manager availability, against selenium 4.46.0.Follow-ups (not in this PR)
test_async_cbsc001_simple_callback) and may be genuinely broken rather than flaky — worth a targeted look once this settles the noise.stablefine either way.