Skip to content

fix(click): report clicks on disabled form controls as errors - #5965

Open
yotambraun wants to merge 5 commits into
browser-use:mainfrom
yotambraun:fix/click-disabled-elements
Open

yotambraun wants to merge 5 commits into
browser-use:mainfrom
yotambraun:fix/click-disabled-elements

Conversation

@yotambraun

@yotambraun yotambraun commented Oct 1, 2026 •

Copy link
Copy Markdown

Fixes #5964

Problem

A disabled form control that has a click listener is offered to the agent, and click on it returns Clicked button "..." with no error, although the browser never dispatches the click. is_interactive() returns early for elements with a click listener, before its disabled check, so these controls get an index. This includes framework buttons: a disabled Vue 3 button with @click and a disabled React 18 button with onClick are both offered and reported as clicked. With error unset, multi_act() keeps running the queued actions (it stops on error), a single-action step is not counted toward max_failures, and the model is told the click worked.

Change

on_ClickElementEvent now checks the element's live :disabled state (which includes a disabled <fieldset>) and returns a validation_error, the same way file inputs and <select> are already handled, before the print-button special case, so click returns ActionResult(error=...). on_ClickCoordinateEvent runs the same check next to its <select> and file-input checks (skipped with force=True, like those). Only elements that can match :disabled (form controls and form-associated custom elements) are checked, so other clicks make no extra CDP calls:

Cannot click element (index=3): it is disabled. Complete whatever enables it first (a required field, a checkbox or a pending check), or wait a moment and try again.

Deliberate limits:

  • The state is read live through CDP, not from the DOM snapshot. The snapshot still says disabled after the page enables the control, so a snapshot check would block the type-then-click flow from multi_act() clicks wrong element when typing enables a previously-disabled button #4518. A test covers this.
  • aria-disabled is not blocked: the browser still delivers those clicks and some pages react to them.
  • If the node cannot be resolved (for example the page re-rendered it), the check returns False and the click proceeds as before.
  • The click tool currently dispatches coordinate clicks with force=True, so the coordinate-path check is exercised by the test rather than by the agent today; it is there so callers that use force=False get the same answer.

Which elements are offered to the agent is unchanged.

Before and after

Calling click on <button disabled> with a click listener (reproduction in #5964):

error extracted_content handler ran
main None Clicked button "Place order" id=place no
this PR Cannot click element (index=3): it is disabled. ... None no

The same before/after holds for a disabled Vue 3 (@click) and React 18 (onClick) button.

Demo

click on a disabled button: main vs this PR

Captured from both builds on a real page (script and raw results in the evidence link below).

Tests

New tests/ci/test_click_disabled_element.py (real headless Chromium, local pages):

  • disabled button, button inside a disabled fieldset, and a disabled print button (onclick="window.print()"): error mentions disabled and the handler did not run. On main these return error=None with Clicked button ..., and for the print button a PDF is also generated.
  • coordinate click (ClickCoordinateEvent, force=False) on a disabled and an enabled button: validation_error vs clicked. The disabled case fails on main.
  • enabled button and aria-disabled element: clicked as before.
  • button disabled at snapshot time, enabled by typing into a field before the click: clicked.
  • one multi_act step with input, click on the still-disabled button, input: the queue stops at the click and the second field stays empty. On main all three actions run.

Checked locally:

  • uv run pytest tests/ci/test_click_disabled_element.py: 9 passed (5 of them fail on main)
  • full tests/ci (--numprocesses 4): 1222 passed, 39 skipped
  • all pre-commit hooks (ruff, pyright, codespell, pyupgrade, ...) on the changed files: pass

Agent runs

To see whether this changes normal agent behavior, I ran browser_use.Agent (use_vision=False, max_steps=12) with gpt-4.1-mini, gpt-5.4-mini and gpt-5.6-luna against local checkout pages, on main and on this branch. An order counted only if the page's POST reached the test server. 60 runs in total.

  • Requirement visible on the page (terms checkbox, confirm-email field), 36 runs: every run placed the order on both builds with the same number of actions, and no click on this branch hit a disabled control.
  • Button enabled asynchronously 1.5 s after typing a username, 24 runs: on this branch, 7 of 12 runs clicked while the button was still disabled and got the new error; on main that click returns Clicked button "Create account". Orders 11/12 here vs 10/12 on main, and fewer repeated identical actions (11 vs 18). These samples are small, so I am not claiming an outcome difference from them.

The protocol (written before the runs), scripts, per-step traces, token usage and the deterministic probes are in https://github.com/yotambraun/browser-use/tree/69c69ad6abdc474a2475d6cb10449571b5d49fac (its README has a summary table and reproduction steps).

@CLAassistant

CLAassistant commented Oct 1, 2026 •

Copy link
Copy Markdown

CLA assistant check
All committers have signed the CLA.

@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.

All reported issues were addressed across 2 files

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

Fix all with cubic | Re-trigger cubic

Comment thread browser_use/browser/watchdogs/default_action_watchdog.py Outdated
Comment thread browser_use/browser/watchdogs/default_action_watchdog.py Outdated

@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.

All reported issues were addressed across 2 files (changes from recent commits).

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

Fix all with cubic | Re-trigger cubic

Comment thread browser_use/browser/watchdogs/default_action_watchdog.py Outdated
Comment thread tests/ci/test_click_disabled_element.py Outdated
Disabled form controls that have a click listener are offered to the agent, and click
returned "Clicked ..." with no error although the browser never dispatches the click.
Check the element's live :disabled state (including a disabled fieldset) and return a
validation_error, as is already done for <select> and file inputs. The state is read
live because the DOM snapshot can be stale after the page enables the control.
aria-disabled is not blocked: the browser still delivers those clicks.
…on-form elements

Add the disabled check to on_ClickCoordinateEvent next to the <select> and file-input
checks (it is skipped with force=True, like the others), and return early from
_is_element_disabled unless the element can match :disabled (form controls and
form-associated custom elements), so other clicks make no extra CDP calls.
Move the index-path check from _click_element_node_impl into on_ClickElementEvent,
before the print-button handling, so a disabled print control is reported instead of
generating a PDF. Add a disabled print button test and make the test helper fail
loudly when it cannot locate an element.
… actions

input + click on a still-disabled button + input in one step: the queue now stops at
the click; on main all three actions run and the second field is typed.
Controls are often enabled a moment after input (debounced checks). In agent runs the
models that waited and retried after the error succeeded; the one that did not tried to
force the control with page scripts instead. Say so in the message.

This branch has not been deployed

No deployments
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.

click reports success on a disabled button that has a click listener

2 participants