@raultov/browser-control-mcp 0.10.0-beta.14 → 0.11.0-beta.16

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/CHANGELOG.md CHANGED
@@ -10,6 +10,119 @@ from a copy of its code: entries describe the differences from that release.
10
10
 
11
11
  ## [Unreleased]
12
12
 
13
+ ## [0.11.0-beta.16] - 2026-11-02
14
+
15
+ Public **BETA**. `audit_page` (`perf` group) audits the rendered page for
16
+ accessibility, SEO and best-practice problems on Chrome and Firefox, with the
17
+ same script and no Lighthouse bundle (each failing check lists its first 10
18
+ elements with their snapshot refs); `emulate` gains `cpu_throttling` and
19
+ `network_throttling` on Chrome; `profile_page_performance` adds INP, LCP image
20
+ discovery, DOM size, oversized/unsized images and third-party weight on both
21
+ browsers, and on Chrome extracts `forced_reflows` from the trace, measures
22
+ `unused_code` (JavaScript and CSS) on a reload and saves the raw trace with
23
+ `save_path`; `get_performance_metrics` takes `collect_garbage: true` and reads
24
+ `Memory.getDOMCounters` on Chrome. Behaviour changes: `get_performance_metrics`
25
+ sorts its metrics by name.
26
+
27
+ ### Added
28
+
29
+ - `emulate` gains `cpu_throttling` (`1` to `20`, e.g. `4` for a 4× slowdown)
30
+ and `network_throttling` (`slow-3g`, `fast-3g`, `4g` or `none`; Chrome
31
+ DevTools names such as `Slow 3G` and `Fast 3G` are accepted too) on Chrome,
32
+ kept in the tab's emulation state across calls, reported in the reply and
33
+ cleared by `reset: true`. On Firefox, where WebDriver BiDi has no CPU or
34
+ bandwidth/latency throttling, they fail naming them. (#52)
35
+ - `profile_page_performance` with `insights: true` adds Interaction to Next
36
+ Paint (`inp`: worst interaction, target and input/processing/presentation
37
+ breakdown), LCP image discovery (`lazy_loaded`, `fetch_priority`), DOM
38
+ size (`nodes`, `max_depth`, `widest_element`, `max_children`), oversized
39
+ or unsized images (`images`) and third-party origins (`third_parties`),
40
+ with findings when they cross the web.dev and Lighthouse thresholds, on
41
+ both Chrome and Firefox. (#53)
42
+ - `audit_page` (`perf` group) audits the rendered page for accessibility
43
+ (images without alt text; fields, buttons and links without a name;
44
+ `html lang`; heading order; duplicate ids; broken `aria-labelledby` and
45
+ `aria-describedby`; `tabindex` above 0; targets under 24×24 px too close
46
+ to another; text contrast under WCAG AA), SEO (title, meta description,
47
+ viewport, vague link text, uncrawlable links, canonical, `noindex`) and
48
+ best practices (doctype and quirks mode, HTTPS, mixed content, distorted
49
+ images, console errors), with the same script on Chrome and Firefox and
50
+ no Lighthouse bundle. Each failing check lists its first 10 elements with
51
+ a snapshot ref, a selector, its start tag and a detail; `categories`
52
+ narrows it. (#55)
53
+ - `get_performance_metrics` takes `collect_garbage: true`, which collects
54
+ garbage before reading on Chrome (`HeapProfiler.collectGarbage`), and adds
55
+ the live `Documents`, `Nodes` and `JSEventListeners` of
56
+ `Memory.getDOMCounters`, so reads after repeating an action show what it
57
+ leaked; its metrics are sorted by name. On Firefox, where WebDriver BiDi
58
+ cannot force a collection, a note says so. (#57)
59
+ - `profile_page_performance` with `insights: true` and `action: "reload"`
60
+ adds `unused_code` on Chrome: the bytes of JavaScript that never ran and
61
+ of CSS that matched nothing during the load (`Profiler` precise coverage
62
+ and `CSS` rule usage, as Lighthouse's "Reduce unused JavaScript / CSS"),
63
+ with the 5 files that waste most and a finding over 50 KB. Without a
64
+ reload, and on Firefox (no coverage over WebDriver BiDi), it is `null`
65
+ with a note saying why. (#54)
66
+ - `profile_page_performance` on Chrome extracts `forced_reflows` from the
67
+ trace when `insights: true` (`Layout` and `UpdateLayoutTree` events with a
68
+ JavaScript stack, grouped by call site, with a finding over 30 ms; `null`
69
+ with a note on Firefox) and takes `save_path` (`.json` or `.json.gz` under
70
+ the upload roots) to write the raw trace for Chrome DevTools or Perfetto
71
+ (refused on Firefox before running). (#56)
72
+
73
+ ### Fixed
74
+
75
+ - `snapshot` names an element by the text slotted into its shadow tree
76
+ (a web component's button labelled by a `<span><slot></span>`):
77
+ `innerText` does not see slotted nodes, so it had no name. (#55)
78
+
79
+ ## [0.10.0-beta.15] - 2026-11-02
80
+
81
+ Public **BETA**. Fixes from a comparison round against Playwright MCP,
82
+ chrome-devtools-mcp and a Puppeteer MCP (now repeatable with
83
+ `make compare`): `snapshot` lists a native `<select>`'s options, marks
84
+ rejected fields `[invalid]` with their error, and gives refs to draggable
85
+ elements and drop zones; `fill_input` picks several options of a
86
+ `<select multiple>`; `press_key` takes `Control+a`; `navigate` and `reload`
87
+ name HTTP authentication (Firefox waited 30 s on its prompt). Behaviour
88
+ changes: on a `<select multiple>`, `fill_input` selects exactly the options
89
+ its text names (it added one); the error for a missing option names it.
90
+
91
+ ### Fixed
92
+
93
+ - `snapshot` and `find_in_snapshot` list the options of a native `<select>`
94
+ under its combobox, with `[selected]` and `[disabled]` and optgroups as
95
+ groups: only the chosen one showed, so an agent had to guess a label to
96
+ learn the choices. `snapshot` lists the first 25 and says how many more;
97
+ `find_in_snapshot` searches them all. Options have no ref: `fill_input`
98
+ picks one by its label or value. (#46)
99
+ - `navigate` and `reload` to a page behind HTTP authentication (401) fail at
100
+ once saying so, with the scheme and realm of its challenge and how to log
101
+ in (the credentials in the URL, which the browser keeps for the session):
102
+ Chrome only said `net::ERR_INVALID_AUTH_CREDENTIALS`, and Firefox showed
103
+ its prompt while the call waited 30 s and answered the previous page.
104
+ (#50)
105
+ - `press_key` takes a key written with its modifiers, `Control+a`,
106
+ `Shift+Tab` or `ControlOrMeta+c` (Meta on macOS, Control elsewhere), as
107
+ agents write it after Playwright; they add to `modifiers`. It failed with
108
+ `Unknown key 'Control+a'`. (#51)
109
+ - `get_network_logs`' note on responses without a body says they are still
110
+ loading or were never read by the page: Chrome never finishes a `fetch`
111
+ whose body the page did not consume, and the old wording (an open event
112
+ stream, no body until it ends) suggested that waiting would help. (#51)
113
+ - `snapshot` marks a field the page or the browser rejects `[invalid]`
114
+ (`aria-invalid`, or `:user-invalid` once the user changed it or tried to
115
+ submit), with `[error="..."]` from its `aria-errormessage`,
116
+ `aria-describedby` or the browser's validation message: after a submit an
117
+ agent could not tell which fields were rejected. (#49)
118
+ - `snapshot` gives a ref to an element made draggable (`draggable="true"`,
119
+ marked `[draggable]`) and to a drop zone (an `ondrop` or `ondragover`
120
+ attribute): both were plain text, so `drag` needed CSS selectors. (#48)
121
+ - `fill_input` (and `fill_form`) on a `<select multiple>` selects exactly
122
+ the options named one per line of `text` (none for an empty one), firing
123
+ `input` and `change` once; an unknown line fails naming it, with nothing
124
+ changed. It could only add one option at a time. (#47)
125
+
13
126
  ## [0.10.0-beta.14] - 2026-11-01
14
127
 
15
128
  Public **BETA**. `emulate` gains `user_agent`, `locale`, `timezone` and
package/README.md CHANGED
@@ -13,7 +13,7 @@ It is the successor of
13
13
  from a copy of its code) and complements Playwright MCP or
14
14
  chrome-devtools-mcp with runtime JavaScript debugging on both browsers.
15
15
 
16
- > **Status: BETA (0.10.0-beta.14).** This version contains known bugs and differences
16
+ > **Status: BETA (0.11.0-beta.16).** This version contains known bugs and differences
17
17
  > between Chrome and Firefox (see [Limitations and TODO](#limitations-and-todo))
18
18
  > and parts that have not been verified (see [Untested](#untested)). Tool
19
19
  > replies may change in incompatible ways before 1.0. Please report problems
@@ -27,7 +27,9 @@ Measured on 2026-10-31 with browser-control-mcp built from the current
27
27
  sources, `@playwright/mcp` 0.0.83 and `chrome-devtools-mcp` 1.12.0, driven by
28
28
  the same stdio client through the same tasks on Chrome 156 (headless,
29
29
  Linux). Every outcome was checked in the page, not taken from the reply.
30
- Tokens are estimated as characters / 4 for every server.
30
+ Tokens are estimated as characters / 4 for every server. browser-control's
31
+ tool lists and the HTTP authentication row were measured again on
32
+ 2026-11-02, with the changes of #46 to #55.
31
33
 
32
34
  **What each one can do**
33
35
 
@@ -41,11 +43,13 @@ Tokens are estimated as characters / 4 for every server.
41
43
  | Snapshot of a long page | **bounded at 50 000 characters**, `find_in_snapshot` searches the rest | unbounded | unbounded |
42
44
  | Waits for the page to react after an action | ✓ ~50 ms when nothing reacts | ✓ ~500 ms | ✓ ~200 ms |
43
45
  | Clicking a covered or hidden element | **fails at once, naming the overlay** | fails after a 5 s timeout | reports success (the overlay gets the click) |
46
+ | Page behind HTTP authentication (401) | **fails at once naming its scheme and realm and how to log in** (credentials in the URL), on Chrome and Firefox | fails with `net::ERR_INVALID_AUTH_CREDENTIALS` | reports success, with `net::ERR_INVALID_AUTH_CREDENTIALS` in the text |
44
47
  | Mock requests, offline mode | ✓ | ✓ | offline only |
45
48
  | PDF | ✓ file or inline | ✓ file | ✗ |
46
49
  | Cookies and web storage | ✓ values hidden unless asked | ✓ values shown | ✗ |
47
50
  | Secrets masked in output (`--secrets`) | ✓ | ✓ | ✗ |
48
- | Performance | vitals + insights (LCP breakdown, layout shifts, render-blocking, long scripts) | trace file | trace + insights, Lighthouse |
51
+ | Performance | vitals + insights (INP, LCP breakdown and discovery, layout shifts, render-blocking, long scripts, DOM size, images, third parties; on Chrome: forced reflows, unused JS/CSS, trace export `.json`/`.gz`, DOM/listener counters after GC); CPU and network throttling on Chrome | trace file | trace + insights; CPU and network throttling |
52
+ | Page audit (accessibility, SEO, best practices) | ✓ `audit_page`, on Chrome and Firefox: failing elements with snapshot refs, no bundle | ✗ | Lighthouse, Chrome only: scores, failing elements with selectors, reports saved to disk |
49
53
  | Heap snapshots, CSS styles, screencast | ✗ | video, tracing | ✓ |
50
54
  | Generates Playwright test code | ✗ | ✓ | ✗ |
51
55
  | Telemetry | **none** | none found | usage statistics on by default; traced URLs sent to CrUX |
@@ -55,8 +59,8 @@ Tokens are estimated as characters / 4 for every server.
55
59
 
56
60
  | Task | browser-control-mcp | Playwright MCP | chrome-devtools-mcp |
57
61
  | --- | --- | --- | --- |
58
- | Tool list in context, default tools | 6 450 tokens | 5 330 tokens | 7 770 tokens |
59
- | Tool list in context, every opt-in tool on | 9 910 tokens | 12 040 tokens | not measured (optional categories) |
62
+ | Tool list in context, default tools | 6 730 tokens | 5 330 tokens | 7 770 tokens |
63
+ | Tool list in context, every opt-in tool on | 10 880 tokens | 12 040 tokens | not measured (optional categories) |
60
64
  | Snapshot of Hacker News | **4 588 tokens** | 12 356 tokens | 6 869 tokens |
61
65
  | Snapshot of Wikipedia "Web browser" | **12 643 tokens** (cut, searchable) | 34 349 tokens | 27 656 tokens |
62
66
  | Snapshot of MDN `<input>` | **12 625 tokens** (cut, searchable) | 158 077 tokens | 76 654 tokens |
@@ -92,7 +96,7 @@ tools are off by default and listed only with `--tool-groups`:
92
96
  | `storage` | `cookies`, `web_storage` | reads and changes session tokens |
93
97
  | `files` | `upload_file`, `save_pdf` | reads and writes files of the machine |
94
98
  | `raw` | `send_cdp_command`, `send_bidi_command`, `send_rdp_request`, `get_custom_events` | anything the protocol allows |
95
- | `perf` | `get_performance_metrics`, `profile_page_performance` | niche |
99
+ | `perf` | `get_performance_metrics`, `profile_page_performance`, `audit_page` | niche |
96
100
  | `emulation` | `emulate` | niche |
97
101
  | `network` | `mock_route`, `list_routes`, `remove_route`, `set_offline` | changes what the page receives |
98
102
 
@@ -119,19 +123,19 @@ see the note.
119
123
  | `close_tab` | ✓ | ✓ | |
120
124
  | `list_tabs` | ✓ | ✓ | |
121
125
  | `switch_tab` | ✓ | ✓ | |
122
- | `navigate` | ✓ | ✓ | `history: "back"` or `"forward"` instead of `url` moves one entry in the tab's history and waits for the page to load (also when it comes from the back-forward cache, which fires no `load`); at either end it fails without moving; a URL the browser saves instead of opening (a download: `Content-Disposition: attachment`, or a type such as CSV) fails saying so, and the tab stays where it was; a page that pauses at a breakpoint or opens a dialog while loading fails at once naming it (see `resume`, `handle_dialog`) |
123
- | `reload` | ✓ | ✓ | a pause or a dialog while loading fails at once, as with `navigate` |
126
+ | `navigate` | ✓ | ✓ | `history: "back"` or `"forward"` instead of `url` moves one entry in the tab's history and waits for the page to load (also when it comes from the back-forward cache, which fires no `load`); at either end it fails without moving; a URL the browser saves instead of opening (a download: `Content-Disposition: attachment`, or a type such as CSV) fails saying so, and the tab stays where it was; a page behind HTTP authentication (401) fails at once naming its scheme and realm and how to log in (credentials in the URL, `http://user:password@host/path`, which the browser keeps for the session); a page that pauses at a breakpoint or opens a dialog while loading fails at once naming it (see `resume`, `handle_dialog`) |
127
+ | `reload` | ✓ | ✓ | a pause, a dialog or HTTP authentication while loading fails at once, as with `navigate` |
124
128
  | `scroll` | ✓ | ✓ | reports the window positions before and after |
125
- | `emulate` | ✓ | partial | `emulation` group; `width`/`height`, `device_scale_factor`, `mobile` (touch), `color_scheme`, `reduced_motion`, `forced_colors`, `media` (`print`), `user_agent`, `locale`, `timezone` (checked by the page's `Intl` first), `geolocation` (the page is granted the permission to read it), `reset`; each call adds to the earlier ones; answers what the page now sees (with the position it reads); Firefox: no `mobile` or media options (BiDi has no media or touch emulation yet; they fail naming them) |
129
+ | `emulate` | ✓ | partial | `emulation` group; `width`/`height`, `device_scale_factor`, `mobile` (touch), `color_scheme`, `reduced_motion`, `forced_colors`, `media` (`print`), `user_agent`, `locale`, `timezone` (checked by the page's `Intl` first), `geolocation` (the page is granted the permission to read it), `cpu_throttling` (1–20×), `network_throttling` (`slow-3g`, `fast-3g`, `4g`, `none`), `reset`; each call adds to the earlier ones; answers what the page now sees (with the position it reads and the active throttling); Firefox: no `mobile`, media or throttling options (BiDi has no media, touch or throttling emulation yet; they fail naming them) |
126
130
  | `wait_for` | ✓ | ✓ | waits (checking every 100 ms, at most 30 s) until a visible text appears or is gone, a selector is `visible` / `hidden` / `attached` / `detached`, or the URL contains a text; a dialog or debugger pause ends the wait at once |
127
131
  | `inspect_dom` | ✓ | ✓ | includes open shadow roots and same-origin iframes (cross-origin ones are noted); at most 100 000 characters without `query` (cut in the page) |
128
- | `snapshot` | ✓ | ✓ | outline of the page: role, name, states, value and a ref (`e12`) per element, through open shadow roots and same-origin iframes, and on Chrome closed shadow roots too (pages up to 20 000 elements; their elements are reached by ref, not by `>>>`); frames of another origin are read too (at most 20, 5 deep), their iframe marked `[frame=f2]` and their refs prefixed (`f2e5`), and `selector: "f2"` outlines one; tables without headers (`th`, `thead`, `caption`) are layout and leave only their content, and a cell is named by its text only when it holds nothing else; comboboxes, spinbuttons and sliders built with ARIA show their value (the field's text, `aria-valuetext` or `aria-valuenow`, the option a button-like combobox shows), progress bars and meters theirs (`40/100`), and a `<details>` summary `[expanded=true|false]`; what the page made inert is left out: `inert` subtrees and, while a modal `<dialog>` is open (marked `[modal]`), everything outside it; an element that is only clickable (a handler or a pointer cursor) is one named line, unless it holds links, buttons or fields, which are listed under it; at most 50 000 characters (`selector` and `depth` narrow it); the same JavaScript on both browsers |
132
+ | `snapshot` | ✓ | ✓ | outline of the page: role, name, states, value and a ref (`e12`) per element, through open shadow roots and same-origin iframes, and on Chrome closed shadow roots too (pages up to 20 000 elements; their elements are reached by ref, not by `>>>`); frames of another origin are read too (at most 20, 5 deep), their iframe marked `[frame=f2]` and their refs prefixed (`f2e5`), and `selector: "f2"` outlines one; tables without headers (`th`, `thead`, `caption`) are layout and leave only their content, and a cell is named by its text only when it holds nothing else; comboboxes, spinbuttons and sliders built with ARIA show their value (the field's text, `aria-valuetext` or `aria-valuenow`, the option a button-like combobox shows), progress bars and meters theirs (`40/100`), and a `<details>` summary `[expanded=true|false]`; a field the page rejects (`aria-invalid`) or the browser does (`:user-invalid`, once the user changed it or tried to submit) is `[invalid]`, with `[error="..."]` from its `aria-errormessage`, `aria-describedby` or the browser's validation message; a native `<select>` lists its options under its combobox (`[selected]`, `[disabled]`, optgroups as groups; no refs: `fill_input` picks one), the first 25 and how many more, all of them for `find_in_snapshot`; what the page made inert is left out: `inert` subtrees and, while a modal `<dialog>` is open (marked `[modal]`), everything outside it; an element that is only clickable (a handler or a pointer cursor), draggable (`draggable="true"`, marked `[draggable]`) or a drop zone (`ondrop`/`ondragover`) is one named line with a ref for `drag`, unless it holds links, buttons or fields, which are listed under it; at most 50 000 characters (`selector` and `depth` narrow it); the same JavaScript on both browsers |
129
133
  | `find_in_snapshot` | ✓ | ✓ | `text` (case-insensitive) or `regex` over the whole outline; matching lines under their ancestors, at most 20 |
130
134
  | `save_pdf` | ✓ | ✓ | `files` group; prints the tab as the browser's print does (`landscape`, `print_background`, `scale` 0.1–2, `page_ranges` like `1-3, 5`); with `path` (an absolute `.pdf` under the upload roots) writes the file and answers `{path, bytes, pages}`, else returns the PDF as an embedded `application/pdf` resource (at most 5 MB); Chrome `Page.printToPDF`, Firefox `browsingContext.print` |
131
135
  | `capture_screenshot` | ✓ | ✓ | `format` `png`, `jpeg` (or `jpg`) or `webp`; `full_page` at most 16 384 px per side (a text note says when cut); `selector` captures one element (CSS, `host >>> inner` or a snapshot ref), scrolled into view, whole even when taller than the viewport |
132
136
  | `click_element` | ✓ | ✓ | scrolls the element into view; `button` (left/right/middle), `click_count` (2 = double click) and `modifiers`; fails without clicking when it is hidden, covered (its own label on top does not count: styled checkboxes and switches are clicked through it; a covered element first gets the mouse, so controls shown only on hover are revealed and clicked) or a disabled control, whose handler would never run; an element a CSS selector does not find yet, or a disabled control, is checked again for up to 2 s, as pages add elements and enable buttons a moment after the previous action (a ref that names nothing, a hidden or a covered element fail at once); `host >>> inner` selectors reach open shadow roots and same-origin iframes (also in `fill_input`, `scroll`, `wait_for`); a dialog or debugger pause the click causes is noted in a successful reply (`{dialog}` or `{paused, location}`) |
133
- | `fill_input` | ✓ | ✓ | picks `<select>` options; sets `date`, `time`, `datetime-local`, `month`, `week`, `range` and `color` inputs to the text in their own format (`2026-10-31`, `13:45`, `72`, `#ff0000`), firing `input` and `change`, noting a value the input adjusted (a range snaps to its step) and failing, without changing it, on a malformed one; fails without typing on elements that cannot take the text; `append: true` types after the content instead of replacing it (a number or email field, which has no caret, is retyped whole); an editor of its own (an element with an `EditContext` or a focusable `textbox` role, or the hidden input of a code editor such as Monaco or Ace) gets the keys a user presses: a click on the editor when its input has no visible box, Control+A (Command+A on macOS) or Control+End, then the text, and the reply says its content could not be checked; in a contenteditable element (including rich text editors such as Slate, Lexical, ProseMirror or CodeMirror 6) it selects the content (a range, then Control+A as a user would) and types over it, so the editor records the edit; types key by key on both (key handlers run; newlines and tabs inserted as text), about 1 ms per character; `change` on leaving the field, or at the end when the window has no focus |
134
- | `press_key` | ✓ | ✓ | one key (a character, Enter, Tab, Escape, arrows, F1–F12...) with Control / Shift / Alt / Meta held, in `selector` (focused first) or the focused element; a shortcut types nothing |
137
+ | `fill_input` | ✓ | ✓ | picks `<select>` options (on a `<select multiple>`, exactly the ones named one per line; an unknown one changes nothing); sets `date`, `time`, `datetime-local`, `month`, `week`, `range` and `color` inputs to the text in their own format (`2026-10-31`, `13:45`, `72`, `#ff0000`), firing `input` and `change`, noting a value the input adjusted (a range snaps to its step) and failing, without changing it, on a malformed one; fails without typing on elements that cannot take the text; `append: true` types after the content instead of replacing it (a number or email field, which has no caret, is retyped whole); an editor of its own (an element with an `EditContext` or a focusable `textbox` role, or the hidden input of a code editor such as Monaco or Ace) gets the keys a user presses: a click on the editor when its input has no visible box, Control+A (Command+A on macOS) or Control+End, then the text, and the reply says its content could not be checked; in a contenteditable element (including rich text editors such as Slate, Lexical, ProseMirror or CodeMirror 6) it selects the content (a range, then Control+A as a user would) and types over it, so the editor records the edit; types key by key on both (key handlers run; newlines and tabs inserted as text), about 1 ms per character; `change` on leaving the field, or at the end when the window has no focus |
138
+ | `press_key` | ✓ | ✓ | one key (a character, Enter, Tab, Escape, arrows, F1–F12...) with Control / Shift / Alt / Meta held (in `modifiers`, or written before the key as `Control+a`, `Shift+Tab` or `ControlOrMeta+c`: Meta on macOS, Control elsewhere), in `selector` (focused first) or the focused element; a shortcut types nothing |
135
139
  | `hover` | ✓ | ✓ | moves the mouse over the element (scrolled into view; fails when hidden or covered) |
136
140
  | `drag` | ✓ | ✓ | presses on `from` (checked like `click_element`), moves in steps to the centre of `to` (which must then be in the viewport) and releases: sortable lists, boards, sliders; HTML5 drag and drop is real on Chrome and simulated with script events on Firefox (its WebDriver input starts a native drag but never drops it), and the reply says so |
137
141
  | `fill_form` | ✓ | ✓ | several fields in one call: text and `<select>` as `fill_input`, a checkbox or radio takes `true`/`false` and is clicked only when its state differs; stops at the first failing field naming what was done; never echoes values |
@@ -141,14 +145,15 @@ see the note.
141
145
  | `cookies` | ✓ | ✓ | `storage` group; `get` / `set` / `delete`: the cookies sent to the page (or `url`) or of a `domain`; values hidden unless `show_values: true` (Chrome `Network.*Cookie*`, Firefox BiDi `storage.*` in the tab's partition) |
142
146
  | `web_storage` | ✓ | ✓ | `storage` group; `get` / `set` / `delete` / `clear` on the page's `localStorage` or `sessionStorage`; values hidden unless `show_values: true` |
143
147
  | `get_console_logs` | ✓ | ✓ | one line per message, oldest first: `[level] text @ url:line` (lines from 1), objects and arrays previewed as the console shows them (`{n: 1}`, `[1, 2, 3]`), repeats in a row folded (`[x3]`), uncaught errors marked with up to 5 stack lines indented; `include_details: true` gives the JSON with every field; a message keeps at most 10 000 characters; Firefox: page errors, CSS warnings and platform messages need the debugger port |
144
- | `get_network_logs` | ✓ | ✓ | one line per request, oldest first (`id METHOD url => status [type]`, or `failed: <reason>` / `pending`), then one per WebSocket frame (payload preview); `request_id` shows one request's headers and body, `include_details: true` every request's as JSON (a binary body has `response_body_encoding: "base64"`; bodies at most 64 000 characters, none over 2 MB; a WebSocket payload keeps 64 000, `payload_len` is the whole); the browser's own requests (`chrome://`, `devtools://`, `data:`, `about:`, extensions) are not logged; Firefox: WebSocket frames need the debugger port |
148
+ | `get_network_logs` | ✓ | ✓ | one line per request, oldest first (`id METHOD url => status [type]`, or `failed: <reason>` / `pending`), then one per WebSocket frame (payload preview); `request_id` shows one request's headers and body, `include_details: true` every request's as JSON (a binary body has `response_body_encoding: "base64"`; bodies at most 64 000 characters, none over 2 MB; a WebSocket payload keeps 64 000, `payload_len` is the whole); the browser's own requests (`chrome://`, `devtools://`, `data:`, `about:`, extensions) are not logged; a note counts the responses with no body because they are still loading or the page never read them (an open stream; on Chrome, a `fetch` whose body the page did not consume); Firefox: WebSocket frames need the debugger port |
145
149
  | `mock_route` | ✓ | ✓ | `network` group; requests whose URL matches a glob (`*`, `?`, `\` escapes) get a mocked `status`, `body` (text, at most 1 MB), `content_type` and `headers` without reaching the network; the latest matching route wins; at most 50 routes per tab, kept until `remove_route` or the tab closes (Chrome `Fetch`, not while `enable_proxy_auth` is active; Firefox a `network` intercept) |
146
150
  | `list_routes` | ✓ | ✓ | `network` group; the tab's routes with the `hits` each answered |
147
151
  | `remove_route` | ✓ | ✓ | `network` group; one route by id, or all |
148
152
  | `set_offline` | ✓ | ✓ | `network` group; requests fail as without a network and `navigator.onLine` is false, until set back (Chrome `Network.emulateNetworkConditions`, Firefox `emulation.setNetworkConditions`); mocked routes still answer; Chrome still serves what its HTTP cache holds, as an offline browser does, Firefox fails every request |
149
153
  | `get_custom_events` | ✓ | partial | `raw` group; `{events, total, truncated}`, the newest `limit` events; `clear` removes the returned ones; params over 64 000 characters of JSON are replaced by `{truncated, characters}`; Firefox: events of the modules subscribed with `send_bidi_command` |
150
- | `get_performance_metrics` | ✓ | partial | `perf` group; Firefox: `Nodes`, `Frames`, `Resources`, `TransferSize` from the Web Performance APIs (no Performance domain), plus a note |
151
- | `profile_page_performance` | ✓ | partial | `perf` group; `insights: true` adds what explains the numbers, read with the Web Performance APIs (same JavaScript on both): the document's TTFB, server time and compression, the LCP element and its breakdown (TTFB, load delay, load, render delay), layout-shift culprits, render-blocking requests, the slowest requests and the scripts of long animation frames (at most 5 each), plus `findings` from the web.dev thresholds; Firefox: FCP, LCP, DCL and Load; no trace, so `total_blocking_time_ms` and `top_long_tasks` are `null`, and its insights have no layout shifts, render-blocking requests or long frames (`null`, with notes) |
154
+ | `get_performance_metrics` | ✓ | partial | `perf` group; Chrome's metrics sorted by name, with the live `Documents`, `Nodes` and `JSEventListeners` of `Memory.getDOMCounters`; `collect_garbage: true` collects garbage first (`HeapProfiler.collectGarbage`), so reads after repeating an action show what it leaked; Firefox: `Nodes`, `Frames`, `Resources`, `TransferSize` from the Web Performance APIs (no Performance domain) and no garbage collection (WebDriver BiDi has none), plus notes |
155
+ | `profile_page_performance` | ✓ | partial | `perf` group; `save_path` (Chrome only) writes the raw trace (`.json` or `.json.gz`, under the upload roots) for Chrome DevTools or Perfetto; `insights: true` adds what explains the numbers, read with the Web Performance APIs and the DOM (same JavaScript on both): the document's TTFB, server time and compression, the LCP element, its breakdown (TTFB, load delay, load, render delay) and image discovery (`lazy_loaded`, `fetch_priority`), Interaction to Next Paint (`inp`: duration, event, target and input/processing/presentation breakdown), layout-shift culprits, render-blocking requests, the slowest requests, the scripts of long animation frames, DOM size (`nodes`, `max_depth`, `widest_element`, `max_children`), oversized or unsized images, and third-party origins by duration and bytes (at most 5 each), plus `findings` from the web.dev and Lighthouse thresholds; on Chrome, `forced_reflows` from the trace (`Layout`/`UpdateLayoutTree` forced by a script, grouped by call site, a finding over 30 ms) and, with `action: "reload"`, `unused_code` (`unused_js_bytes`, `unused_css_bytes` and the 5 files with most unused bytes, from the `Profiler` precise coverage and `CSS` rule usage of the reload; a finding over 50 KB; without a reload it is `null`: code run before the recording would count as unused); Firefox: FCP, LCP, DCL and Load; no trace (`save_path` fails), so `total_blocking_time_ms` and `top_long_tasks` are `null`, and its insights have no layout shifts, render-blocking requests, long frames, forced reflows or unused code (`null`, with notes) |
156
+ | `audit_page` | ✓ | ✓ | `perf` group; accessibility (images without alt, fields, buttons and links without a name, `html lang`, heading order, duplicate ids, broken `aria-labelledby`/`aria-describedby`, `tabindex` > 0, targets under 24×24 px too close to another, text contrast under WCAG AA), SEO (title, meta description, viewport, vague link text, uncrawlable links, canonical, `noindex`) and best practices (doctype and quirks mode, HTTPS, mixed content, distorted images, console errors); the same script on both browsers; each failing check lists its first 10 elements with a snapshot ref, a selector, its start tag and a detail (the contrast ratio, the size...), then the ids that passed or did not apply; `categories` narrows it; not checked: text over background images or gradients, iframes, closed shadow roots, response headers |
152
157
  | `set_breakpoint` | ✓ | partial | Firefox: by URL only (no `script_id` / `script_hash`) |
153
158
  | `remove_breakpoint` | ✓ | ✓ | |
154
159
  | `resume` | ✓ | ✓ | |
@@ -683,12 +688,17 @@ incompatible:
683
688
  make check # fmt, clippy -D warnings, tests, docs, duplication, packaging, version
684
689
  make e2e # E2E smoke against real Chrome and Firefox, headless (E2E_VISIBLE=1 shows them; Linux, macOS)
685
690
  make check-all # both
691
+ make compare # the same tasks on this and other browser MCP servers (tests/compare)
686
692
  ```
687
693
 
688
694
  `make check` is hermetic: it never starts a browser or touches the network.
689
695
  `make e2e` runs `tests/run_all_e2e.sh`: the launched-mode smoke for both
690
696
  browsers, then the attach mode against browsers started by `tests/chrome.sh`
691
- and `tests/firefox.sh`. See [AGENTS.md](AGENTS.md) for the conventions.
697
+ and `tests/firefox.sh`. `make compare` runs the scenarios of
698
+ [tests/compare](tests/compare/README.md) (from common tasks to rare ones, each
699
+ with a check) on browser-control and on Playwright MCP, chrome-devtools-mcp
700
+ and a Puppeteer MCP, and compares the outcome, calls and tokens with a saved
701
+ baseline. See [AGENTS.md](AGENTS.md) for the conventions.
692
702
 
693
703
  ## Releasing
694
704
 
@@ -19,7 +19,7 @@
19
19
  "hasInstallScript": true,
20
20
  "license": "MIT",
21
21
  "name": "@raultov/browser-control-mcp",
22
- "version": "0.10.0-beta.14"
22
+ "version": "0.11.0-beta.16"
23
23
  },
24
24
  "node_modules/detect-libc": {
25
25
  "engines": {
@@ -48,5 +48,5 @@
48
48
  }
49
49
  },
50
50
  "requires": true,
51
- "version": "0.10.0-beta.14"
51
+ "version": "0.11.0-beta.16"
52
52
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "artifactDownloadUrls": [
3
- "https://github.com/raultov/browser-control-mcp/releases/download/v0.10.0-beta.14"
3
+ "https://github.com/raultov/browser-control-mcp/releases/download/v0.11.0-beta.16"
4
4
  ],
5
5
  "author": "raultov <raultov@protonmail.com>",
6
6
  "bin": {
@@ -92,7 +92,7 @@
92
92
  "zipExt": ".tar.xz"
93
93
  }
94
94
  },
95
- "version": "0.10.0-beta.14",
95
+ "version": "0.11.0-beta.16",
96
96
  "volta": {
97
97
  "node": "18.14.1",
98
98
  "npm": "9.5.0"