Add click_element utility for robust element clicking in tests - #6968
Add click_element utility for robust element clicking in tests#6968masenf wants to merge 1 commit into
Conversation
`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
Greptile SummaryAdds a Selenium click helper that repeatedly re-locates elements to tolerate DOM replacement during client-side navigation.
Confidence Score: 4/5The 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
|
| 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() |
There was a problem hiding this comment.
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.
Merging this PR will not alter performance
Comparing Footnotes
|
There was a problem hiding this comment.
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: |
There was a problem hiding this comment.
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() |
There was a problem hiding this comment.
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>
Type of change
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 usesAppHarness._poll_for()to retry until the click succeeds or timeout is reached.Changes
tests/integration/utils.pyTimeoutTypefromreflex.testingclick_element(driver, by, value, timeout)function that:TimeoutErrorwith context if click fails within timeouttests/integration/test_dynamic_routes.pyclick_elementutilityfind_element().click()patterns withclick_element()callslinkvariable assignmentsTesting
test_dynamic_routes.pynow use the more robust clicking mechanismChecklist
https://claude.ai/code/session_01W7hxgQtxcFzZC795xyftVS