Replace the 6-param pattern (cdp, target_id, selector, registry,
session_id, tab_id) with a TabContext struct created once per command.
Before: element::resolve_node(&cdp, &target_id, &sel, registry, &s, &t)
After: ctx.resolve_node(&sel)
TabContext bundles (cdp, target_id, registry, session, tab) and exposes
resolve_node, resolve_center, resolve_object, scroll_into_view, and
resolve_object_id. Public fields cdp/target_id for direct CDP calls.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P1: Skip CDP DOM.resolveNode for synthetic backendNodeIds (< 0, from AX
nodes without a backing DOM element) — go straight to AX fallback.
P2: Distinguish CDP -32000 (stale node) from other errors (connection
lost, etc.) — only return REF_STALE for true staleness, propagate
other CDP errors through the standard error path.
Fallback: When backendNodeId resolution fails or is synthetic, use
Accessibility.queryAXTree with the stored role + name to find the
element in the current AX tree.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Wire up snapshot refs (@e1, @e5, etc.) so all commands that accept a
<selector> argument can use them. Previously resolve_ref() was a stub
returning UNSUPPORTED_OPERATION.
- Add reverse lookup (ref_to_id) to RefCache for O(1) ref→backendNodeId
- Add peek_ref_cache() to SessionRegistry for read-only access
- Implement resolve_ref: @eN → backendNodeId → objectId → nodeId via CDP
- Thread registry context through resolve_node, resolve_element_center,
and resolve_selector_object (22 call sites updated)
- Error codes: REF_NOT_FOUND, REF_STALE, INVALID_ARGUMENT
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add short alias -v to --version flag
- Add `version` subcommand that calls the same handle_version() logic
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [packages/cli-v2]feat: implement browser storage commands (Batch 9)
Add browser local-storage and session-storage commands (list/get/set/
delete/clear) via Runtime.evaluate on window.localStorage/sessionStorage.
Tab-level addressing (--session + --tab). Shared StorageKind enum drives
JS object selection and response storage field. clear <key> removes a
specific key (action: "clear", affected: 1), matching spec §14.5.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* [packages/cli-v2]fix: correct CDP null detection in storage get
CDP represents JS null as {type:"object",subtype:"null"}, not type:"null".
Update cold-miss check to test for subtype=="null" when type is "object".
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
CDP Runtime.evaluate response nests the value at result.result.value,
not result.value. Wrong pointer caused implicit-domain derivation to always
fall back to None, triggering "domain/url required" CDP error.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All Network.getAllCookies, Network.setCookie, Network.deleteCookies calls
now route via execute_on_tab using the first active tab's target_id instead
of execute_browser (browser-scope). Local Chrome does not expose Network.*
at browser scope — fixes CDP error -32601 on real E2E runs.
Also defaults path to "/" in set.rs when --domain is omitted (§13.3).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
B1: when --domain is omitted, derive domain from window.location.hostname
evaluated on the first active tab; prepend leading dot for subdomain matching.
B2: normalize_domain now lowercases in addition to stripping the leading dot
to handle mixed-case domain comparisons (e.g. Example.COM vs example.com).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add browser.cookies.list/get/set/delete/clear commands with CDP-level
Network.getAllCookies, Network.setCookie, Network.deleteCookies support.
Session-level only (no --tab); list/get are observations, set/delete/clear
are actions. Includes domain filtering, field mapping (httpOnly→http_only,
sameSite→same_site, expires=-1→null), and text/JSON output formatting.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Root cause of B3 false-positive: subscribing to Network events inside
`wait network-idle` only sees requests that start AFTER the wait call.
Requests already in-flight (e.g. fetch/new Image() started before wait)
are permanently missed, so pending stays 0 and the idle quiet window
expires immediately.
Fix — session-level Network tracking:
- `CdpSession` gains `tab_net_pending: Arc<Mutex<HashMap<String, i64>>>`
- `attach()` calls `Network.enable` on the tab immediately after storing
the session ID, before any user command runs
- `reader_loop` maintains `tab_net_pending` from Network domain events:
+1 on requestWillBeSent, -1 on loadingFinished/loadingFailed/
requestServedFromCache (clamped to 0)
- New `network_pending(cdp_session_id) -> i64` returns the live counter
- `detach()` cleans up the counter entry
`network_idle.rs` simplified:
- Removes per-wait event-channel subscription (subscribe-after-request
race is gone by design)
- Polls `cdp.network_pending()` — reflects all requests since attach
- Keeps JS guard (readyState + img.complete) as secondary check
- Quiet window: pending==0 && js_idle held for 500ms
New unit test: test_network_pending_counter verifies counter increments,
decrements on terminal events, and clamps at 0.
Updated test_attach_detach to expect the new Network.enable exchange.
178/178 unit tests pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add event routing infrastructure to CdpSession:
- New `event_subs` field keyed by "{cdp_session_id}:{method}"
- reader_loop now routes CDP events (messages without "id") to subscribers
- `subscribe_events(cdp_session_id, method)` returns mpsc::Receiver<Value>
- `get_cdp_session_id(target_id)` looks up the flat-session ID
- 2 new unit tests: test_event_routing, test_event_not_routed_to_wrong_session
Rewrite network_idle.rs to use CDP Network domain events:
- Subscribe to requestWillBeSent/loadingFinished/loadingFailed/requestServedFromCache
before calling Network.enable (avoids the subscribe-enable race)
- Track in-flight pending count; decrement on terminal events
- Require pending == 0 AND js_idle (readyState + img.complete) for 500ms quiet
window before declaring idle
- Fixes B3 false-positive: JS-only heuristics missed non-DOM fetch/XHR/script
requests; CDP events cover all network traffic
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Performance Resource Timing entries only appear after completion so the
previous responseEnd-only filter missed in-flight requests. Add two more
layers before the Performance API check:
1. document.readyState guard (page must be complete)
2. img.complete check — detects <img> elements still loading
3. responseEnd === 0 entries (W3C Resource Timing Level 2 in-flight)
+ recent entries within 500ms idle window
Any of the three layers returning pending > 0 keeps the wait loop alive.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
B1 wait navigation: use OR instead of AND for url_changed/readyState check.
Same-URL navigations (reload) never change URL — returning on either condition
matches actionbook-rs reference and handles both cases correctly.
B2 wait condition: arrays and objects are always truthy in JS regardless of
whether they are empty. Fix Rust truthiness to use _ => true for both,
matching actionbook-rs reference (waiting.rs:173).
B3 wait network-idle: replace readyState-only check with Performance Resource
Timing API inspection. Count resources with responseEnd < 500ms ago;
network is idle only when that count reaches zero. Matches actionbook-rs
reference (waiting.rs:98-120).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Chrome silently ignores Page.addScriptToEvaluateOnNewDocument unless the
Page domain is enabled on the session. Add Page.enable call (idempotent)
before the script registration in goto.rs to fix B1: page-load logs now
captured after browser goto.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
B1: Register ENSURE_LOG_CAPTURE_JS via Page.addScriptToEvaluateOnNewDocument
in goto.rs before navigation so the hook runs at document start, capturing
page-load console calls before any user scripts execute.
B2: Remove console.error double-push to __ab_error_logs. Errors store now
captures only uncaught exceptions (window error event) and unhandled
promise rejections — not explicit console.error calls.
12/12 logs E2E tests still pass.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Add observation/logs_console.rs and observation/logs_errors.rs with a
JS hook (ENSURE_LOG_CAPTURE_JS) that monkey-patches console.* methods
and listens for window error/unhandledrejection events. Hook is also
injected in browser eval so console calls are captured before browser
logs console is called. All 12 E2E tests pass (12/12).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Accept main's version for all actionbook-rs conflicts (not modified
by this branch's cli-v2 screenshot changes).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Without this the text formatter skips the "ok browser.pdf" line,
failing pdf_text_output E2E test at pdf.rs:164.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
CDP Page.printToPDF → base64 decode → write to path.
Registers pdf.rs in mod.rs/action.rs/cli.rs/router.rs/output.rs.
Text output: path: {path} line per §10.3.
Errors: ARTIFACT_WRITE_FAILED, SESSION_NOT_FOUND, TAB_NOT_FOUND.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Return INVALID_ARGUMENT for unknown --screenshot-format values instead
of silently coercing to PNG, preventing mismatched artifacts.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Hidden elements should still report their text content (e.g. "Item Hidden").
Removes the isVisible guard on text extraction so all matched elements
include their text regardless of visibility state.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Let querySelectorAll exceptions propagate to Runtime.evaluate's
exceptionDetails so the Rust-level JS_EXCEPTION guard fires correctly.
Fixes P1 blocker from Chopper review.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds observation/query.rs with 4 subcommand modes per §10.7:
- one: exactly 1 match or ELEMENT_NOT_FOUND/MULTIPLE_MATCHES
- all: returns all matches (0+ is success)
- nth: 1-based index or INDEX_OUT_OF_RANGE
- count: returns match count only (0+ is success)
Extended CSS selectors: :visible, :contains(), :has(), :enabled,
:disabled, :checked. Real nth-of-type DOM position computation.
Registered in cli.rs, action.rs, router.rs, output.rs, mod.rs.
Added fatal_with_details() helper to ActionResult.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
feat: add agent-friendly help messages with examples to all CLI commands
Add after_help with usage examples and contextual notes to all 35 browser
commands, plus top-level help and browser group help screens. Follows
progressive disclosure: top-level → browser group → per-command --help.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Add observation commands for element description (role, name, tag,
attributes, state, nearby context) and element state (visible, enabled,
checked, focused, editable, selected). Both commands follow the
established observation pattern with CDP Runtime.callFunctionOn,
exceptionDetails → JS_EXCEPTION handling, and proper error envelopes.
All 17 E2E tests in describe_state.rs pass.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>