Commit Graph

682 Commits

Author SHA1 Message Date
mcfn 143ba43e8b [packages/cli]fix: send mouseMoved before mousePressed in dispatch_click (#220)
SPA frameworks (React Router, Vue Router, Next.js) require a prior
mouseMoved event to establish pointer hover state before the click
event synthesizes correctly. Without it, the router's click listener
never fires and the URL remains unchanged (url_changed: false).

Add a single mouseMoved dispatch at (x, y) with button=none before
the existing mousePressed/mouseReleased loop — matches Playwright's
standard click implementation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 13:04:30 +08:00
mcfn f6946d12d9 [packages/cli]test: add click_spa_navigation E2E test (#220)
Injects a SPA-style fixture (history.pushState on click with
preventDefault) into about:blank and verifies that browser click
triggers the router handler and returns url_changed=true.

Tests the mouseMoved fix — will fail until dispatch_click sends
mouseMoved before mousePressed in click.rs.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-02 13:03:31 +08:00
mcfn.feng fc8a3300ad [packages/cli]fix: set-based inflight tracking + stale eviction for network-idle
Replace simple i64 counter with HashMap<requestId, Instant> per session
(Playwright-style set tracking). Requests whose loadingFinished fires on
a different CDP session (cross-origin iframes) are automatically evicted
after 3 seconds, preventing permanent counter inflation.

Key changes:
- TabNetPending: HashMap<String, i64> → HashMap<String, HashMap<String, Instant>>
- network_pending() evicts entries older than 3s before counting
- Exclude WebSocket, EventSource, favicon, data: from tracking
- Redirect requests don't double-count (requestId reuse)
- Comprehensive unit test for skip/undercount scenarios

Results:
- flightaware.com: 30s timeout → 4s (was stuck on WebSocket)
- costcotravel.com: 30s timeout → 2.6s
- healthline.com: still times out (needs per-frame tracking — follow-up)

TODO: Implement Playwright-style per-frame idle tracking to handle
sites with heavy cross-origin iframe tracking (Facebook pixel, etc.)
2026-04-02 12:52:47 +08:00
mcfn.feng df258b8194 [packages/cli]fix: track skipped requestIds to prevent undercount + handle redirects
Address Codex bot P1 review finding: skipping favicon/data: URLs on
requestWillBeSent without also skipping their loadingFinished would
undercount active requests, causing premature network-idle.

Changes:
- Maintain `skipped_request_ids` HashSet to track suppressed requests
- loadingFinished/loadingFailed checks the set and suppresses delta
- Add EventSource exclusion (persistent like WebSocket)
- Handle redirect double-counting (redirectResponse → don't re-increment)
- Add comprehensive unit test covering all skip/undercount scenarios
2026-04-02 12:21:32 +08:00
mcfn.feng a7a61d7e4d [packages/cli]fix: exclude WebSocket/favicon/data: from network-idle pending count
WebSocket upgrade requests fire `Network.requestWillBeSent` but never
`Network.loadingFinished`, leaving the pending counter stuck at ≥1
and causing `wait network-idle` to always time out on sites with
persistent WebSocket connections (analytics, real-time features).

Align with Playwright's behaviour by excluding:
- WebSocket upgrades (type == "WebSocket")
- Favicon requests (url ends with /favicon.ico)
- data: URLs (not intercepted by Network Service)

Before: healthline.com 30s timeout, flightaware.com ~25s
After:  healthline.com 523ms, flightaware.com 514ms
2026-04-02 11:58:46 +08:00
mcfn.feng 5e7a636721 [feat] update version. 2026-04-02 02:53:23 +08:00
mcfn.feng 7a6a240715 [packages/cli]chore: relocate api and commands modules from cli-v2 to cli
Move new files added in upstream release/1.0.0 from the old
packages/cli-v2/ path to packages/cli/ after merge.
2026-04-02 02:21:04 +08:00
mcfn.feng 4d4a437a40 Merge remote-tracking branch 'origin/release/1.0.0' into feat/update-repo 2026-04-02 02:20:01 +08:00
mcfn.feng 2448228950 [root]chore: sync platform package versions to 1.0.0-alpha.4
Run sync-versions.js to align 6 platform packages with CLI version.
Fixes broken release path where optionalDependencies would point to
unpublished npm versions.
2026-04-02 01:23:54 +08:00
mcfn.feng ebd0f6f416 [doc] remove useless doc. 2026-04-02 01:14:35 +08:00
4bmis 5fa2645c83 [packages/cli-v2]feat: add search and get commands from actionbook-rs
Port search and get action commands to cli-v2 with identical logic:
- API client module for search_actions and get_action_by_area_id
- --api-key global flag (env: ACTIONBOOK_API_KEY)
- Updated help text with new commands

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 01:04:53 +08:00
mcfn.feng 22fd6b2ebc [root]docs: update actionbook-rs references in dify-plugin and cli docs
Fix P1 from review: dify-plugin comments referenced deleted
actionbook-rs paths. Also remove "v2" from IFRAME_DESIGN.md.
2026-04-02 00:55:21 +08:00
mcfn.feng 58545129de [root]refactor: remove actionbook-rs, promote cli-v2 to packages/cli
- Delete packages/actionbook-rs/ (legacy 0.x CLI)
- Delete old packages/cli/ (JS npm wrapper for 0.x)
- Rename packages/cli-v2/ → packages/cli/ (git mv)
- Merge npm wrapper files (bin/actionbook.js, scripts/postinstall.js) into new cli/
- Update package.json: name → @actionbookdev/cli, private → false, add npm distribution fields
- Update sync-versions.js: point Cargo.toml/Cargo.lock sync at packages/cli/
- Update release.yml: build from packages/cli/ instead of packages/actionbook-rs/
- Update test.yml: paths and working-directory from cli-v2 → cli
- Update CLAUDE.md, CONTRIBUTING.md, docs, issue templates
- Delete stale changeset (fix-screenshot-timeout.md)
- Keep 6 platform binary packages unchanged
2026-04-02 00:46:12 +08:00
Junliang Feng 5aa37070ea [packages/cli-v2]feat: auto-restart daemon on version mismatch (#443)
* [packages/cli-v2]feat: auto-restart daemon on version mismatch

CLI now compares full BUILD_VERSION (not just major.minor) with the
running daemon. On mismatch, sends SIGTERM to old daemon, waits for
flock release, and starts a fresh daemon automatically.

Changes:
- client.rs: Replace check_version(major.minor) with versions_match
  (full string). Add restart_daemon() + wait_for_daemon(). Socket
  connect path now auto-restarts instead of returning VersionMismatch.
- server.rs: Write daemon.version file on startup, clean up on shutdown.
- Tests updated to verify exact version matching semantics.

* [packages/cli-v2]fix: treat missing daemon.version as version mismatch

Old daemons that never wrote daemon.version were silently accepted
instead of being restarted. Now missing/empty version file returns
false from versions_match(), triggering auto-restart on upgrade.

* [packages/cli-v2]test: E2E tests for daemon version auto-restart

Two scenarios:
- daemon_version_mismatch_auto_restart: tamper daemon.version to
  simulate rebuild, verify CLI auto-restarts daemon with new PID
- daemon_missing_version_file_triggers_restart: delete daemon.version
  to simulate old daemon, verify restart is triggered

* [packages/cli-v2]fix: harden daemon restart — wait for version file, abort on timeout

- P1: restart_daemon() now returns error if old daemon is still running
  after 5s SIGTERM timeout, instead of force-cleaning files
- P2: connect() waits up to 1s for daemon.version file to appear before
  treating missing version as mismatch (avoids killing starting daemon)

* [packages/cli-v2]fix: track target PID during restart instead of global flock

Use kill(pid, 0) to check if the specific old daemon process exited,
instead of is_daemon_running() which checks global flock state. This
prevents false "did not exit" errors when a concurrent CLI has already
started a new daemon that holds the flock.

* [packages/cli-v2]fix: guard cleanup against concurrent restarts + wait for version file

- P1: Before cleanup_stale_files(), check if a concurrent CLI already
  started a matching-version daemon. If so, skip cleanup to avoid
  deleting the new daemon's socket/ready/version files.
- P2: When daemon is running but not connectable, wait up to 1s for
  version file to appear before treating absence as mismatch. Prevents
  killing a same-version daemon still booting up.

* [packages/cli-v2]fix: treat EPERM as alive in PID liveness check

kill(pid, 0) returns EPERM when the process exists but signaling is
not permitted. The previous code treated this as "dead", which could
cause restart logic to delete a live daemon's files. Now only ESRCH
(no such process) is interpreted as definitely dead.

* [packages/cli-v2]fix: reject non-positive PID before sending SIGTERM

kill(2) treats pid<=0 specially (0=caller's process group, negative=
process group). Validate pid>0 to prevent accidental mass kill from
corrupted daemon.pid file.

* [packages/cli-v2]fix: check SIGTERM result + use canonical paths in cleanup

- send_sigterm() now returns bool (false = ESRCH, process already dead)
  restart_daemon() skips wait loop when process is already gone
- cleanup_stale_files() uses server::socket_path().with_extension()
  instead of hardcoded filenames for forward compatibility

* [packages/cli-v2]fix: skip cleanup when PID file is missing but daemon is running

When daemon.pid is missing/corrupt, don't blindly delete socket files.
Check flock first — if daemon is still running, return error. If daemon
is truly gone, clean up and start fresh.
2026-04-02 00:33:25 +08:00
Junliang Feng 841b6319af fix: recover from orphan Chrome after daemon SIGKILL (#219)
* [packages/cli-v2]test: add E2E tests for orphan Chrome recovery and SIGHUP shutdown (#219)

Two new tests in browser_lifecycle.rs:
- lifecycle_daemon_sigkill_orphan_recovered: SIGKILL daemon, verify orphan
  Chrome stays alive, then browser start must succeed without timing out
- lifecycle_daemon_sighup_closes_chrome: SIGHUP triggers graceful shutdown
  and kills Chrome (same as SIGTERM)

Tests will fail until implementation in start.rs (chrome.pid orphan detection),
close.rs (chrome.pid cleanup), and server.rs (SIGHUP handler) is added.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* [packages/cli-v2]fix: recover from orphan Chrome after daemon SIGKILL (#219)

Three changes:

1. start.rs: Before launch_chrome, read chrome.pid from user-data-dir.
   If the recorded PID is still alive (kill -0 probe), SIGKILL it and
   wait 500 ms, then remove chrome.pid. After successful launch, write
   the new Chrome PID to chrome.pid so a future restart can detect it.

2. close.rs: On normal browser close, remove chrome.pid before deleting
   the profile directory so a stale file cannot trigger false orphan
   detection on the next browser start.

3. server.rs: Register SIGHUP handler in run_daemon(). Terminal close
   (SIGHUP) now triggers the same graceful shutdown path as SIGTERM,
   killing all Chrome processes before the daemon exits.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 23:31:01 +08:00
Junliang Feng 785aa91681 [packages/cli-v2]fix: re-read drag source coordinates after destination center-scroll (#440)
* [packages/cli-v2]fix: re-read source coordinates after destination scroll in drag

resolve_center() now scrolls to viewport center unconditionally. For
selector-to-selector drags, the destination scroll can invalidate the
source's viewport coordinates captured earlier. Fix by re-reading
source position via get_center() (no scroll) after destination resolve.

Also returns nodeId from resolve_center() so callers can refresh
coordinates without re-resolving the selector.

* [packages/cli-v2]fix: preserve source frame_id when refreshing drag coordinates

get_center() now accepts an explicit frame_id parameter so drag can
pass the saved source frame context. This prevents cross-frame drags
from querying getBoxModel in the wrong CDP session after destination
resolution overwrites resolved_frame_id.
2026-04-01 18:52:23 +08:00
Junliang Feng e2c29a09d2 test: remove unreliable activeElement.id check in focus_json (#218) (#442)
[packages/cli-v2]test: remove unreliable activeElement.id assertion from focus_json (#218)

In headless Chrome, document.activeElement reverts to document.body
after the CDP focus sequence completes (no OS-level window focus event).
The pre/post reference comparison used by focus_changed runs inside the
CDP sequence where focus is still held, so focus_changed=true is the
authoritative signal. Remove the post-command activeElement.id and
__ab_last_focused eval checks that were flaky in CI.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 18:50:45 +08:00
Junliang Feng 859588501d [packages/cli-v2]test: align snapshot e2e with path output (#441) 2026-04-01 18:40:07 +08:00
Junliang Feng a737a7b0e9 fix(cli-v2): resolve 4 CI E2E failures (#217) (#439)
* [packages/cli-v2]fix: resolve 4 CI E2E failures (#217)

1. start.rs: update native_tabs[0] URL after Page.navigate so registry
   reflects the navigated URL (fixes lifecycle_open_with_url_json)
2. browser_lifecycle.rs: verify --open-url via `browser url` instead of
   `browser status` text (§7.3: status text excludes tab URLs)
3. focus.rs: add Runtime.callFunctionOn after DOM.focus to durably update
   document.activeElement in headless Chrome
4. wait.rs: mark wait_network_idle_json_happy_path #[ignore] with CI note

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* [packages/cli-v2]fix: collapse nested if-let in focus.rs (clippy collapsible_if)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 18:36:39 +08:00
mcfn.feng 04d4300ebf [packages/cli-v2]feat: scroll element to viewport center before interaction
All element interaction commands now scroll the target element to the
viewport center before operating, following Playwright's approach. This
prevents edge cases where elements at the viewport edge are intercepted
by sticky headers/footers.

Changes:
- element.rs: Replace DOM.scrollIntoViewIfNeeded with
  scrollIntoView({ block: 'center' }) via Runtime.callFunctionOn
- type_text.rs, fill.rs, select.rs, focus.rs: Add scroll_into_view()
  call before operating (previously had no scroll at all)
- click and hover inherit the center-scroll automatically via
  resolve_center() and scroll_into_view()
- 7 new E2E tests verifying offscreen element operations
2026-04-01 18:11:42 +08:00
Junliang Feng bb55557f4f [packages/cli-v2] fix: tighten query contains matching (#437)
[packages/cli-v2]fix: tighten query contains matching
2026-04-01 16:46:39 +08:00
4bmis 85187a6cc4 [packages/cli-v2]release: 1.0.0-alpha.4 2026-04-01 16:41:56 +08:00
Junliang Feng 51851bc75b feat(cli-v2): enhance stealth anti-detection (#436)
* [packages/cli-v2]feat: enhance stealth anti-detection for bot.sannysoft.com

- Add Playwright/Puppeteer trace removal (__playwright, __pw_manual, __PW_inspect)
- Enhance chrome.loadTimes() with realistic staggered timestamps
- Add navigator.maxTouchPoints = 0 for desktop consistency
- Add screen.colorDepth/pixelDepth = 24 for headless consistency
- Add canvas fingerprint noise via temporary offscreen canvas (non-destructive)
- Add CDP Emulation.setDeviceMetricsOverride (1920x1080) in attach()
- Replace window.__stealth_applied__ with closure-scoped guard to avoid detection
- Update contract tests and CDP mock test for new capabilities

* [packages/cli-v2]fix: use non-enumerable property for stealth idempotency guard

Replace var-scoped __stealth_guard__ (broken due to IIFE hoisting) with a
non-enumerable, non-configurable property on Navigator.prototype. This
prevents double injection while avoiding detection by anti-bot scripts.

Addresses Codex review feedback on #436.
2026-04-01 16:36:19 +08:00
Junliang Feng b11d5bda75 docs: update api-reference §10.1 snapshot — path replaces content, cursor default true (#435)
[packages/cli-v2]docs: update api-reference §10.1 snapshot — path replaces content, cursor default true

- §10.1 JSON data: replace `content` (inline text) with `path` (file path to saved snapshot)
  — content is now saved to a file only; `nodes` array remains for programmatic use
- §10.1 text output: update example to show "output saved to <path>" format
- §10.1 cursor param: default changed to `true` (always enabled)
- §1.3/§1.4/§5.2: session_id auto-generated as sN (global counter, mode-agnostic)

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 16:21:54 +08:00
Junliang Feng 4923cbcf79 test(cli-v2): E2E tests for browser query extended CSS selectors (#184) (#434)
[packages/cli-v2]test: add E2E tests for extended CSS selectors in browser query

Add 6 tests covering :visible, :contains, :has, :enabled, :disabled, :checked
pseudo-selectors — the remaining gap in #184 (browser query E2E coverage).
All tests use deterministic about:blank + injected DOM fixture.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 15:58:48 +08:00
Junliang Feng 637630394e [packages/cli-v2] feat: snapshot --cursor enabled by default (#212) (#433)
[packages/cli-v2]feat: snapshot --cursor enabled by default (#212)

Change cursor field default from false to true in SnapshotCmd.
Add cursor_default() serde helper so IPC deserialization also defaults
to true when the field is absent.

Scope: no --no-cursor flag per mcfeng confirmation.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 15:49:03 +08:00
Junliang Feng 0fd0b5636c [packages/cli-v2] test: E2E tests for snapshot --cursor default on (#211) (#432)
[packages/cli-v2]test: E2E tests for snapshot --cursor default on (#211)

Add /cursor-fixture deterministic page to harness.rs local server
(cursor:pointer, onclick, tabindex divs) and url_cursor_fixture() helper.

Add snap_cursor_on_by_default test to snapshot.rs: verifies that the
default browser snapshot (no flags) includes cursor-interactive elements.
Expected to fail until #212 implementation (cursor default = true) lands.

Scope: cursor on by default, no --no-cursor per mcfeng confirmation.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 15:44:48 +08:00
Junliang Feng aded1b16b1 [packages/cli-v2] test: update E2E default session ID assertions to s1 (#208) (#431)
* [packages/cli-v2]test: update E2E default session ID from SLOCAL-1 to s1 (#208)

Change DEFAULT_LOCAL_SESSION_ID const from "SLOCAL-1" to "s1".
Update all literal [SLOCAL-1 / [SLOCAL-1] text assertions to [s1 / [s1].

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

* [packages/cli-v2]test: fix cargo fmt in browser_lifecycle.rs (#208)

Collapse short assert! to single line per rustfmt.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 15:44:44 +08:00
Junliang Feng 5f31f6d82c [packages/cli-v2]feat: rename auto-generated session IDs to sN format (#209) (#430)
[packages/cli-v2]feat: rename auto-generated session IDs from SLOCAL-N/SCLOUD-N/SEXT-N to sN

- types.rs: is_valid() drops the uppercase prefix special cases; all IDs
  now use the single lowercase format. auto_generate(n) no longer takes
  a mode param and emits sN (s1, s2, ...). from_profile() drops mode param.
- registry.rs: generate_session_id() uses a single "s" prefix for
  max_active_prefix_n; removes per-mode prefix match. Mode is no longer
  encoded in auto-generated IDs.
- Unit tests updated: SLOCAL-N/SCLOUD-N/SEXT-N assertions replaced with
  sN equivalents; added test that old uppercase prefixes are now rejected.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-01 15:34:30 +08:00
4bmis 6253091d93 [packages/cli-v2]release: 1.0.0-alpha.3 2026-04-01 15:15:57 +08:00
Junliang Feng f06f33a377 [feat] update version. (#429) 2026-04-01 15:11:29 +08:00
mcfn.feng 646376bcce [feat] update version. 2026-04-01 15:10:21 +08:00
4bmis 3b6f6987b0 [packages/cli-v2]feat: add --set-tab-id option to new-tab command
Allow callers to specify a custom tab ID instead of relying on auto-assignment.
Includes duplicate ID validation with TAB_ID_CONFLICT error and rollback on conflict.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 13:57:10 +08:00
mcfn.feng 318b27cd47 Merge remote-tracking branch 'origin/release/1.0.0' into bugfix/e2e-dot-to-space 2026-04-01 13:29:54 +08:00
mcfn.feng 52be63ca59 [packages/cli-v2]fix: resolve remaining e2e failures — timing, assertions, clap
- Add wait_url_contains harness helper (5s poll) for async --open-url
- Fix open_with_url tests: wait for navigation before URL assertion
- Fix profile conflict assertion: match "already in use" wording
- Fix nav_back test: account for Chrome newtab history entry
- Relax focus_json: only assert activeElement (DOM.focus may skip JS events)
2026-03-31 22:57:43 +08:00
Junliang Feng f0f0261a09 fix: list-tabs uses CDP real-time state with registry sync (#428)
* [packages/cli-v2]fix: list-tabs uses CDP real-time state, syncs registry

list-tabs now builds the tab list from CDP Target.getTargets instead of
cross-matching with the registry. This ensures it always reflects actual
browser state even after restart or reconnect.

Registry sync logic:
- Matching native_id → keep short tab ID, update url/title
- Stale registry tabs (not in CDP) → removed
- New CDP tabs (not in registry) → assigned new short ID via push_tab

* [packages/cli-v2]fix: attach newly discovered tabs in list-tabs sync

When list-tabs discovers a CDP page not in the registry, it now calls
cdp.attach() after assigning a short tab ID. Without this, subsequent
commands on the new tab would fail with "no CDP session for target".

* [packages/cli-v2]fix: collapse nested if per clippy
2026-03-31 22:40:08 +08:00
4bmis 43fdcc175e [packages/cli-v2]feat: save snapshot output to per-session data directory
Snapshot output is now written to a timestamped file under
~/.actionbook/sessions/{session_id}/ instead of being returned inline.
The session data directory is created on browser start and cleaned up
on browser close with absolute path safety checks. The snapshot response
now includes a ref usage hint and the file path.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-31 22:23:49 +08:00
mcfn.feng 2d595dfdff Merge remote-tracking branch 'origin/release/1.0.0' into bugfix/e2e-dot-to-space 2026-03-31 22:05:17 +08:00
Junliang Feng 4ee4f98055 [packages/cli-v2]fix: describe --nearby 4 bug fixes — children/siblings/qualifiers/parent (#206)
[packages/cli-v2]fix: describe --nearby 4 bug fixes in JS (children/siblings/state/parent)

Fix 1 (children): relax isig() — add explicit role attr check and
  e.innerText check so nav/semantic elements with descendant text pass.
Fix 2 (siblings): same updated isig() catches role="tab" elements that
  only have text inside a child span (no direct text node).
Fix 3 (state qualifiers): sm() now appends [disabled/checked/selected]
  when detected via element properties or aria-selected attribute.
Fix 4 (parent): add isig_par() that returns true when any direct child
  has an explicit role attribute, preventing always-null parent for
  structural wrappers that contain semantic children.

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-31 21:58:45 +08:00
Junliang Feng 2fe66b5dec [packages/cli-v2] test: cover describe nearby fixes (#426)
[packages/cli-v2]test: cover describe nearby fixes
2026-03-31 21:46:56 +08:00
mcfn.feng 9eba09ae0c [packages/cli-v2]fix: align e2e tests with space-separated command names + fix type clap args
- 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.
2026-03-31 20:57:56 +08:00
Junliang Feng dfe0599af8 [packages/cli-v2]fix: harden CDP session robustness (#422) 2026-03-31 19:49:01 +08:00
mcfn.feng 209f254b53 [packages/cli-v2]fix: detach orphaned CDP session on attach rollback
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.
2026-03-31 19:47:06 +08:00
Junliang Feng 4f8356d37b [packages/cli-v2]fix: browser text returns contentDocument content for iframe elements (#203) (#424)
[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>
2026-03-31 19:12:50 +08:00
Junliang Feng 25aaa33fa3 [packages/cli-v2]test: cover iframe text extraction (#423) 2026-03-31 19:09:45 +08:00
mcfn.feng 89e1ccb031 [packages/cli-v2]fix: harden CDP session robustness — timeout, error propagation, cleanup
- 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
2026-03-31 18:56:53 +08:00
Junliang Feng bb9f872b66 [packages/cli-v2]feat: fill/type accept coordinates and selector-less mode (#202) (#421)
[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>
2026-03-31 18:27:43 +08:00
Junliang Feng 70b3ad9059 [packages/cli-v2]test: cover fill type target modes (#420) 2026-03-31 18:15:34 +08:00
mcfn.feng ae00a43cf2 [packages/cli-v2]fix: change --wait-until default from load to domcontentloaded
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.
2026-03-31 17:35:29 +08:00
Junliang Feng 652f4e5d11 feat: add --wait-until flag to browser goto (#415) 2026-03-31 17:17:45 +08:00