Skip to content

click_element races Bootstrap toasts, so any click within 5s of a save is a coin flip #17308

Description

@jaredlockhart

Problem

Base.click_element waits for EC.element_to_be_clickable and then clicks:

def click_element(self, locator):
    el = self._wait_clickable(locator)
    el.click()

element_to_be_clickable checks that the element is visible and enabled, not that it is unobscured. The app renders Bootstrap toasts into a fixed #toasts-container after saves, initialised as new bootstrap.Toast(toastEl) with no options, so they inherit the defaults autohide: true, delay: 5000 and float over the page for five seconds. Any click landing in that window passes the wait and then throws:

selenium.common.exceptions.ElementClickInterceptedException: Element
<button class="btn p-0 border-0 text-secondary" type="button"> is not clickable
at point (1297,640) because another element <div class="toast-body"> obscures it

There is no toast handling anywhere in the integration suite: git grep -i toast experimenter/tests/integration returns nothing. click_element is the shared click path for the whole suite, so this is not specific to one test.

Observed on the Desktop Nimbus UI (Release Firefox) job taking a retry attempt on test_rollouts_ui.py::test_rollout_can_be_launched[FIREFOX_DESKTOP], raised from pages/base.py click_element via pages/rollouts/form_base.py edit. The earlier CI profiling work counted 39 of these in a single bad run.

Proposed change

In experimenter/tests/integration/nimbus/pages/base.py, retry the click when it is intercepted, and dismiss any visible toast before retrying so the retry does not have to wait out the full 5 second autohide:

  • add ElementClickInterceptedException to the selenium.common.exceptions import
  • add a _dismiss_toasts helper that removes the show class from .toast.show via JS, wrapped in contextlib.suppress (already imported)
  • rewrite click_element to attempt the click inside a short bounded WebDriverWait, dismissing toasts and returning False on ElementClickInterceptedException

The dismissal only runs after a real interception, so it never speculatively mutates page state, and it only touches .toast. No test asserts on toasts, so nothing loses coverage. A permanently obscured element still fails once the bounded wait expires, so this does not mask a genuine regression where something legitimately covers a control.

Out of scope

There are roughly 17 raw .click() call sites in page objects that bypass click_element. Those keep the current behaviour. This change covers the shared path, which is where the failure has actually been observed.

Acceptance criteria

  • An intercepted click retries rather than failing immediately, bounded so a permanent overlay still fails.
  • Visible toasts are dismissed only on the retry path, never pre-emptively.
  • Desktop Nimbus UI stops failing with ElementClickInterceptedException naming toast-body, confirmed by sampling job logs on main after merge.

┆Issue is synchronized with this Jira Task

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Fields

Priority

None yet

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions