Skip to content

Add click_element utility for robust element clicking in tests - #6968

Open
masenf wants to merge 1 commit into
mainfrom
claude/flaky-test-on-load-navigate-lw72ae
Open

Add click_element utility for robust element clicking in tests#6968
masenf wants to merge 1 commit into
mainfrom
claude/flaky-test-on-load-navigate-lw72ae

Conversation

@masenf

@masenf masenf commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Type of change

  • New feature (non-breaking change which adds functionality)

Description

This PR adds a new click_element() utility function to the integration test suite that robustly handles clicking elements in client-side navigation scenarios.

Problem: In client-side navigation, the DOM is swapped after the URL changes. When an element is located immediately after navigation, it can become stale before the click is dispatched, causing StaleElementReferenceException.

Solution: The new click_element() function re-locates the element on each click attempt, ensuring it clicks whichever node is currently rendered. It uses AppHarness._poll_for() to retry until the click succeeds or timeout is reached.

Changes

  1. tests/integration/utils.py

    • Import TimeoutType from reflex.testing
    • Add click_element(driver, by, value, timeout) function that:
      • Locates and clicks an element, retrying on exceptions
      • Re-locates on each attempt to avoid stale element references
      • Raises TimeoutError with context if click fails within timeout
  2. tests/integration/test_dynamic_routes.py

    • Import the new click_element utility
    • Replace 8 instances of manual find_element().click() patterns with click_element() calls
    • Removes unnecessary intermediate link variable assignments

Testing

  • Existing integration tests in test_dynamic_routes.py now use the more robust clicking mechanism
  • No new test files added; the change is validated by existing test suite execution
  • All affected test cases continue to pass with improved reliability

Checklist

  • Changes follow the guidelines in CONTRIBUTING.md
  • Code is linted and formatted
  • Docstring follows Google style with Args/Returns/Raises sections
  • Existing tests pass with the changes

https://claude.ai/code/session_01W7hxgQtxcFzZC795xyftVS

Review in cubic

`poll_for_navigation` returns as soon as the URL changes, but the client
side router swaps the route component after that, replacing the DOM
nodes. Since index and /static/x render the same component, a link
located right after navigating back to index could go stale before the
click was dispatched, raising StaleElementReferenceException.

Add a `click_element` helper that re-locates the element on every attempt
and use it for the link clicks in the dynamic route navigation tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01W7hxgQtxcFzZC795xyftVS
@masenf
masenf requested a review from a team as a code owner August 28, 2026 08:30
@greptile-apps

greptile-apps Bot commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

Adds a Selenium click helper that repeatedly re-locates elements to tolerate DOM replacement during client-side navigation.

  • Replaces direct clicks in dynamic-route integration tests with the new retrying helper.
  • Reports the last click exception when polling expires.
  • The helper's polling deadline does not bound WebDriver calls when a longer implicit wait is configured.

Confidence Score: 4/5

The PR appears safe to merge, with a non-blocking timeout issue that can make missing-element failures substantially slower than requested.

The click retry behavior is compatible with the changed navigation tests, but WebDriver's configured 30-second implicit wait can overrun the helper's nominal 15-second deadline because polling cannot interrupt an in-progress element lookup.

Files Needing Attention: tests/integration/utils.py

Important Files Changed

Filename Overview
tests/integration/utils.py Adds the retrying click helper; its synchronous element lookup can exceed the requested polling timeout under an implicit wait.
tests/integration/test_dynamic_routes.py Migrates dynamic-route navigation clicks to the helper without changing their locators or asserted destinations.

Reviews (1): Last reviewed commit: "Fix stale element flake in test_on_load_..." | Re-trigger Greptile

def _click() -> bool:
nonlocal last_exc
try:
driver.find_element(by, value).click()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Implicit wait overruns timeout

When WebDriver's implicit wait exceeds click_element's timeout, the synchronous find_element call can run past the polling deadline. In this test fixture, a missing element can therefore take roughly 30 seconds or longer to fail despite the helper's nominal 15-second timeout, and explicit shorter timeout values are ineffective.

@codspeed-hq

codspeed-hq Bot commented Aug 28, 2026

Copy link
Copy Markdown

Merging this PR will not alter performance

✅ 27 untouched benchmarks
⏩ 8 skipped benchmarks1


Comparing claude/flaky-test-on-load-navigate-lw72ae (8ed21c5) with main (f7c848f)2

Open in CodSpeed

Footnotes

  1. 8 benchmarks were skipped, so the baseline results were used instead. If they were deleted from the codebase, click here and archive them to remove them from the performance reports.

  2. No successful run was found on main (45b8ed5) during the generation of this report, so f7c848f was used instead as the comparison base. There might be some changes unrelated to this pull request in this report.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="tests/integration/utils.py">

<violation number="1" location="tests/integration/utils.py:92">
P2: The retry re-clicks on any exception, so a click that fired but then raised during the client-side DOM swap (the exact scenario this helper targets) gets re-located and clicked again, potentially triggering a second navigation or double event. Only retry on transient/stale errors such as StaleElementReferenceException, NoSuchElementException and ElementClickInterceptedException instead of all Exception, so a permanent failure fails fast and a click that already landed is not re-fired.</violation>

<violation number="2" location="tests/integration/utils.py:93">
P2: Temporarily disable the driver's implicit wait while running this polling loop. Otherwise `find_element` can block for the session's implicit wait, allowing a short `timeout` to overrun by roughly 30 seconds when the element is missing.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


def _click() -> bool:
nonlocal last_exc
try:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The retry re-clicks on any exception, so a click that fired but then raised during the client-side DOM swap (the exact scenario this helper targets) gets re-located and clicked again, potentially triggering a second navigation or double event. Only retry on transient/stale errors such as StaleElementReferenceException, NoSuchElementException and ElementClickInterceptedException instead of all Exception, so a permanent failure fails fast and a click that already landed is not re-fired.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/integration/utils.py, line 92:

<comment>The retry re-clicks on any exception, so a click that fired but then raised during the client-side DOM swap (the exact scenario this helper targets) gets re-located and clicked again, potentially triggering a second navigation or double event. Only retry on transient/stale errors such as StaleElementReferenceException, NoSuchElementException and ElementClickInterceptedException instead of all Exception, so a permanent failure fails fast and a click that already landed is not re-fired.</comment>

<file context>
@@ -64,6 +64,43 @@ def poll_for_navigation(
+
+    def _click() -> bool:
+        nonlocal last_exc
+        try:
+            driver.find_element(by, value).click()
+        except Exception as exc:
</file context>

def _click() -> bool:
nonlocal last_exc
try:
driver.find_element(by, value).click()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Temporarily disable the driver's implicit wait while running this polling loop. Otherwise find_element can block for the session's implicit wait, allowing a short timeout to overrun by roughly 30 seconds when the element is missing.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At tests/integration/utils.py, line 93:

<comment>Temporarily disable the driver's implicit wait while running this polling loop. Otherwise `find_element` can block for the session's implicit wait, allowing a short `timeout` to overrun by roughly 30 seconds when the element is missing.</comment>

<file context>
@@ -64,6 +64,43 @@ def poll_for_navigation(
+    def _click() -> bool:
+        nonlocal last_exc
+        try:
+            driver.find_element(by, value).click()
+        except Exception as exc:
+            last_exc = exc
</file context>

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants