fix(replay): port the engine to browser-use 0.13's CDP actor API (deterministic replay was 100% dead)
Problem
The replay engine is written against Playwright's API, but runs on browser-use 0.13's CDP actor surface, which has none of it. Verified with the pinned version (browser-use==0.13.4):
| Called by the engine | On CDP actor Page/Element |
|---|---|
page.locator / locator.wait_for | ❌ doesn't exist |
page.wait_for_selector / wait_for_load_state | ❌ doesn't exist |
page.check / page.uncheck | ❌ doesn't exist |
page.query_selector_all / el.is_visible() / el.text_content() | ❌ doesn't exist |
locator.click(force=True) | ❌ no force= kwarg |
locator.select_option(label=...) | ❌ values only — option label text is invisible to it |
locator.press | ❌ Element has no press |
page.screenshot(path=...) | ❌ returns base64, no path= |
evaluate treated as returning dict/bool | ❌ always returns a string (JSON-stringified) |
Because nearly every call site wraps these in broad except blocks, the failures are silent: get_best_element_handle exhausts all fallbacks and raises for every selector, so any recorded workflow with a cssSelector step fails on its first element interaction. Radio/checkbox steps only 'succeed' when the state already matches; form-validation detection always returns 'no errors' (rejected submits are reported SUCCESS); XPath fallback never fires; the multi-strategy finder reads node.text/node.aria_label etc. that don't exist on the slotted EnhancedDOMTreeNode, so no semantic strategy can ever match.
Fix (reviewable commit-by-commit)
test(contract)— an import-time API-contract test (workflows/tests/test_browser_use_contract.py, no browser launch):- asserts every Page/Element/Browser attribute the engine relies on exists with a compatible signature (fails at CI time on a breaking browser-use upgrade, instead of silently at replay time)
- a static scan asserting no Playwright-only API call exists outside the compat layer — it failed with 35 real call sites before the port and is green after; it also prevents this bug class from regressing
fix(replay)deterministic path — newworkflow_use/compat/cdp.py(single module that knows the Playwright↔CDP differences: pollingwait_for_element, visibility via geometry+computed style, XPath resolution via a temporary marker attribute, JSON-decoding ofevaluatestring returns, readyState-based load waits, base64 screenshot-to-file, checkbox state setter, select-by-visible-text with properinput/changeevents) + portscontroller/utils.py,controller/service.py,workflow/service.py. Also:DISABLED_DEFAULT_ACTIONSlisted pre-0.2 action names, so only 4 of 27 matched and 16 agent-only builtins stayed registered while customclick/input/scrollsilently overwrote builtins — replaced with excluding all builtins by their real names + explicit registrationsgo_back/go_forwardsteps built an emptyActionModel(), whichTools.actno-ops while reporting success — now real CDP navigations- dropped Playwright-only
:has-text()/:visibleselectors (invalid CSS that throws inquerySelectorAll)
fix(replay)semantic executor / validation / element finder — same treatment forsemantic_executor.py(11page.checksites, 5wait_for_selectorsites, select-by-visible-text, AI extraction calledainvoke(str)and read.contentwhile the API takes a message list and exposes.completion),validation_utils.py(rewritten as one page-evaluate pass), andelement_finder.py(reads the real node sources:attributesdict,ax_node,get_all_children_text()).
Verification
cd workflows && uv run pytest tests/test_browser_use_contract.py -q # 14 passed
Related: #165 (recording-side findings), #166 (recording pipeline fixes — independent; both PRs apply cleanly in either order aside from trivial context overlap).
🤖 Generated with Claude Code
Summary by cubic
Ports the replay engine from Playwright APIs to the browser-use 0.13 CDP actor surface, restoring deterministic replay. Adds a CDP compat layer and contract tests so API breaks fail fast instead of silently at runtime.
-
Bug Fixes
- Replaced Playwright-only calls with CDP equivalents: selector waits, visibility checks, XPath resolution, evaluate return decoding, load-state waits, screenshots, key press, checkbox/radio handling, and select-by-visible-text.
- Controller now excludes all built-in agent actions and registers deterministic actions explicitly; implements real back/forward navigation (fixes prior no-op success).
- Element finder reads real
EnhancedDOMTreeNodesources (attributes/AX/text, including nested{'attributes': {...}}), uses CDP-safe text/XPath strategies with per-lookup marker tokens, and a page-side text finder that scans renderedinnerTextand polls until timeout; removed:has-text()/:visible. - Validation reworked to collect visible error texts in one pass; prevents false “success” after rejected submits.
- Error reporting uses
get_url/get_titleand writes base64 screenshots; XPath click path now decodes evaluate JSON before inspecting; checkbox state handling is centralized viacdp.set_checkbox_state.
-
New Features
- New
workflow_use/compat/cdp.pyshims:wait_for_element, visibility via geometry+style, XPath resolution with tokenized markers, readyState-based load waits, base64 screenshot-to-file, checkbox state setter, select-by-visible-text, and focused key press. - Import-time contract tests assert
Page/Element/BrowserAPI surface and statically block Playwright-only calls to guardbrowser-useupgrades. - Dev setup: add
pytestfor the contract test; removepytest-asyncioand pytest config. Run the gate with:uv run pytest tests/test_browser_use_contract.py.
- New