eval "1+1" returns number 2, not string "2". The test used as_str()
which returns None for numbers, causing the assertion to always fail.
Fix: compare with assert_eq!(value, 2) directly. Remove #[ignore].
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
P1: Only delete profile directory for local sessions (those with a
Chrome child process). Cloud sessions don't own a local profile
and could share a profile name with an active local session.
P2: Release the registry lock before slow I/O (Chrome kill, recursive
directory removal) to avoid blocking concurrent session operations.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- wait navigation: use two-phase detection (URL change → readyState
complete) instead of OR logic that false-positives on initial page's
readyState=complete before navigation fires
- cloud_concurrent_eval_multi_tab: mark #[ignore] — known CDP
multiplexing issue mis-routes concurrent responses across tabs
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- browser close now removes the Chrome profile directory after killing
the process, preventing disk accumulation from test runs
- Remove --test-threads=1 from CI — each test already uses a unique
session/profile via unique_session(), so parallel execution is safe
- Profile isolation: each test gets its own Chrome user-data-dir,
no shared state between concurrent tests
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>