- Update all e2e test assertions from dot notation ("ok browser.start")
to space-separated ("ok browser start") matching the refactored
COMMAND_NAME constants
- Fix type command clap panic: change from separate selector/text fields
to `#[arg(num_args = 1..=2)] args: Vec<String>` pattern (same as fill)
to avoid "non-required positional before required" clap assertion
- Fix storage test command_name helper to use space separator
Reduces e2e failures from 57 (on release/1.0.0 CI) to ~5 pre-existing
timing/flaky issues.
Address PR review: when Network.enable fails after Target.attachToTarget
succeeds, send Target.detachFromTarget before rolling back the local
mapping to prevent an unreachable browser-side session.
[packages/cli-v2]fix: return contentDocument.body.innerText for iframe/frame elements in browser text
iframe.innerText is always empty; the text content lives in the embedded document.
Detect IFRAME/FRAME via tagName and read contentDocument.body.innerText instead.
Cross-origin iframes catch SecurityError and return empty string.
Closes#203
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add 60s timeout to CdpSession::execute() to prevent indefinite hangs
on half-open sockets or unresponsive CDP commands (was infinite wait)
- Propagate Network.enable errors in attach() with session mapping rollback
to avoid half-initialized ghost tabs
- Return error from cdp_navigate() on WebSocket EOF instead of silent Ok(())
- Clean up event subscriptions on detach() via unsubscribe_all()
- Remove requestServedFromCache from network pending counter (Chrome CDP
spec: it's an informational event, not a lifecycle terminator)
- Add 8 unit tests covering all fixes
[packages/cli-v2]feat: add coordinate and selector-less support to fill and type commands
- Extract ClickTarget enum and parse_target() from click.rs to element.rs (shared)
- fill: accept optional selector (CSS/XPath/@ref/x,y) or omit to fill activeElement
- type: accept optional selector (CSS/XPath/@ref/x,y) or omit to type into activeElement
- Both commands dispatch mouse click to focus when coordinates are given
- Both commands return NO_FOCUSED_ELEMENT error when no selector and no focused element
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
load waits for ALL resources (including third-party scripts like Adobe
Analytics, Google Maps SDK) which causes timeouts on heavy pages like
Costco Travel. domcontentloaded returns when the DOM is ready —
sufficient for snapshot + click workflows.
Verified: Costco Travel Regent page loads in 1.6s with domcontentloaded
vs 60s+ timeout with load.
Command names in JSON output and internal COMMAND_NAME constants now use
spaces (e.g., "browser snapshot") to match the CLI subcommand format,
replacing the previous dot notation (e.g., "browser.snapshot").
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
reader_loop now clears event_subs when the WebSocket connection drops.
This ensures that event subscribers (like goto's Page.loadEventFired wait)
receive None instead of hanging forever when the session dies mid-navigation.
[packages/cli-v2]fix: --version now prints version string instead of help (#199)
Intercept --version/-V in the pre-parse block before positional-arg
dispatch so it doesn't fall through to the [] => handle_help() branch.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Return INTERNAL_ERROR when get_cdp_session_id is None instead of
silently defaulting to empty string (would cause infinite hang)
- Check Page.enable result when wait is active (fail fast, not 30s timeout)
- Drain stale lifecycle events after Page.enable before navigate to
prevent residual events from satisfying the wait prematurely
goto now waits for page load by default instead of returning immediately
after Page.navigate. This ensures snapshot refs are stable and URLs/titles
reflect the loaded page.
Options:
- load (default): wait for Page.loadEventFired
- domcontentloaded: wait for Page.domContentEventFired (faster)
- none: return immediately (old behavior)
Uses CDP event subscription (Page.enable already called) instead of
JS polling. No internal timeout — relies on global --timeout flag.
Wire-compatible via #[serde(default)] on the new field.
Log every command with session/tab address, status, and elapsed time.
Errors (fatal/retryable/user_action) are logged at warn/error level.
Daemon stderr is now redirected to ~/.actionbook/daemon.log.
[packages/cli-v2]fix: hint when snapshot ref used without @ prefix
When a selector like "e46" is passed to click/fill/etc., the error now
hints: "did you mean @e46? snapshot refs require the @ prefix"
[packages/cli-v2]fix: improve SESSION_ALREADY_EXISTS error with hint
When `browser start --set-session-id` fails because the profile already
has an active session, the error now shows which profile is occupied and
includes a hint telling the user to either address the existing session
or close it first.
Before: "session already exists: SLOCAL-1" (no hint)
After: "profile 'actionbook' already has an active session: SLOCAL-1"
hint: "use --session SLOCAL-1 to address it, or run browser close --session SLOCAL-1 first"
[packages/cli-v2]feat: snapshot output — remove empty quotes for nameless nodes and show URL for link elements (#195)
- render_content(): omit "" suffix when name is empty (- generic instead of - generic "")
- AXNode: add url field; parse from CDP properties array for role=link nodes
- render_content(): append url=<value> inline after ref for link nodes with a URL
- truncate_to_tokens(): skip " \"\"" cost when name is empty
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* [packages/cli-v2]fix: send full CDP key definitions in press command
press Enter (and other functional keys) was not triggering native browser
behaviours like form submission because Input.dispatchKeyEvent was missing
critical CDP parameters (code, windowsVirtualKeyCode, text).
Two issues fixed:
- Added KeyDef mapping table covering 15 functional keys, F1-F12, and
single printable characters with their CDP code, keyCode, and text
- Use "keyDown" type (with text) for keys that produce text (Enter, Space,
letters, digits) so Chrome fires native actions; use "rawKeyDown" for
pure-functional keys (Tab, Escape, arrows)
* [packages/cli-v2]test: add e2e tests for press CDP key definitions
- press_enter_sends_cdp_key_definitions: verify code="Enter", keyCode=13
- press_enter_submits_form: verify Enter triggers native form submission
- press_escape_sends_cdp_key_definitions: verify code="Escape", keyCode=27
- press_tab_sends_cdp_key_definitions: verify code="Tab", keyCode=9
* [packages/cli-v2]fix: address PR review — symbol keys and Shift modifier
- Include ASCII symbol/punctuation keys (/, ., -, ;, etc.) in printable
key mapping so they dispatch as keyDown with text, not rawKeyDown
- Only suppress text for shortcut modifiers (Ctrl/Alt/Meta), not Shift;
Shift+A, Shift+1 (!) etc. are text-producing and need native behaviour
* [root]chore: add changeset for screenshot timeout fix
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [packages/cli-v2]feat: add iframe content expansion and frame-aware interaction support
Snapshot now automatically expands 1 level of iframe content, and all
interaction/observation commands route CDP operations to the correct
frame session for cross-origin iframe elements. This is fully transparent
to agents — no new commands or flags required.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [packages/cli-v2]fix: complete frame routing in screenshot annotate + clarify flag semantics
- get_rect_for_object() now uses execute_for_frame() for Runtime.callFunctionOn,
fixing iframe element annotation boxes that would fail on cross-origin objectIds
- Design doc: replace overly broad "all flags work as before" with precise per-flag
iframe semantics (--selector/--cursor main-frame only, --depth per-frame)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [packages/cli-v2]style: apply rustfmt formatting
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [packages/cli-v2]style: fix clippy collapsible_if warning
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [packages/cli-v2]test: add iframe E2E tests + RefCache unit tests + fix fill for iframe
- 7 E2E tests: snapshot expansion, interactive filter, html/click/fill/value
on iframe refs, ref uniqueness across frames
- 4 RefCache unit tests: composite key collision, frame_id round-trip,
cross-frame stability
- fix fill.rs: use Runtime.callFunctionOn(objectId) instead of
document.activeElement to work correctly for iframe elements
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [packages/cli-v2]test: add cross-origin (OOPIF) iframe E2E tests
- Second HTTP server on different port for true cross-origin iframes
- 4 new tests: xo_iframe_snapshot_expands, xo_iframe_html, xo_iframe_fill,
xo_iframe_click — all validate execute_for_frame() session routing
- Total iframe E2E coverage: 11 tests (7 same-origin + 4 cross-origin)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [packages/cli-v2]style: apply rustfmt to test files
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [packages/cli-v2]style: fix clippy useless_format in test harness
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [packages/cli-v2]refactor: rename execute_in_frame → execute_on_element
Rename frame-aware CDP execution methods to better reflect semantics:
- execute_in_frame → execute_on_element (TabContext method)
- execute_for_frame → execute_for_element (standalone helper)
The old names leaked the iframe implementation detail. The new names
express the caller's intent ("execute on the resolved element") without
implying frame awareness — consistent with the cli-v2 principle that
agents should not need to know about iframe boundaries.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [packages/cli-v2]refactor: restore execute_for_frame name for low-level helper
Layered naming convention:
- High-level (command files): ctx.execute_on_element() — named by intent
- Low-level (infrastructure): execute_for_frame() — named by mechanism
The low-level helper takes frame_id as input and routes by frame/session,
so execute_for_frame accurately describes what it does. Renaming it to
execute_for_element would obscure the mechanism it implements.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* [packages/cli-v2]test: add screenshot --annotate and scroll into-view iframe E2E tests
Complete the iframe E2E test matrix:
- iframe_screenshot_annotate_includes_iframe_elements: verifies annotated
screenshot succeeds with iframe refs in RefCache
- iframe_scroll_into_view_works_on_iframe_element: verifies scroll into-view
routes callFunctionOn to correct frame session
Total: 13 iframe E2E tests (9 same-origin + 4 cross-origin)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
[packages/cli-v2]fix: stealth V1 detection bugs — Google search now works
Three stealth bugs that caused Google bot detection:
1. navigator.webdriver: was re-defined after delete, so 'webdriver' in navigator
still returned true. Fixed: delete from Navigator.prototype only.
2. platform mismatch: hardcoded 'Win32' conflicted with macOS UA. Fixed: removed
platform override, let Chrome report real value.
3. plugins instanceof: fake plugins didn't pass PluginArray instanceof check.
Fixed: set correct prototypes via Object.setPrototypeOf.
Also fixes two Codex-reported bugs from PR #404:
- P1: new tabs (open/click --new-tab) now inherit stealth_ua from session
- P2: ensure_scheme fallback navigates to about:blank instead of passing
blocked protocols through to Page.navigate
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Old configs (without version field) may have incompatible browser.mode values
like "isolate". On load, the config is now parsed as raw TOML first to check
for a version field. If missing, the old config is backed up to config.toml.bak
and a new config is created with version=1, browser.mode forced to "local",
and all other compatible fields copied over.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Refs are tied to backendNodeId, so they persist across snapshots as long
as the DOM node stays the same. This is a key affordance for agents
chaining commands without re-snapshotting after every step.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat(cli): add help examples for logs, query, and storage commands
Add after_help with usage examples to the remaining commands that were
missing them: logs console, logs errors, query, and all five storage
subcommands (list, get, set, delete, clear). Also update the browser
help listing with new sections for Logs, Wait, Cookies, Storage, and
Query so all commands are discoverable from `actionbook browser help`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(cli): show query nth syntax with required <n> argument in browser help
Codex review caught that `query nth` requires an index before the
selector, but the help listed it grouped as `query one|all|nth|count
<selector>`. Split into two lines so nth shows `query nth <n> <selector>`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Catch CDP -32000 "DOM Error while querying" from DOM.querySelector
and return a friendly ELEMENT_NOT_FOUND with the selector in details.
For @-prefixed selectors, hint that the correct format is @eN.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Select an <option> by its snapshot ref (@eN) instead of value or text.
Resolves the ref to a remote object and passes it directly via CDP
arguments, avoiding intermediate value lookups. Includes mutual
exclusivity with --by-text, updated text output, and e2e tests.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Codex review caught that `query nth` requires an index before the
selector, but the help listed it grouped as `query one|all|nth|count
<selector>`. Split into two lines so nth shows `query nth <n> <selector>`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Restore profile directory cleanup on close, but only for non-default
profiles. The default profile ("actionbook") preserves user state
(cookies, localStorage) across sessions. Test-generated profiles
(e.g. "profile-s-12345-0") are cleaned up automatically.
Profile deletion happens after Chrome has fully exited to avoid
EBUSY from held locks.
- restart_kills_old_chrome_and_spawns_new: verify old Chrome PIDs are
dead after restart and new PIDs are different from old ones
- double_close_returns_not_found: verify second close returns
SESSION_NOT_FOUND without panic or crash
- browser.rs: replace inline child.kill() (no wait) with
chrome_reaper::kill_and_reap() to prevent zombie on launch timeout
- chrome_reaper.rs: remove misleading setsid reference from doc comment
Chrome processes were not being cleaned up in several scenarios:
- SessionEntry had no Drop impl, so dropping it leaked the Chrome process
- CdpSession had no close() method, so background reader/writer tasks leaked
- close/restart/shutdown held the registry lock during slow CDP/Chrome cleanup
- child.wait() in spawn_blocking was fire-and-forget (never awaited)
Changes:
- Add chrome_reaper module with centralized kill_and_reap helpers
- Add Drop impl on SessionEntry as last-resort backstop
- Add CdpSession::close() that drops the writer channel and fails pending requests
- Move all slow I/O (cdp.close(), Chrome kill/reap) outside registry lock
- Use kill_and_reap_async (awaited) instead of fire-and-forget spawn_blocking
- kill_and_reap_option takes ownership (sets to None) to prevent double-cleanup
Add after_help with usage examples to the remaining commands that were
missing them: logs console, logs errors, query, and all five storage
subcommands (list, get, set, delete, clear). Also update the browser
help listing with new sections for Logs, Wait, Cookies, Storage, and
Query so all commands are discoverable from `actionbook browser help`.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
The clap argument descriptions for <SELECTOR> fields now read
"Selector (CSS, XPath, or @ref)" instead of generic "Target element
selector" or "CSS selector". Updated 19 command structs.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Two fixes for remaining flaky E2E failures (5-10 random tests per run):
1. Limit parallel threads to 8 (package.json test:e2e) to reduce Chrome
process contention and CPU pressure during parallel test runs.
2. Add wait_page_ready() helper that polls document.readyState every 200ms
(up to 2s) after session start/goto. Called in all 14 local start_session()
functions and both harness helpers. Prevents tests from interacting with
pages before DOM is ready under high parallel load.
Result: 441 passed, 0 failed (was 5-10 random failures per run).
14 test files defined local start_session() without unique_session(),
causing all parallel tests to share the default profile and hit
SESSION_STARTING rejections (238 failures).
Fix: add unique_session("s") to each local start_session(), passing
--set-session-id and --profile to browser start. This gives each test
its own isolated Chrome profile, matching the pattern in harness.rs.
Result: 238 failures → 3 flaky (unrelated to session isolation).