Bug: user-agent shadow roots are reported as SHADOW(open) in the serialized page state
Browser Use Version
d8110c5ff87ccba887aaa726cdb780f2f84bef8d
Bug Description, Steps to Reproduce, Screenshots
CDP defines three shadow root types — user-agent, open, closed — but the code tests only for closed and treats everything else as open. Chrome's internal shadow roots for native form controls are therefore reported to the agent as author-written open shadow DOM.
Four call sites share the pattern:
browser_use/agent/prompts.py:197-206 the shadow(open)/shadow(closed) counts
browser_use/dom/serializer/serializer.py:1020 |SHADOW(...)| prefix
browser_use/dom/serializer/serializer.py:1105 |SHADOW(...)| prefix, second path
browser_use/dom/serializer/serializer.py:1146 "Open Shadow" / "Closed Shadow" text lines
Each is a variant of:
has_closed_shadow = ... shadow_root_type.lower() == 'closed'
shadow_prefix = '|SHADOW(closed)|' if has_closed_shadow else '|SHADOW(open)|'
Reproduction
No LLM or API key needed. Serve this over HTTP:
<select id="many"><option>one</option><option>two</option></select>
<button aria-label="Save">Icon</button>
<div role="button">Custom button</div>
<input value="prefilled">
Run python -m browser_use.dom.playground.extraction against it. From tmp/user_message.txt:
<page_stats>0 links, 4 interactive, 0 iframes, 2 shadow(open), 0 shadow(closed), 8 total elements</page_stats>
|SHADOW(open)|*[2]<select id=many ... />
|SHADOW(open)|[3]<input value=prefilled />
The page contains no author shadow DOM. In DevTools, document.querySelector('select').shadowRoot and document.querySelector('input').shadowRoot both return null.
The playground's own dump shows what CDP actually reported:
$ grep -o '"shadow_root_type": "[^"]*"' tmp/original_element_tree.json | sort | uniq -c
8 "shadow_root_type": "user-agent"
Related
#4649 reported the volume of Open Shadow / Shadow End pairs in state output on sites like Reddit. Part of that volume is user-agent roots being labelled as open shadow DOM.
Which behaviour do you want?
- Report
user-agentas its own category in the counts and markers - Exclude user-agent shadow roots from the serialized tree — they're browser internals the agent can't act on and no page author wrote them
- Keep the counts but drop the
|SHADOW(...)|marker andOpen Shadowlines for them
I'd lean toward 2, but it changes what every agent sees on any page with a native form control, so I'd rather ask than guess. Happy to send a PR once you've picked.
Failing Python Code
# None - reproduced with `python -m browser_use.dom.playground.extraction`
# against the HTML above. No agent or LLM involved.
LLM Model
No response
Operating System & Browser Versions
No response
Full DEBUG Log Output
No error or traceback - the bug is incorrect output, not a failure.
$ grep -o '"shadow_root_type": "[^"]*"' tmp/original_element_tree.json | sort | uniq -c
8 "shadow_root_type": "user-agent"
$ head -3 tmp/user_message.txt # the serialized state the agent receives
<page_stats>0 links, 4 interactive, 0 iframes, 2 shadow(open), 0 shadow(closed), 8 total elements</page_stats>
|SHADOW(open)|*[2]<select id=many ... />
|SHADOW(open)|[3]<input value=prefilled />
Source: browser-use/browser-use