Skip to content

[Fix] Tell the model which element blocked its click - #9

Closed
sirotaoleg wants to merge 1 commit into
ThinkFlowLab:jiuwen-jevfrom
sirotaoleg:pr/browser-explain-intercepted-clicks
Closed

sirotaoleg wants to merge 1 commit into
ThinkFlowLab:jiuwen-jevfrom
sirotaoleg:pr/browser-explain-intercepted-clicks

Conversation

@sirotaoleg

Copy link
Copy Markdown

Problem. When a click lands on an element covered by something else (a cookie dialog, a modal, an icon over its own input), Playwright MCP returns its full retry log: 1.9–5.6 KB of ANSI-coloured noise, in which the actual blocker is one line.

  • In 90 WebVoyager tasks, 12 hit this (18 failures, each after a 5 s timeout).
  • The model needed 4.5 steps on average (worst 8–9) to get from the failed click to a working one.
  • Typical blockers: OneTrust consent dialogs (Coursera, Cambridge, Allrecipes), the Amazon redirect modal, a Google overlay.

Fix. A new InterceptedActionHintRail on the worker (after_tool_call, the documented place for rewriting tool_msg, S_05 invariant 11) turns that log into a short message: what was clicked, what covers it, and what to do. For consent dialogs that means "close it first, prefer Reject all / Close". Example (2.4 KB → 394 chars):

Click not delivered: button[data-testid="filter-and-sort-button"] is covered by
div.onetrust-pc-dark-filter (inside div#onetrust-consent-sdk).
This is a cookie-consent dialog: close it first (prefer 'Reject all' / 'Close') ...
Then retry the action (take a fresh browser_snapshot if the page changed).

Details:

  • Only the model-facing message changes. The structured tool result that progress/failure tracking reads stays raw.
  • Content that doesn't mention an intercepted action is left byte-identical.

Result. Steps from the failed click to recovery: 3.8 → 2.7 on the same tasks.

Tests. test_browser_intercepted_action_hint.py:

  • log parsing on real fixtures (consent, modal, icon-over-input);
  • {"result": ...} envelope kept;
  • idempotent;
  • structured result untouched;
  • rail registered.

When a Playwright MCP action fails because another element sits on top
of the target, the worker model got the full Playwright call log: 1.9-5.6
KB of ANSI-coloured retry noise in which the blocker is one line. On
WebVoyager (@playwright/mcp 0.0.78, agent-core 18b840c) 12 of 90 tasks
hit "element intercepts pointer events" (18 failures, each after a 5 s
timeout), and it took 4.5 steps on average (54 in total, worst 8-9) to
get from the failed click to the next successful one. Blockers were the
OneTrust consent dialog (Coursera, Cambridge, Allrecipes), Amazon's
redirect modal, Amazon's radio icon over its own input, and a Google
overlay.

Add InterceptedActionHintRail, registered on the Playwright worker by
BrowserService.ensure_started(). Its after_tool_call parses the call log
(target from the last "locator resolved to", blocker and container from
the last "... intercepts pointer events" line) and replaces only the
model-facing ToolMessage content with a short error, keeping the
{"result": ...} envelope and the "### Error" marker:

  before (2.4 KB, shortened):
    {"result": "### Error\nTimeoutError: browserBackend.callTool: Timeout
    5000ms exceeded.\nCall log:\n  \u001b[2m- waiting for locator(...)
    ... - <div class=\"onetrust-pc-dark-filter ot-fade-in\"></div> from
    <div ... id=\"onetrust-consent-sdk\">…</div> subtree intercepts
    pointer events ... - retrying click action ... (x24)"}
  after (394 chars):
    ### Error
    Click not delivered: button.css-bnpmeq[type="button"][data-testid=
    "filter-and-sort-button"] is covered by div.onetrust-pc-dark-filter
    (inside div#onetrust-consent-sdk).
    This is a cookie-consent dialog: close it first (prefer 'Reject all'
    / 'Close'; if its settings panel is open, confirm or close that
    panel).
    Then retry the action (take a fresh browser_snapshot if the page
    changed).

Non-consent blockers get a generic hint (dismiss the overlay, or click
the control's visible label when an icon covers its input). Detection is
by content, so every action tool (click, hover, type, fill_form,
select_option, drag, ...) is covered. Content that does not mention an
intercepted action, or whose call log cannot be parsed, is left
byte-identical.

A rail rather than a wrapper around ability_manager.execute: S_05 (tools
contract, invariant 11) makes AFTER_TOOL_CALL the place that rewrites
tool_msg, and resolve_tool_message/rendered_result read that message,
so what is streamed and what the model reads stay the same text. The
structured tool_result is untouched, so tool_result_observer (progress
and failure tracking) still receives the raw result.

Out of scope: auto-dismissing consent banners, a blocked_by field in the
probe tools, the Amazon geo redirect, and porting to the refactor
layout (browser_move/runtime/..., legacy MCP path).
@sirotaoleg

Copy link
Copy Markdown
Author

Superseded by #12

@sirotaoleg sirotaoleg closed this Oct 15, 2026
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.

1 participant