Skip to content

feat(element): add timeout parameter to click() and click_using_js()#400

Open
gab-i-alves wants to merge 6 commits into
autoscrape-labs:mainfrom
gab-i-alves:feat/click-timeout
Open

feat(element): add timeout parameter to click() and click_using_js()#400
gab-i-alves wants to merge 6 commits into
autoscrape-labs:mainfrom
gab-i-alves:feat/click-timeout

Conversation

@gab-i-alves

@gab-i-alves gab-i-alves commented Apr 12, 2026

Copy link
Copy Markdown

Description

Add an optional timeout parameter to click() and click_using_js() on WebElement.

When an element is not immediately visible, callers can now pass timeout=N to wait up to N seconds
for the element to become visible before clicking. Internally delegates to the existing
wait_until(is_visible=True) method, keeping the implementation DRY.

Default timeout=0 preserves current behavior (raises ElementNotVisible immediately).

Related Issue(s)

Closes #274

Type of Change

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

How Has This Been Tested?

# Wait up to 5s for element to become visible, then click
button = await tab.find(id='submit')
await button.click(timeout=5)

# Same for JS click
await button.click_using_js(timeout=5)

# Without timeout — current behavior unchanged
await button.click()  # raises ElementNotVisible immediately if hidden

Testing Checklist

  • Unit tests added/updated
  • All existing tests pass

Implementation Details

  • Reuses wait_until(is_visible=True, timeout=N) which already implements the polling loop
    with 0.5s intervals and raises WaitElementTimeout on expiry.
  • 5 new unit tests covering: wait success, timeout expiry, and already-visible edge case
    for both click() and click_using_js().

API Changes

  • click(): new keyword argument timeout: int = 0
  • click_using_js(): new keyword argument timeout: int = 0
  • Non-breaking: default value preserves existing behavior.

Checklist before requesting a review

  • My code follows the style guidelines of this project
  • I have performed a self-review of my code
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have run poetry run task lint and fixed any issues
  • My commits follow the conventional commits style

Summary by CodeRabbit

  • New Features

    • Added an optional timeout parameter to both standard and JavaScript-based element clicks.
    • Clicks now wait for an element to become visible before attempting interaction.
    • Elements are automatically scrolled into view before clicking.
  • Bug Fixes

    • Improved behavior when timeout=0: visibility is checked once and fails immediately if not met.
    • Negative timeout values now raise a ValueError.
    • Hidden-element click failures now raise WaitElementTimeout (instead of prior visibility-specific errors).
  • Tests

    • Updated unit and integration tests to cover the new timeout and visibility behavior.

@coderabbitai

coderabbitai Bot commented Apr 12, 2026

Copy link
Copy Markdown

Caution

Review failed

An error occurred during the review process. Please try again later.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/test_web_element.py (1)

730-757: Add parity test for “already visible + timeout” on click_using_js.

You already validate this optimization for click(...) (Line 839+). Adding the same assertion for click_using_js(...) would prevent future regressions in the JS path.

✅ Optional test addition
+    `@pytest.mark.asyncio`
+    async def test_click_using_js_visible_ignores_timeout(self, web_element):
+        """Already-visible element should not wait even when timeout is provided."""
+        web_element.is_visible = AsyncMock(return_value=True)
+        web_element.scroll_into_view = AsyncMock()
+        web_element.wait_until = AsyncMock()
+        web_element.execute_script = AsyncMock(
+            return_value={'result': {'result': {'value': True}}}
+        )
+
+        await web_element.click_using_js(timeout=5)
+
+        web_element.wait_until.assert_not_called()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_web_element.py` around lines 730 - 757, Add a parity test to
ensure click_using_js short-circuits when the element is already visible even if
a timeout is passed: create a test (e.g.
test_click_using_js_visible_with_timeout_does_not_wait) that sets
web_element.is_visible = AsyncMock(return_value=True),
web_element.scroll_into_view = AsyncMock(), web_element.wait_until =
AsyncMock(), web_element.execute_script = AsyncMock(return_value={'result':
{'result': {'value': True}}}), call await web_element.click_using_js(timeout=5),
and assert that web_element.wait_until.assert_not_called() and that
web_element.execute_script (and optionally web_element.scroll_into_view) was
called as expected to mirror the existing click(...) optimization.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@pydoll/elements/web_element.py`:
- Around line 552-555: The code treats any truthy timeout (including negative
numbers) as a wait request; add explicit validation to reject negative timeout
values by checking the timeout parameter before branching: if timeout is not
None and timeout < 0 raise a ValueError with a clear message (e.g., "timeout
must be non-negative"); then keep the existing logic that calls wait_until when
timeout > 0 and raises ElementNotVisible() when timeout is zero/None. Apply this
validation in both places where timeout is checked (the branch that calls
wait_until and the similar block around lines 597-600) and reference the
wait_until call, the timeout parameter, and ElementNotVisible in those methods.

---

Nitpick comments:
In `@tests/test_web_element.py`:
- Around line 730-757: Add a parity test to ensure click_using_js short-circuits
when the element is already visible even if a timeout is passed: create a test
(e.g. test_click_using_js_visible_with_timeout_does_not_wait) that sets
web_element.is_visible = AsyncMock(return_value=True),
web_element.scroll_into_view = AsyncMock(), web_element.wait_until =
AsyncMock(), web_element.execute_script = AsyncMock(return_value={'result':
{'result': {'value': True}}}), call await web_element.click_using_js(timeout=5),
and assert that web_element.wait_until.assert_not_called() and that
web_element.execute_script (and optionally web_element.scroll_into_view) was
called as expected to mirror the existing click(...) optimization.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9e07631e-832c-4f90-8e39-8e2ef7878c75

📥 Commits

Reviewing files that changed from the base of the PR and between fd27c92 and b58587d.

📒 Files selected for processing (2)
  • pydoll/elements/web_element.py
  • tests/test_web_element.py

Comment thread pydoll/elements/web_element.py Outdated
@codecov

codecov Bot commented Apr 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
tests/test_web_element.py (3)

737-751: Assert post-wait click execution, not only the wait call.

On Line 746 and Line 748, the test proves waiting happened, but not that the click actually proceeded afterward.

Suggested assertion additions
         await web_element.click_using_js(timeout=5)

         web_element.wait_until.assert_called_once_with(
             is_visible=True, timeout=5
         )
+        web_element.scroll_into_view.assert_called_once()
+        web_element.execute_script.assert_called_once()
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_web_element.py` around lines 737 - 751, The test
test_click_using_js_not_visible_with_timeout_waits currently only asserts
web_element.wait_until was called; update it to also assert the JavaScript click
actually ran after the wait by asserting web_element.execute_script (or the
method that performs the JS click used by click_using_js) was called once with
the expected script/arguments after invoking
web_element.click_using_js(timeout=5); reference the test function name
test_click_using_js_not_visible_with_timeout_waits and the mocks
web_element.wait_until and web_element.execute_script to add the post-wait
assertion.

835-854: Strengthen this test to verify the click path runs after waiting.

On Line 849 and Line 851, the wait contract is checked, but the actual click execution is not asserted.

Suggested assertion additions
         with patch('asyncio.sleep'):
             await web_element.click(timeout=5)

         web_element.wait_until.assert_called_once_with(
             is_visible=True, timeout=5
         )
+        web_element.scroll_into_view.assert_called_once()
+        assert web_element._connection_handler.execute_command.call_count == 3
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_web_element.py` around lines 835 - 854, The test
test_click_not_visible_with_timeout_waits should also assert that the click path
runs after waiting: after awaiting web_element.click(timeout=5) verify that
web_element.wait_until was called (already present) and additionally assert that
web_element.scroll_into_view was called and that
web_element._connection_handler.execute_command was invoked for the click action
(e.g., verify execute_command.call_count increased and/or that one of the calls
contains the expected click-related payload). Update the test to check
scroll_into_view.assert_called_once() and inspect execute_command.mock_calls to
confirm a click command was issued.

867-884: Add one execution assertion for the “visible ignores timeout” path.

Line 883 validates the no-wait branch, but adding a click-path assertion would prevent false positives.

Suggested assertion additions
         with patch('asyncio.sleep'):
             await web_element.click(timeout=5)

         web_element.wait_until.assert_not_called()
+        web_element.scroll_into_view.assert_called_once()
+        assert web_element._connection_handler.execute_command.call_count == 3
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/test_web_element.py` around lines 867 - 884, The test
test_click_visible_ignores_timeout currently asserts wait_until was not called
but misses verifying the click execution path; after awaiting
web_element.click(timeout=5) add an assertion that the click invoked the
connection layer (e.g.
web_element._connection_handler.execute_command.assert_called() or a more
specific assert_called_with / assert_has_calls) so the test ensures the click
actually executed via web_element.click and not a no-op.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@tests/test_web_element.py`:
- Around line 737-751: The test
test_click_using_js_not_visible_with_timeout_waits currently only asserts
web_element.wait_until was called; update it to also assert the JavaScript click
actually ran after the wait by asserting web_element.execute_script (or the
method that performs the JS click used by click_using_js) was called once with
the expected script/arguments after invoking
web_element.click_using_js(timeout=5); reference the test function name
test_click_using_js_not_visible_with_timeout_waits and the mocks
web_element.wait_until and web_element.execute_script to add the post-wait
assertion.
- Around line 835-854: The test test_click_not_visible_with_timeout_waits should
also assert that the click path runs after waiting: after awaiting
web_element.click(timeout=5) verify that web_element.wait_until was called
(already present) and additionally assert that web_element.scroll_into_view was
called and that web_element._connection_handler.execute_command was invoked for
the click action (e.g., verify execute_command.call_count increased and/or that
one of the calls contains the expected click-related payload). Update the test
to check scroll_into_view.assert_called_once() and inspect
execute_command.mock_calls to confirm a click command was issued.
- Around line 867-884: The test test_click_visible_ignores_timeout currently
asserts wait_until was not called but misses verifying the click execution path;
after awaiting web_element.click(timeout=5) add an assertion that the click
invoked the connection layer (e.g.
web_element._connection_handler.execute_command.assert_called() or a more
specific assert_called_with / assert_has_calls) so the test ensures the click
actually executed via web_element.click and not a no-op.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9fbaa140-1072-49da-887f-c61d2aa30ba3

📥 Commits

Reviewing files that changed from the base of the PR and between b5f2859 and 945c7ea.

📒 Files selected for processing (1)
  • tests/test_web_element.py

Comment thread pydoll/elements/web_element.py Outdated
Resolve conflict from the tests/ -> tests/unit + tests/integration
reorg and apply review feedback (thalissonvs): use wait_until as the
single visibility gate in click() and click_using_js() instead of the
is_visible()/timeout branching.

- wait_until now fails fast when timeout=0 (checks once, then raises
  WaitElementTimeout) so the default no-wait behavior is preserved.
- the not-visible path now raises WaitElementTimeout (was ElementNotVisible).
- port the click-timeout unit tests to tests/unit/test_web_element.py and
  add real-browser coverage (wait-then-click, timeout expiry) in the
  integration suite with a late-appearing button fixture.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
pydoll/elements/web_element.py (1)

487-487: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Use float for timeout type annotations.

Timeouts in Python are commonly fractional (e.g., timeout=0.5). The type hint int restricts this unnecessarily, while the underlying polling logic (loop.time() - start_time >= timeout) fully supports floats. This aligns with standard asyncio interfaces and WebElement.get_shadow_root, which already uses timeout: float.

  • pydoll/elements/web_element.py#L487-L487: change timeout: int = 0 to timeout: float = 0 in wait_until.
  • pydoll/elements/web_element.py#L531-L531: change timeout: int = 0 to timeout: float = 0 in click_using_js.
  • pydoll/elements/web_element.py#L569-L569: change timeout: int = 0 to timeout: float = 0 in click.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pydoll/elements/web_element.py` at line 487, Update the timeout annotations
in wait_until, click_using_js, and click to use float instead of int while
preserving the default value of 0, enabling fractional timeout values
consistently with get_shadow_root.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pydoll/elements/web_element.py`:
- Around line 554-555: In pydoll/elements/web_element.py at lines 554-555,
update click_using_js to call wait_until(is_visible=True, timeout=timeout)
before scroll_into_view. Apply the same ordering change in click at lines
599-600 so both methods wait for visibility before scrolling.

---

Nitpick comments:
In `@pydoll/elements/web_element.py`:
- Line 487: Update the timeout annotations in wait_until, click_using_js, and
click to use float instead of int while preserving the default value of 0,
enabling fractional timeout values consistently with get_shadow_root.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 1bedd349-e948-4ec3-a125-6f1722a61e0d

📥 Commits

Reviewing files that changed from the base of the PR and between 945c7ea and 97908c8.

📒 Files selected for processing (4)
  • pydoll/elements/web_element.py
  • tests/integration/pages/web_element.html
  • tests/integration/test_web_element_integration.py
  • tests/unit/test_web_element.py

Comment thread pydoll/elements/web_element.py Outdated
The mouse click() path needs the element on-screen: scroll_into_view runs
while the button is still display:none (zero box, no-op), so a button below
the fold never received the dispatched mouse event. Move it into the initial
viewport.
@gab-i-alves
gab-i-alves requested a review from thalissonvs July 17, 2026 03:55
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.

[Feature Request]: Add Timeout Value to click() Events

2 participants