cdpilot 0.9.5 → 0.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +158 -13
- package/bin/cdpilot.js +399 -20
- package/package.json +3 -3
- package/src/cdpilot.py +2619 -293
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# cdpilot
|
|
2
2
|
|
|
3
|
-
> Zero-dependency browser automation from your terminal. One command, full control.
|
|
3
|
+
> Zero-npm-dependency browser automation from your terminal. One command, full control.
|
|
4
4
|
|
|
5
5
|
[](https://www.npmjs.com/package/cdpilot)
|
|
6
6
|
[](https://www.npmjs.com/package/cdpilot)
|
|
@@ -148,7 +148,26 @@ tools (navigate, click, fill, content, screenshot, eval, wait_for, assert, tabs,
|
|
|
148
148
|
plus `browser_tools`. The agent calls `browser_tools` with `list` to see the other
|
|
149
149
|
tools, or `enable` to add some (the server sends `tools/list_changed`). `--tools a,b,c`
|
|
150
150
|
picks tools by name; `all` (default) is unchanged. Schema size of `tools/list`
|
|
151
|
-
(tiktoken `o200k_base`): 45 tools = 5,769 tokens, `core` = 11 tools = 1,342 tokens
|
|
151
|
+
(tiktoken `o200k_base`): 45 tools = 5,769 tokens, `core` = 11 tools = 1,342 tokens; the four
|
|
152
|
+
`browser_agent_*` tools add ~11% of schema (49 tools ≈ 6,400 tokens, scaled from the measurement).
|
|
153
|
+
|
|
154
|
+
### Agent mode over MCP
|
|
155
|
+
|
|
156
|
+
`browser_agent_observe` (`diff`), `browser_agent_act` (`ref`/`url`, `action`, `text`), `browser_agent_flow_save`
|
|
157
|
+
(`name`, `last`) and `browser_agent_run` (`flow`, `vars`, `dry_run`) wrap the agent commands below, so a model
|
|
158
|
+
on MCP works from numbered refs, gets diffs, and replays a learned flow with no page read at all. They are
|
|
159
|
+
outside the `core` profile; `browser_tools enable` adds them. A drift or a missing variable comes back as
|
|
160
|
+
`isError` with the command's JSON (`status:"drift"` carries the page's observation).
|
|
161
|
+
|
|
162
|
+
Set `CDPILOT_DAEMON=1` in the MCP server's environment and every tool call runs in the server process
|
|
163
|
+
instead of a fresh Python process: ~10 ms before the CDP work instead of ~400 ms (`browser_eval` /
|
|
164
|
+
`browser_tabs` / `browser_content` medians 11 / 7 / 9 ms against 412 / 390 / 416 ms, M1 Max under load).
|
|
165
|
+
Same rules as the warm process below — same output, exit codes and session log; lifecycle tools
|
|
166
|
+
(`browser_launch`, `browser_connect`) still run fresh; a call past 30 s is cancelled.
|
|
167
|
+
|
|
168
|
+
```json
|
|
169
|
+
{ "mcpServers": { "cdpilot": { "command": "npx", "args": ["cdpilot", "mcp"], "env": { "CDPILOT_DAEMON": "1" } } } }
|
|
170
|
+
```
|
|
152
171
|
|
|
153
172
|
### MCP Bundle (.mcpb)
|
|
154
173
|
|
|
@@ -321,7 +340,7 @@ cdpilot network <url> --har trace.har # Export navigation traffic as HAR 1.2 JSO
|
|
|
321
340
|
cdpilot debug [url] # Full diagnostic (console+network+perf+shot)
|
|
322
341
|
cdpilot perf # Performance metrics
|
|
323
342
|
cdpilot eval <js> # Execute JavaScript
|
|
324
|
-
cdpilot eval-batch <json> # Run N JS expressions in
|
|
343
|
+
cdpilot eval-batch <json> # Run N JS expressions in one process and one CDP roundtrip instead of N
|
|
325
344
|
```
|
|
326
345
|
|
|
327
346
|
#### Network monitoring & HAR export
|
|
@@ -453,6 +472,82 @@ cdpilot a11y summary # Quick summary
|
|
|
453
472
|
cdpilot a11y find <role> # Find elements by ARIA role
|
|
454
473
|
```
|
|
455
474
|
|
|
475
|
+
### Agent mode: numbered refs and diffs (token budget)
|
|
476
|
+
|
|
477
|
+
```bash
|
|
478
|
+
cdpilot agent observe # {url, title, actions:[{ref:"@1", role, name, value?}], text, token_estimate}
|
|
479
|
+
cdpilot agent act --ref @3 --action click
|
|
480
|
+
cdpilot agent act --ref @4 --action fill --text "cdpilot"
|
|
481
|
+
cdpilot agent act --url https://example.com/ # navigates silently, prints the diff
|
|
482
|
+
cdpilot agent observe --diff # only what changed since the last observe/act
|
|
483
|
+
cdpilot agent stats | agent reset
|
|
484
|
+
```
|
|
485
|
+
|
|
486
|
+
`observe` turns the accessibility tree into a numbered list of things an agent can act on (buttons, links,
|
|
487
|
+
fields with their current value, unique text anchors) plus a short text summary; no screenshot, no HTML.
|
|
488
|
+
Every `act` and `observe --diff` returns only the change: `changed.added` (full descriptors, so the agent can act
|
|
489
|
+
on a new element without another observe), `changed.removed` (refs), `changed.value_changed`, and `text` only
|
|
490
|
+
when it changed (`text_changed`). Refs are stable while the element exists (keyed by the DOM node, not by
|
|
491
|
+
position); a navigation starts a new set. `token_estimate` is `len(json)/4`; `saved_vs_full` compares the diff
|
|
492
|
+
with a full observe of the same page. State lives in `~/.cdpilot/projects/<id>/agent-state.json`.
|
|
493
|
+
|
|
494
|
+
#### Learned flows: drive a site once, replay it with no model
|
|
495
|
+
|
|
496
|
+
```bash
|
|
497
|
+
cdpilot agent observe && cdpilot agent act --ref @3 --action click && cdpilot agent act --ref @4 --action fill --text "cdpilot"
|
|
498
|
+
cdpilot agent flow save inbox-triage # the acts since the last navigation → ~/.cdpilot/projects/<id>/flows/inbox_triage.json
|
|
499
|
+
cdpilot agent run inbox-triage # replays by anchors; prints one JSON: {status:"ok", steps, url, title, ms, tokens_avoided}
|
|
500
|
+
cdpilot agent run login --var password=… # a typed secret was saved as {{password}}; --var supplies it, or CDPILOT_VAR_PASSWORD
|
|
501
|
+
# with CDPILOT_VAR_HOSTS=example.com (the host the value was recorded on)
|
|
502
|
+
cdpilot agent flow list | show <name> | delete <name>
|
|
503
|
+
```
|
|
504
|
+
|
|
505
|
+
Every `agent act` leaves a trail entry addressed by an *anchor*, the element's accessibility role, name and
|
|
506
|
+
position among equals (`{"role":"button","name":"Archive","nth":0}`), never a `@ref` or a DOM node id, which
|
|
507
|
+
belong to one session. `agent flow save` writes the trail as a flow; `agent run` finds each anchor on the live
|
|
508
|
+
page (exact name, then case-insensitive, then a longer name containing it when that is unambiguous) and acts,
|
|
509
|
+
with no model in the loop. The result's `tokens_avoided` is an upper bound — a full observation before each step;
|
|
510
|
+
an agent that reads diffs reads less. On three real sites the model loop read
|
|
511
|
+
2,292–18,331 tokens, the `agent run` result 90–136 (HN 18,331 → 90, Wikipedia 15,474 → 136,
|
|
512
|
+
httpbin 2,292 → 119). When a site has a flow, `agent observe` lists it under `known_flows`.
|
|
513
|
+
|
|
514
|
+
**Drift stops, never guesses.** If a step's anchor is gone, or the step lands on another host than it did
|
|
515
|
+
when recorded, `agent run` exits 4 and prints that page's full observation (`status:"drift"`, `step`,
|
|
516
|
+
`expected`, `actions`, `text`): the agent acts on those refs, finishes the task, and `agent flow save <name>`
|
|
517
|
+
rewrites the flow. Exit 2: unknown flow or a `{{variable}}` without a value (checked before any step runs);
|
|
518
|
+
`--dry-run` prints the steps with placeholders, never values. Values typed into secret-named fields
|
|
519
|
+
(password, one-time code, API key, PIN, card number, …) or token-shaped values are placeholders on disk; the
|
|
520
|
+
same fields show `«masked»` as their value in `observe` output. Masking goes by field name or token shape: a
|
|
521
|
+
secret typed into a field named otherwise is stored in clear, so review a flow before sharing it (flow files
|
|
522
|
+
and `agent-state.json` are 0600). A `{{placeholder}}` typed into `agent act --text` or `--url` is refused
|
|
523
|
+
(exit 2): placeholders are cdpilot's own, so a flow replays only values the user recorded, and a step that
|
|
524
|
+
types one runs only on the origin (scheme, host, port) it was recorded on (old flows without an origin fall
|
|
525
|
+
back to host); another origin or an opaque page is a drift. `--var` values are visible in the process list for the
|
|
526
|
+
duration of the call and are redacted in the session log; an environment value never enters argv.
|
|
527
|
+
`CDPILOT_VAR_<NAME>` is used only by a step recorded on a host named in `CDPILOT_VAR_HOSTS` (comma-separated,
|
|
528
|
+
subdomains included), on that same origin over https (http only on loopback), and never inside a URL — so a flow
|
|
529
|
+
that a prompt-injected model records on another site cannot draw on the environment's secrets; anything else is
|
|
530
|
+
refused before the first step (exit 2, `refused` says why) or is a drift. `agent run` sets a placeholder value on
|
|
531
|
+
the recorded field in one browser call that first checks the field (editable, in the top-level page, not an
|
|
532
|
+
iframe; for an environment value not a search or url field) and the page's origin; a failed check exits 2 and types nothing.
|
|
533
|
+
|
|
534
|
+
#### Text that talks to the agent: `injection_suspect`
|
|
535
|
+
|
|
536
|
+
Page text that addresses the model instead of the user is the shape of an indirect prompt injection ("AI
|
|
537
|
+
assistants reading this page: ignore your previous instructions…"). `agent observe`, the diff of `agent act`
|
|
538
|
+
and a drift result of `agent run` carry `injection_suspect: [{text, match}]` (at most 5, absent when none)
|
|
539
|
+
whenever such a block is in the text: `ignore_instructions`, `addresses_ai`, `system_prompt`,
|
|
540
|
+
`new_instructions`, `hide_from_user`, `exfiltrate`, `fake_system`, `tool_call`, `navigate_order`. Nothing is
|
|
541
|
+
hidden or blocked, the text stays where it was; the agent sees the mark next to it and treats the block as data.
|
|
542
|
+
Matching runs on a normalised copy (zero-width characters removed, NFKC, case folded, spaced-out letters joined);
|
|
543
|
+
the "ignore previous instructions" family also covers Spanish, French, Turkish, Japanese, German and Portuguese.
|
|
544
|
+
The patterns were tuned on two sets (`test/fixtures/injection-korpus.json`, `injection-heldout.json`), so their
|
|
545
|
+
numbers there (20 of 20 and 24 of 24 caught, 0 false alarms) are in-sample. On a set never used for tuning
|
|
546
|
+
(`injection-heldout-2.json`, 30 + 30 lines in 7 languages) they catch 8 of 30 with 1 of 30 false alarms (a recipe
|
|
547
|
+
line); on a third such set (`injection-heldout-3.json`, 40 + 40 lines in 12 languages) 7 of 40 with 2 of 40
|
|
548
|
+
(before this pass: 2 of 40). Fake chat turns, homoglyphs, base64 and Chinese/Russian/Arabic lines still pass.
|
|
549
|
+
Treat the mark as a hint, not a filter: a page without it can still carry an injection.
|
|
550
|
+
|
|
456
551
|
### Session Management
|
|
457
552
|
|
|
458
553
|
```bash
|
|
@@ -537,7 +632,7 @@ cdpilot watch stop # Stop the screencast
|
|
|
537
632
|
```
|
|
538
633
|
|
|
539
634
|
Works on both local files (`file://...`) and online video (YouTube, Vimeo,
|
|
540
|
-
Twitter, Facebook, Instagram).
|
|
635
|
+
Twitter, Facebook, Instagram). No extra package needed — Pillow is optional and only
|
|
541
636
|
used for motion-detection between frames.
|
|
542
637
|
|
|
543
638
|
> **DRM limitation:** DRM-protected players (Netflix and similar) render as
|
|
@@ -558,7 +653,7 @@ cdpilot watch stop # Stop screencast
|
|
|
558
653
|
|
|
559
654
|
### Stealth & CAPTCHA
|
|
560
655
|
|
|
561
|
-
|
|
656
|
+
Self-contained anti-fingerprint layer (no extra packages) — patches `navigator.webdriver`,
|
|
562
657
|
`chrome.runtime`, plugins (proper `PluginArray` inheritance), WebGL
|
|
563
658
|
vendor/renderer, permissions, hardware concurrency, and the `Worker`
|
|
564
659
|
constructor. Injected via `Page.addScriptToEvaluateOnNewDocument` before
|
|
@@ -946,6 +1041,54 @@ CDPILOT_TIMEOUT=60 cdpilot run flow.cdp # default for every command (and each
|
|
|
946
1041
|
For `mcp` and `serve` the value is not applied to the long-running server
|
|
947
1042
|
itself; it is passed on to every tool call / request it runs.
|
|
948
1043
|
|
|
1044
|
+
### Warm process (`CDPILOT_DAEMON=1`)
|
|
1045
|
+
|
|
1046
|
+
Every cdpilot call is a fresh process: node starts (~0.1 s), Python imports the
|
|
1047
|
+
core (~0.15 s) and a CDP session is set up (~0.5 s) before the command itself
|
|
1048
|
+
runs — a browser paints a page in 20 ms, `cdpilot go` took about a second before
|
|
1049
|
+
0.9.6; now 0.5–0.8 s fresh and ~0.23–0.30 s with `CDPILOT_DAEMON=1`
|
|
1050
|
+
(measurements: `.claude/docs/perf-taban-2026-11-08.md`). With
|
|
1051
|
+
`CDPILOT_DAEMON=1` the launcher keeps one warm Python process per project and
|
|
1052
|
+
environment and hands page commands to it over a Unix socket; the process keeps
|
|
1053
|
+
the module and its event loop between calls, so the second and later calls skip
|
|
1054
|
+
the Python start-up and CDP session set-up; node's ~0.1 s launcher remains: a
|
|
1055
|
+
browser-free command ~270 → ~140 ms, `content`/`eval` ~360 → ~110 ms (M1 Max).
|
|
1056
|
+
|
|
1057
|
+
```bash
|
|
1058
|
+
export CDPILOT_DAEMON=1
|
|
1059
|
+
cdpilot go https://example.com # first call: fresh process, starts the daemon
|
|
1060
|
+
cdpilot content # served warm
|
|
1061
|
+
cdpilot daemon status # pid, requests served, uptime, socket
|
|
1062
|
+
cdpilot daemon stop # stop every running daemon (they also leave after 30 min idle)
|
|
1063
|
+
```
|
|
1064
|
+
|
|
1065
|
+
Output, exit codes, the session log and recordings are the same either way:
|
|
1066
|
+
each request runs as its own process would (the caller's argv, cwd,
|
|
1067
|
+
environment and stdin; module state reset between requests; exit-time hooks
|
|
1068
|
+
run at request end; the CDP connections a command opened are closed at request
|
|
1069
|
+
end, as a process exit closed them, so no viewport, throttle or event state
|
|
1070
|
+
leaks into the next command). Closing the client (Ctrl+C) cancels the running
|
|
1071
|
+
command. The launcher runs a fresh process instead whenever the daemon is
|
|
1072
|
+
missing, busy with another call, of another version (that one is asked to
|
|
1073
|
+
leave), set up for a port or profile the registry no longer names, or the
|
|
1074
|
+
command is not a page command: `launch`, `stop`, `connect`, `mcp`, `serve`,
|
|
1075
|
+
`watch`, `replay`, `run`, `captcha-wait` and the other lifecycle commands, and
|
|
1076
|
+
anything with `--timeout`. Nothing is ever executed twice. POSIX only for now;
|
|
1077
|
+
the socket lives in `$CDPILOT_HOME/daemon/` (or `$XDG_RUNTIME_DIR` / a private
|
|
1078
|
+
temp directory when that path is too long for a Unix socket), 0600, and is used
|
|
1079
|
+
only when it and its directory belong to you. `CDPILOT_DAEMON_IDLE=<seconds>`
|
|
1080
|
+
sets the idle exit (default 1800). Opt-in in this release; the default may
|
|
1081
|
+
change once two weeks of use show no difference in error rates.
|
|
1082
|
+
|
|
1083
|
+
The MCP server (`cdpilot mcp`) reads the same variable: with `CDPILOT_DAEMON=1`
|
|
1084
|
+
in its environment it runs each tool call in the server process the same way (the
|
|
1085
|
+
same executor, no socket involved) instead of starting a Python process per
|
|
1086
|
+
call — ~400 ms → ~10 ms before the CDP work. Lifecycle tools and a project
|
|
1087
|
+
configuration that moved since the server started still run fresh; a call past
|
|
1088
|
+
30 s is cancelled at its next async step. A tool call stuck in synchronous code
|
|
1089
|
+
(not waiting on the browser) is not interrupted by the 30 s cancel, unlike the
|
|
1090
|
+
fresh path's kill. See "Agent mode over MCP" above.
|
|
1091
|
+
|
|
949
1092
|
### Session log
|
|
950
1093
|
|
|
951
1094
|
Every command (and every MCP tool call) appends one JSON line to a local,
|
|
@@ -991,7 +1134,7 @@ cdpilot record stop [--save login-flow.json]
|
|
|
991
1134
|
cdpilot replay login-flow.json [--var k=v ...] [--delay real|none|<ms>] [--dry-run] [--continue]
|
|
992
1135
|
```
|
|
993
1136
|
|
|
994
|
-
Commands that run while recording is active are appended to `$CDPILOT_HOME/record/active.json`; secrets are replaced by `{{name}}` placeholders before a step is written to active.json. `CDPILOT_LOG=0` does not turn recording off, and steps run by `replay` are not recorded. Failed steps (`exit != 0`) are dropped at `record stop`. A value becomes a placeholder `{{name}}` cataloged under `vars` if the selector/label is secret-shaped (`#password`, `input[type=password]`, API key), or the value matches a token/bearer pattern or secret URL parameter. Other typed values (emails, search terms, anything the rules do not recognise) are stored verbatim, so review a flow before sharing it; `--save` files are created with mode 0600. In `replay`, `--delay real` reproduces recorded inter-step timing (capped at 10 s per gap), while `--delay none` or a fixed millisecond delay skips waiting. Variables are supplied via `--var k=v` or `CDPILOT_VAR_<NAME>`; missing variables halt replay before any step runs.
|
|
1137
|
+
Commands that run while recording is active are appended to `$CDPILOT_HOME/record/active.json`; secrets are replaced by `{{name}}` placeholders before a step is written to active.json. `CDPILOT_LOG=0` does not turn recording off, and steps run by `replay` are not recorded. Failed steps (`exit != 0`) are dropped at `record stop`. A value becomes a placeholder `{{name}}` cataloged under `vars` if the selector/label is secret-shaped (`#password`, `input[type=password]`, API key), or the value matches a token/bearer pattern or secret URL parameter. Other typed values (emails, search terms, anything the rules do not recognise) are stored verbatim, so review a flow before sharing it; `--save` files are created with mode 0600. In `replay`, `--delay real` reproduces recorded inter-step timing (capped at 10 s per gap), while `--delay none` or a fixed millisecond delay skips waiting. Variables are supplied via `--var k=v` or `CDPILOT_VAR_<NAME>`; missing variables halt replay before any step runs. A `--var` value fills a `{{name}}` anywhere in a step. A `CDPILOT_VAR_<NAME>` environment value fills only the value of an input step (`fill`, `type`, `smart-fill`, `smart-select`, `assert-value` — never its selector, a flag or a flag's value, never a step routed into a frame or typed with `--entropy=on`), and only on an https page whose host `CDPILOT_VAR_HOSTS` names (subdomains included; http only on localhost, 127.0.0.1, [::1] and *.localhost; another port only when the entry is written `host:port`; a URL with a backslash, a control character, whitespace, or `@` / `%` before its path is refused). The value reaches the step's process through its environment, never its command line. The page is checked right before the step and again inside the one browser call that sets the value; that call also refuses a target that is not a text, password, email, tel or number input, a textarea or a select of the top-level page (an iframe, a contenteditable element, a search or url field). A flow that takes any environment value may contain only these steps, which run no page script and print no page data: `go` / `open` (only to a page the rule above allows; the page the browser lands on is read back and checked too), `click`, `dblclick`, `hover`, `smart-click`, `submit`, `scroll-to`, `dismiss`, `keys` (no Ctrl/Alt/Meta), `fill`, `type`, `smart-fill`, `smart-select`, `wait`, `wait-for`, `wait-for-text`, `assert-count`, `assert-visible`, `assert-hidden`, and `assert-value` that takes the value itself. One `eval`, `run`, `batch`, `tools call` or any other step refuses the whole flow, as does an XPath selector or one that reacts to a typed value (`[value…]`, `@value`, `value=`, `:placeholder-shown`, `:valid`, `:checked` and the like, CSS comments and escapes included); in such a flow replay never shows a step's stdout or its duration. Once a value is typed, every later step runs only on the exact origin it was typed on (checked before each step), and any other page stops the replay with exit 2. The flow chooses the field, so a flow can still type the value into a field the allowed site shows or sends on (a comment box, a form): replay only flows you trust. A `google.com` entry covers every subdomain, so prefer exact hosts. A refusal exits 2 before the first step (JSON with `missing` / `refused`, names only) or stops the replay at that step with exit 2. `go`, `eval` and every other command take `--var` only. Replaying a flow runs its steps with your browser profile, so replay only flows you trust: a flow with `eval` can act on any page you are logged into.
|
|
995
1138
|
|
|
996
1139
|
### Use your own browser (human-in-the-loop)
|
|
997
1140
|
|
|
@@ -1232,7 +1375,7 @@ print(result.stdout)
|
|
|
1232
1375
|
│ (CLI) │ Chrome DevTools │ (CDP mode) │
|
|
1233
1376
|
└─────────────┘ Protocol └──────────────┘
|
|
1234
1377
|
│ │
|
|
1235
|
-
│ Zero dependencies
|
|
1378
|
+
│ Zero npm dependencies │ Isolated profile
|
|
1236
1379
|
│ Pure HTTP + WebSocket │ Separate from your
|
|
1237
1380
|
│ single file, stdlib + websockets │ personal browser
|
|
1238
1381
|
└────────────────────────────────────────┘
|
|
@@ -1292,13 +1435,15 @@ The full adaptive layer is bench-neutral vs baseline (30/80 vs 30/80) for Stealt
|
|
|
1292
1435
|
|
|
1293
1436
|
| Feature | cdpilot | Puppeteer | Playwright | Selenium |
|
|
1294
1437
|
|---------|-----------|-----------|------------|----------|
|
|
1295
|
-
| Install size | **one Python file + a small Node launcher, no node_modules** |
|
|
1296
|
-
| Dependencies | **0 npm** (Python: `websockets`, auto-installed) |
|
|
1297
|
-
| Setup time | **
|
|
1438
|
+
| Install size | **one Python file + a small Node launcher, no node_modules** | puppeteer-core ~5.7MB + Chromium ~170-280MB | playwright-core ~12.8MB + browsers | selenium-webdriver ~21.8MB + drivers |
|
|
1439
|
+
| Dependencies | **0 npm** (Python: `websockets`, auto-installed) | 6 direct npm | 1 (playwright-core) direct npm | 4 direct npm |
|
|
1440
|
+
| Setup time | **one npx; needs Python 3.10+** | minutes | minutes | painful |
|
|
1298
1441
|
| AI-agent ready | **yes** | manual | manual | manual |
|
|
1299
1442
|
| Browser download | **no** | yes (Chromium) | yes (3 browsers) | no |
|
|
1300
1443
|
| CLI-first | **yes** | no (library) | no (library) | no |
|
|
1301
|
-
| MCP support | **yes** |
|
|
1444
|
+
| MCP support | **yes** | deprecated official server | yes (official @playwright/mcp) | no official server |
|
|
1445
|
+
|
|
1446
|
+
Third-party numbers from npm on 27.09.2026; sources on [cdpilot.ndr.ist/compare](https://cdpilot.ndr.ist/compare).
|
|
1302
1447
|
|
|
1303
1448
|
## Monetization / Pro (Coming Soon)
|
|
1304
1449
|
|
|
@@ -1344,7 +1489,7 @@ Expired cookies are filtered automatically on load.
|
|
|
1344
1489
|
|
|
1345
1490
|
## Roadmap
|
|
1346
1491
|
|
|
1347
|
-
|
|
1492
|
+
A browser MCP with 10 built-in test assertions. Here's what we've shipped and what's next:
|
|
1348
1493
|
|
|
1349
1494
|
### Shipped
|
|
1350
1495
|
|
|
@@ -1358,7 +1503,7 @@ The only browser MCP with built-in test assertions. Here's what we've shipped an
|
|
|
1358
1503
|
- [x] **Annotated screenshots** — @N badge overlays on interactive elements
|
|
1359
1504
|
- [x] **Auto-wait** — MutationObserver-based, 5s automatic element waiting
|
|
1360
1505
|
- [x] **`wait-for-text`** — adaptive text-based waiting (subtree + characterData) for streaming AI responses, async toasts, and selector-less synchronization
|
|
1361
|
-
- [x] **`eval-batch`** — run N JS expressions in
|
|
1506
|
+
- [x] **`eval-batch`** — run N JS expressions in one process and one CDP roundtrip instead of N
|
|
1362
1507
|
- [x] **`block`** — request blocking via `Network.setBlockedURLs` with built-in presets (images/fonts/ads/media); fewer bytes and faster loads on opt-in (measure on your own pages)
|
|
1363
1508
|
- [x] **`dismiss`** — heuristic auto-click for LLM chat sign-up walls (EN+TR pattern library, destructive-action guards)
|
|
1364
1509
|
- [x] **`adaptive`** — auto-escalate to stealth on CAPTCHA-protected hosts, persistent per-host memory ("run fast, climb walls")
|