@automatebrowser/mcp 0.3.0
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/.claude-plugin/marketplace.json +17 -0
- package/.claude-plugin/plugin.json +10 -0
- package/CHANGELOG.md +167 -0
- package/LICENSE +202 -0
- package/README.md +2092 -0
- package/dist/chunk-R5JREBXQ.js +211 -0
- package/dist/chunk-WAXVZKT5.js +5363 -0
- package/dist/cli.js +135 -0
- package/dist/index.js +287 -0
- package/dist/relay.js +919 -0
- package/package.json +80 -0
- package/skills/automate-browser/SKILL.md +181 -0
- package/skills/automate-browser/references/capture-and-diagnostics.md +486 -0
- package/skills/automate-browser/references/page-interaction.md +237 -0
- package/skills/automate-browser/references/reading-and-extraction.md +113 -0
- package/skills/automate-browser/references/sessions-and-state.md +186 -0
- package/skills/automate-browser/references/tabs-and-multi-agent.md +156 -0
- package/skills/automate-browser/references/tool-reference.md +236 -0
- package/skills/automate-browser/references/troubleshooting.md +136 -0
|
@@ -0,0 +1,486 @@
|
|
|
1
|
+
# Screenshots, logs and diagnostics
|
|
2
|
+
|
|
3
|
+
## Contents
|
|
4
|
+
|
|
5
|
+
| Section | What it answers |
|
|
6
|
+
|---|---|
|
|
7
|
+
| [Screenshots](#screenshots) | Viewport, full page, one element, to a file |
|
|
8
|
+
| [A strip of stills](#a-strip-of-stills-when-one-picture-is-not-enough) | Several frames on a timer, and how fast they really are |
|
|
9
|
+
| [Background tabs and the banner](#screenshotting-a-tab-the-user-is-not-looking-at) | Why a banner sometimes appears |
|
|
10
|
+
| [Console logs](#console-logs) | What the page printed, including before you arrived |
|
|
11
|
+
| [The issues feed](#the-issues-feed-the-failures-with-no-console-error) | Failures that produce no console error |
|
|
12
|
+
| [Accessibility](#accessibility-a-floor-not-a-pass) | Auditing the page, and what an audit cannot tell you |
|
|
13
|
+
| [Network](#network) | The request list, and one request's body |
|
|
14
|
+
| [The footers](#the-footers-on-every-action) | The counts appended to each action |
|
|
15
|
+
| [Performance](#performance) | This machine, and what real visitors get |
|
|
16
|
+
| [Memory](#memory-is-this-page-leaking) | Watching the JS heap, and the snapshot DevTools opens |
|
|
17
|
+
| [Emulation](#emulation) | Pretending to be somewhere or something else |
|
|
18
|
+
| [Advanced mode](#advanced-mode-and-what-it-costs) | When the debugger is worth it |
|
|
19
|
+
| [Recipe: a slow page](#recipe-this-page-is-slow) | The order to diagnose it in |
|
|
20
|
+
| [Recipe: an accessibility audit](#recipe-auditing-a-page-for-accessibility) | Running one, and reporting it honestly |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## Screenshots
|
|
25
|
+
|
|
26
|
+
```
|
|
27
|
+
browser_screenshot // the visible viewport
|
|
28
|
+
browser_screenshot { fullPage: true } // the whole scrollable page
|
|
29
|
+
browser_screenshot { ref: "e12" } // one element, cropped
|
|
30
|
+
browser_screenshot { filePath: "./shot.png" } // to disk, not into your context
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
`format` takes `png` (default), `jpeg` or `webp`; `quality` applies to both lossy formats, not to png.
|
|
34
|
+
All three work with `fullPage` as well as the viewport.
|
|
35
|
+
|
|
36
|
+
**Photograph the element, not the page.** This is the single most expensive habit an agent has with
|
|
37
|
+
this server, and it is measurable: across 445 real captures, a viewport picture averaged **~1,531
|
|
38
|
+
tokens** and only **20** of those captures scoped themselves to one element. A picture does not leave
|
|
39
|
+
your context when you are finished with it — it is re-read on every turn for the rest of the session,
|
|
40
|
+
so ten page captures in a long session cost more than every other browser call put together.
|
|
41
|
+
|
|
42
|
+
So before capturing, ask what you are actually checking:
|
|
43
|
+
|
|
44
|
+
| What you want to know | Reach for |
|
|
45
|
+
|---|---|
|
|
46
|
+
| Did this one button / field / card render right? | `browser_screenshot { ref }` — a fraction of a page |
|
|
47
|
+
| Is this text on the page, is this value right? | `browser_eval` or `browser_find` — **no picture at all** |
|
|
48
|
+
| What is the overall layout doing? | a plain viewport capture, once |
|
|
49
|
+
| A record for a human to look at later | `filePath` — it never enters your context |
|
|
50
|
+
|
|
51
|
+
**Use `filePath` whenever you do not need to look at the image yourself.** An inlined screenshot is
|
|
52
|
+
one of the most expensive things you can put in a reply.
|
|
53
|
+
|
|
54
|
+
**An inline capture is held under 1536 x 4096 device pixels and may come back downscaled.** The
|
|
55
|
+
aspect ratio is kept, and the reply tells you the size before and after whenever it happened. **Do not
|
|
56
|
+
measure page coordinates off an image that says it was downscaled** — read them off the smaller image,
|
|
57
|
+
or take the capture again with `filePath`, which is never downscaled. The user's
|
|
58
|
+
`AUTOMATE_BROWSER_SCREENSHOT_MAX_WIDTH` / `..._MAX_HEIGHT` set the box, and `0` switches either half
|
|
59
|
+
off.
|
|
60
|
+
|
|
61
|
+
**For a smaller FILE, ask for `format: "webp"` with a `quality` — not a smaller size.** Downscaling a
|
|
62
|
+
screenshot and re-encoding it as PNG makes the file *bigger* (measured: a 332 KB 2K capture becomes
|
|
63
|
+
529 KB at 1536 wide, against 74 KB as webp at quality 60), because a screenshot is flat colour and
|
|
64
|
+
sharp edges that PNG already compresses well.
|
|
65
|
+
|
|
66
|
+
**Three things to know about `ref` crops.** The first two were measured on a 1.5x display on
|
|
67
|
+
2026-09-02:
|
|
68
|
+
|
|
69
|
+
- The crop is in **device** pixels, so on a scaled or Retina screen a 300x150 element comes back
|
|
70
|
+
450x225. That is correct, not a bug — do not "correct" it back to CSS pixels.
|
|
71
|
+
- When the element's size lands on a **half** device pixel the crop rounds outward, so up to one
|
|
72
|
+
pixel of the surrounding page can show at the right and bottom edges. Rounding the other way would
|
|
73
|
+
shave the element instead.
|
|
74
|
+
- **A ref from a SAME-ORIGIN frame crops fine**, at any depth: the element's position is measured
|
|
75
|
+
inside its frame and translated up through each parent. A ref from a **cross-origin** frame is
|
|
76
|
+
**refused** — that chain cannot be walked across an origin boundary, the capture covers the whole
|
|
77
|
+
tab, and cropping anyway would hand you a confidently wrong region. Take the screenshot without
|
|
78
|
+
`ref` and find the frame's area in the full picture.
|
|
79
|
+
|
|
80
|
+
**Judging a crop by eye is unreliable on a busy page.** Overlapping text from behind the element will
|
|
81
|
+
appear inside the crop because it genuinely renders there, which looks exactly like a mis-aligned
|
|
82
|
+
crop. If you need to be sure, give the element a temporary border, or check against a background
|
|
83
|
+
colour that cannot occur inside it.
|
|
84
|
+
|
|
85
|
+
## A strip of stills, when one picture is not enough
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
browser_screenshot { frames: 10, intervalMs: 150, filePath: "./out/strip.png" }
|
|
89
|
+
// → out/strip-01.png … out/strip-10.png, plus one summary line. Never the images.
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
For showing a **person** what happened — a transition, a flicker, a flow that scrolled past. Up to 30
|
|
93
|
+
frames, at least 100 ms apart. `filePath` is required: a strip is written to disk, never returned as
|
|
94
|
+
pictures, and asking for frames without one is refused.
|
|
95
|
+
|
|
96
|
+
**Check the achieved interval in the reply before you trust the strip.** It reports both what you
|
|
97
|
+
asked for and what it got, and they differ enormously depending on one thing:
|
|
98
|
+
|
|
99
|
+
| The tab is | You get | Good for |
|
|
100
|
+
|---|---|---|
|
|
101
|
+
| In the **foreground** | ~110 ms per frame | A fade, a transition, anything fast |
|
|
102
|
+
| In the **background** (the default) | **~3.9 seconds per frame** | Slow changes only |
|
|
103
|
+
|
|
104
|
+
Chrome does not draw a tab nobody is looking at, so each frame waits for one to be rendered. **Your
|
|
105
|
+
tab is a background tab unless you did something about it.** For a strip of something moving, call
|
|
106
|
+
`browser_switch_tab` first — it takes the user's focus, so ask, or accept the slow strip. The reply
|
|
107
|
+
tells you when this bit.
|
|
108
|
+
|
|
109
|
+
Two more things, neither of them optional to know:
|
|
110
|
+
|
|
111
|
+
- **It is stills, not video.** No audio, no file to play. Open them in order.
|
|
112
|
+
- **The debugger banner shows for the whole strip**, foreground or not, because the cheap capture path
|
|
113
|
+
is capped by Chrome at 2 frames per second. It attaches once and detaches at the end.
|
|
114
|
+
|
|
115
|
+
## Screenshotting a tab the user is not looking at
|
|
116
|
+
|
|
117
|
+
This works, and it does **not** bring the tab forward. How it works is worth knowing because it has a
|
|
118
|
+
visible side effect.
|
|
119
|
+
|
|
120
|
+
The cheap capture the browser offers photographs *whatever is on screen* — the foreground tab of a
|
|
121
|
+
window. Aimed at a background tab it would return the wrong page; aimed at a window the operating
|
|
122
|
+
system is not drawing it returns a **stale or blank frame with no error at all**. So when your tab is
|
|
123
|
+
not the foreground tab of a drawn window, the server does not use that path. It renders your exact tab
|
|
124
|
+
through the debugger instead.
|
|
125
|
+
|
|
126
|
+
- **The cost:** the browser shows its "being debugged" banner for the duration, then detaches. This is
|
|
127
|
+
the same mechanism `fullPage: true` has always used.
|
|
128
|
+
- **The result says so** — it comes back flagged, with the reason. If a user asks why a banner
|
|
129
|
+
flashed up, that is why.
|
|
130
|
+
- **If the debugger cannot attach** — a restricted page, or policy forbids it — the call **fails with
|
|
131
|
+
that reason named**. It never quietly falls back to photographing whatever was on screen. A refusal
|
|
132
|
+
here is the tool protecting you from a wrong answer.
|
|
133
|
+
- **The user having DevTools open does NOT stop you.** Measured in Edge 152 on 2026-09-02: a
|
|
134
|
+
background tab with the DevTools panel open attached and captured correctly. Chromium allows
|
|
135
|
+
several debugger clients on one tab. This page said the opposite until that date, and the tool's
|
|
136
|
+
own error offered "close DevTools" as the first remedy — advice that could never have helped.
|
|
137
|
+
One caveat worth knowing: what you get back is **what that tab is actually rendering**, so if
|
|
138
|
+
their DevTools is in device-emulation mode you will receive the emulated phone-sized page, which is
|
|
139
|
+
correct but probably not what you expected.
|
|
140
|
+
- **It can stall, and stalling is not failing.** Chrome stops drawing a tab nobody is looking at, so
|
|
141
|
+
a capture sometimes waits on a frame that never comes. The tool nudges the page awake and tries
|
|
142
|
+
again on its own; if both attempts stall you get `CAPTURE_STALLED`, which is **marked retryable
|
|
143
|
+
because a screenshot changes nothing** — simply ask again. Measured before this handling existed:
|
|
144
|
+
3 stalls in 8 captures of a background tab, every one of which succeeded on a retry. Re-measured
|
|
145
|
+
with it in place on 2026-09-02: **0 stalls in 11**, including two captures taken after the tab had
|
|
146
|
+
sat hidden and idle for 75 seconds. So treat `CAPTURE_STALLED` as rare rather than routine — if you
|
|
147
|
+
see it twice in a row on the same tab, that is worth reporting, not just retrying.
|
|
148
|
+
|
|
149
|
+
## Console logs
|
|
150
|
+
|
|
151
|
+
`browser_get_console_logs` returns what the page printed, including uncaught errors with their stacks
|
|
152
|
+
and service-worker lifecycle events.
|
|
153
|
+
|
|
154
|
+
`includePreserved: true` also returns the **previous pages'** logs — the answer to "it errored, then
|
|
155
|
+
redirected, and now I cannot see it". Logs survive the browser shutting the extension down to save
|
|
156
|
+
memory.
|
|
157
|
+
|
|
158
|
+
You get the **50 newest entries**. If there are more, a footer names the page you are on, the total,
|
|
159
|
+
and the exact call for the next one — and `page: 2` goes *further back in time*, not forward. Stacks
|
|
160
|
+
are printed for the thrown errors **on the page you asked for**, so an error further back needs its
|
|
161
|
+
page fetched.
|
|
162
|
+
|
|
163
|
+
## The issues feed: the failures with no console error
|
|
164
|
+
|
|
165
|
+
`browser_issues` is the only tool that sees problems the console never mentions:
|
|
166
|
+
|
|
167
|
+
- content blocked by a security policy
|
|
168
|
+
- a dropped third-party cookie
|
|
169
|
+
- mixed content
|
|
170
|
+
- a CORS refusal
|
|
171
|
+
- browser interventions
|
|
172
|
+
- failed and 4xx/5xx requests
|
|
173
|
+
|
|
174
|
+
**When something "works by hand but not here", this is usually why.** Reach for it before you start
|
|
175
|
+
theorising.
|
|
176
|
+
|
|
177
|
+
## Accessibility: a floor, not a pass
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
browser_snapshot // FIRST — this is what creates the refs
|
|
181
|
+
browser_issues { audit: "a11y" } // violations, worst first
|
|
182
|
+
browser_issues { audit: "a11y", page: 2 }
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
Runs **axe-core** against the page you are on. Violations come back grouped by impact — **critical,
|
|
186
|
+
serious, moderate, minor** — with the number of elements each rule matched, up to **five examples**
|
|
187
|
+
each, and a link to that rule's fix guidance. **20 rules per page** by default (`limit` changes it),
|
|
188
|
+
worst first; the footer names the next call.
|
|
189
|
+
|
|
190
|
+
**Snapshot first.** Refs are written onto the page by `browser_snapshot`, so an audit run before any
|
|
191
|
+
snapshot reports CSS selectors and no refs. With a ref you can hand the failing element straight to
|
|
192
|
+
`browser_click`, `browser_get_html` or `browser_eval`; without one you are reading a selector. A
|
|
193
|
+
finding is never dropped for lacking a ref.
|
|
194
|
+
|
|
195
|
+
**Say the limit out loud when you report the result. Automated rules catch roughly a third of real
|
|
196
|
+
accessibility barriers.** They check that attributes exist, never that they are right — no rule here
|
|
197
|
+
can tell whether alt text describes its image, whether the focus order is sensible, or whether a
|
|
198
|
+
custom widget can actually be operated from the keyboard. **Never report "0 violations" as
|
|
199
|
+
"accessible."** Report it as what it is: the automatable third found nothing.
|
|
200
|
+
|
|
201
|
+
Rules axe could not decide alone are **counted at the end, not listed** — a contrast check over a
|
|
202
|
+
background image, say. If that count is high, the page needs a person, not another call.
|
|
203
|
+
|
|
204
|
+
No debugger and no banner, and it leaves nothing behind on the page.
|
|
205
|
+
|
|
206
|
+
## Network
|
|
207
|
+
|
|
208
|
+
- **`browser_network_requests`** lists what the current page requested — method, URL, status, type.
|
|
209
|
+
`resourceTypes` filters; `includePreserved` reaches back through a redirect. You get the **50
|
|
210
|
+
newest** per call (`limit` sets the page size); `page: 2` is the 50 *before* those, and a footer
|
|
211
|
+
names the next call when more remain.
|
|
212
|
+
- **`browser_get_network_request { url | requestId }`** returns one request's **response body**, plus
|
|
213
|
+
its request and response headers. Needs advanced mode. When a URL substring matches several
|
|
214
|
+
requests, it returns the newest and lists the others so you can address the one you meant by
|
|
215
|
+
`requestId`. Header values that look like credentials come back `<redacted>`; that is the
|
|
216
|
+
[redaction rule](./sessions-and-state.md#cookies-and-storage), and `revealValues: true` opts out.
|
|
217
|
+
|
|
218
|
+
## The footers on every action
|
|
219
|
+
|
|
220
|
+
Mutating actions come back with a short footer counting **new console errors** and **new browser
|
|
221
|
+
issues** caused by that action. It is the cheapest possible signal that a click that reported success
|
|
222
|
+
actually broke something.
|
|
223
|
+
|
|
224
|
+
It only counts what is *new* since your last look, so an error the page logged before you arrived is
|
|
225
|
+
never blamed on you. Set `AUTOMATE_BROWSER_DELTA_FOOTER=off` to silence it.
|
|
226
|
+
|
|
227
|
+
For detail in the same reply, ask for it:
|
|
228
|
+
|
|
229
|
+
```
|
|
230
|
+
browser_click { ..., include: "console, network" }
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
A section that cannot be fetched comes back labelled `(unavailable: ...)` and changes nothing else —
|
|
234
|
+
the action still succeeded, and any other section you asked for still arrives. A section NAME you get
|
|
235
|
+
wrong is the opposite case: it is refused **before** the action runs, so nothing happened and the
|
|
236
|
+
corrected call is safe to make.
|
|
237
|
+
|
|
238
|
+
## Performance
|
|
239
|
+
|
|
240
|
+
- **`browser_perf_trace`** records a trace of this machine on this run and returns **Core Web Vitals**
|
|
241
|
+
— LCP, FCP, CLS, INP — rated against Google's thresholds, plus the long tasks that blocked the main
|
|
242
|
+
thread. Needs advanced mode. `action: "analyze"` re-reads a saved trace later with no browser at
|
|
243
|
+
all.
|
|
244
|
+
- **It also breaks the LCP down and names the cause.** Where the vitals tell you *that* a page was
|
|
245
|
+
slow, the breakdown tells you *which part* was: time to first byte, resource load delay, resource
|
|
246
|
+
load time and render delay, each with its share of the total, followed by one line naming the cause
|
|
247
|
+
and one naming the fix. Read the biggest span first — the advice for a slow server and the advice
|
|
248
|
+
for a late image have nothing in common, so acting on the total alone is guesswork.
|
|
249
|
+
- **Render-blocking resources are listed too** — the requests that finished before first paint and
|
|
250
|
+
held it up, slowest first. An async script is deliberately *not* listed: it is already doing the
|
|
251
|
+
right thing, and reporting it would be advice to break working code.
|
|
252
|
+
- **A span the trace cannot support is named, not omitted.** A text LCP has no resource to download,
|
|
253
|
+
so it correctly shows two spans rather than four zeroes; an image whose request cannot be matched
|
|
254
|
+
says so; and a trace with no document-request timing says that instead of reporting a breakdown it
|
|
255
|
+
cannot stand behind. **If you see one of those lines, the number you wanted was not measured** —
|
|
256
|
+
record the load itself with `{ action: "start", reload: true, autoStop: true }` rather than reading
|
|
257
|
+
a gap as good news.
|
|
258
|
+
- **`browser_perf_field_data { url }`** answers "how fast is this for *real* visitors", from Google's
|
|
259
|
+
public Chrome UX Report. It needs no browser — but it **sends the URL you ask about to a Google
|
|
260
|
+
API**, and needs `AUTOMATE_BROWSER_CRUX_KEY` set. It is the only outbound call this server makes.
|
|
261
|
+
|
|
262
|
+
Use them together: the trace tells you what this machine did, the field data tells you whether that
|
|
263
|
+
resembles reality.
|
|
264
|
+
|
|
265
|
+
## Memory: is this page leaking?
|
|
266
|
+
|
|
267
|
+
Two actions on the same tool, split by how much each can honestly tell you.
|
|
268
|
+
|
|
269
|
+
```
|
|
270
|
+
browser_perf_trace { action: "memory" } // watch the heap for 5s
|
|
271
|
+
browser_perf_trace { action: "memory", durationMs: 30000 } // watch it for 30s
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
**`action: "memory"`** samples `performance.memory.usedJSHeapSize` every 500 ms across the window and
|
|
275
|
+
reports start, end, every reading, and a **least-squares trend in MB/s**. The trend is a fit, not
|
|
276
|
+
end-minus-start, because the heap saws: a collection mid-window drops it a long way, and a leaking
|
|
277
|
+
page can easily finish *lower* than it started.
|
|
278
|
+
|
|
279
|
+
- **It needs no advanced mode** — no debugger, no banner. This is the one performance action you can
|
|
280
|
+
run on a tab a person is looking at without changing anything for them.
|
|
281
|
+
- `durationMs` is **1000-30000**, default 5000, and out-of-range is **refused, not clamped**. For a
|
|
282
|
+
longer watch, call it again between your own interactions.
|
|
283
|
+
- The interval is fixed at 500 ms and is not an argument. Chrome quantises the reading into coarse
|
|
284
|
+
buckets, so sampling faster buys more points off the same staircase, not more resolution.
|
|
285
|
+
- **You will often get fewer samples than 500 ms implies, and that is fine.** Chrome throttles timers
|
|
286
|
+
in a tab it is not drawing to about once a second, and your tab is a background tab. The reply says
|
|
287
|
+
the cadence it **achieved**, and the trend is computed from the real elapsed time the page measured
|
|
288
|
+
— so the MB/s figure is right either way. `browser_switch_tab` is what changes it.
|
|
289
|
+
- **Chrome-family only.** `performance.memory` does not exist in Firefox or Safari; there the call
|
|
290
|
+
fails and says so. Verified working on **both Chrome and Edge** — the whole integration suite
|
|
291
|
+
passes 66/66 on each.
|
|
292
|
+
|
|
293
|
+
**The sentence that matters: a rising heap is not proof of a leak.** It may simply be memory a
|
|
294
|
+
collection has not reclaimed yet. The reply says so every time. To go from "rising" to "leaking",
|
|
295
|
+
take two snapshots.
|
|
296
|
+
|
|
297
|
+
**There is no heap snapshot here, and there cannot be.** It was built and Chrome refused it:
|
|
298
|
+
`chrome.debugger` exposes a fixed allow-list of DevTools Protocol domains and `HeapProfiler` is not on
|
|
299
|
+
it — the CPU `Profiler` is, the heap one is not. No extension can capture one. To find *what* grew,
|
|
300
|
+
a person opens **DevTools → Memory**, takes two snapshots and uses the comparison view. Ask for that
|
|
301
|
+
rather than looking for a tool.
|
|
302
|
+
|
|
303
|
+
## Emulation
|
|
304
|
+
|
|
305
|
+
`browser_emulate` fakes a location, a user agent, extra headers, a colour scheme, a mobile viewport
|
|
306
|
+
with touch, a throttled network, or a slow CPU. `clear` takes them back off.
|
|
307
|
+
|
|
308
|
+
Everything here is **per tab** and evaporates with the tab — except `browser_proxy`, which changes the
|
|
309
|
+
browsing of the human sharing the browser. Treat that one as an interruption to a person, not a
|
|
310
|
+
setting.
|
|
311
|
+
|
|
312
|
+
## Advanced mode, and what it costs
|
|
313
|
+
|
|
314
|
+
`browser_advanced_mode { enable: true }` attaches the Chrome debugger to your tab. While attached, the
|
|
315
|
+
browser shows a banner saying so.
|
|
316
|
+
|
|
317
|
+
**Required for:** `browser_upload_file`, `browser_get_network_request`, `browser_perf_trace` (all
|
|
318
|
+
actions except `memory`, which is debugger-free), some `browser_emulate` options, and
|
|
319
|
+
`browser_navigate`'s `initScript` / `handleBeforeUnload`.
|
|
320
|
+
|
|
321
|
+
**Attached for you automatically, briefly, by:** `fullPage: true` screenshots, and screenshots of a
|
|
322
|
+
background tab.
|
|
323
|
+
|
|
324
|
+
**While attached, a `browser_click` on a CROSS-origin frame's ref is refused** — the trusted click
|
|
325
|
+
path drives one debugger session and cannot reach another origin's frame. Turn advanced mode off and
|
|
326
|
+
the default click reaches it, because that one injects into the frame directly. A same-origin frame
|
|
327
|
+
works either way, with the frame's offset applied so the click lands on the element rather than on
|
|
328
|
+
whatever sits at that point in the top page.
|
|
329
|
+
|
|
330
|
+
**It does not change what your other calls return.** With it on, clicks and key presses are dispatched
|
|
331
|
+
as real OS-level input instead of synthetic events, but they still wait for the page to settle and
|
|
332
|
+
still report `navigated`, `elapsedMs` and — for a coordinate click — the `hit` naming what was under
|
|
333
|
+
the point. Until 2026-09-01 they did not, so turning this on for an unrelated reason quietly changed
|
|
334
|
+
both the shape and the timing of every click.
|
|
335
|
+
|
|
336
|
+
**But it makes clicking impossible on a background tab, which is where you normally work.** Chrome
|
|
337
|
+
**discards** real input aimed at a tab it is not drawing. So with advanced mode on, `browser_click`
|
|
338
|
+
and `browser_press_key` **refuse outright** on your background tab and tell you to bring it forward or
|
|
339
|
+
turn the mode off — they never report a success that did not happen. The practical rule: enable
|
|
340
|
+
advanced mode for what needs it (a response body, a trace, an upload), then **turn it off again before
|
|
341
|
+
you interact**. The default path works perfectly in a hidden tab; the trusted one cannot.
|
|
342
|
+
|
|
343
|
+
Everything else is debugger-free by design — that is the point of this server. Turn it off when done:
|
|
344
|
+
`browser_advanced_mode { enable: false }`. A tool that needs it and does not have it says so with
|
|
345
|
+
`ADVANCED_MODE_REQUIRED` rather than failing obscurely.
|
|
346
|
+
|
|
347
|
+
### Reaching a staging site with a bad certificate
|
|
348
|
+
|
|
349
|
+
**This does not work, and you should not spend a call finding that out.** There is no argument for
|
|
350
|
+
it. `acceptInsecureCerts` existed until 2026-09-16 and never worked on any build: extensions get a
|
|
351
|
+
fixed list of debugger domains and the one it needed is not on it. It was deleted rather than kept
|
|
352
|
+
as a permanent error charged to every request you make.
|
|
353
|
+
|
|
354
|
+
**If you pass it anyway the call is refused by name**, not quietly ignored — so an older habit costs
|
|
355
|
+
you one error rather than a false belief that certificate checking is off.
|
|
356
|
+
|
|
357
|
+
**What to tell the user instead**, because this is theirs to fix and it takes them ten seconds:
|
|
358
|
+
|
|
359
|
+
- **Click through the warning page once, by hand.** Chrome remembers that host for the session, and
|
|
360
|
+
every AutomateBrowser tool then works against it normally. This is almost always the right answer.
|
|
361
|
+
- **Or start the browser with `--ignore-certificate-errors`** if it is going to keep happening.
|
|
362
|
+
|
|
363
|
+
**Do not retry, and do not look for another way round it** — there is not one from inside an extension.
|
|
364
|
+
Say plainly that the site's certificate is being rejected, name which of the two fixes you want, and
|
|
365
|
+
wait. A certificate warning is exactly the thing a person should look at once rather than have an agent
|
|
366
|
+
silently bypass.
|
|
367
|
+
|
|
368
|
+
One related fact is unchanged: a tab parked on the warning page refuses a debugger attachment at all.
|
|
369
|
+
|
|
370
|
+
## Recipe: "this page is slow"
|
|
371
|
+
|
|
372
|
+
Work outwards from the cheapest evidence. Steps 1 and 2 need no debugger and no banner.
|
|
373
|
+
|
|
374
|
+
**1. Ask whether it is slow for anyone else.**
|
|
375
|
+
|
|
376
|
+
```
|
|
377
|
+
browser_perf_field_data { url: "https://example.com/page" }
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
Real visitors' vitals from Google's Chrome UX Report, with no browser involved. If the field data is
|
|
381
|
+
green and your run is not, you are measuring **this machine on this network**, not the site. Say that
|
|
382
|
+
before you go further. (Needs `AUTOMATE_BROWSER_CRUX_KEY`, and it sends the URL to a Google API.)
|
|
383
|
+
|
|
384
|
+
**2. Check for things failing silently.**
|
|
385
|
+
|
|
386
|
+
```
|
|
387
|
+
browser_issues
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
A blocked script, a CORS refusal or a 4xx on a render-blocking resource makes a page slow *and* prints
|
|
391
|
+
nothing in the console. Rule this out before you profile — it is the cheapest call here and it is
|
|
392
|
+
often the whole answer.
|
|
393
|
+
|
|
394
|
+
**3. Profile one full page load, in a single call.**
|
|
395
|
+
|
|
396
|
+
```
|
|
397
|
+
browser_advanced_mode { enable: true }
|
|
398
|
+
browser_perf_trace { action: "start", reload: true, autoStop: true, filePath: "./trace.json" }
|
|
399
|
+
```
|
|
400
|
+
|
|
401
|
+
`reload` + `autoStop` records from before navigation until loading finishes and returns LCP, FCP, CLS
|
|
402
|
+
and INP rated against Google's thresholds, plus the long tasks that blocked the main thread, **the LCP
|
|
403
|
+
breakdown, and the render-blocking resources**. **Pass `filePath`** — the path is checked before
|
|
404
|
+
recording starts, and it lets you re-read the trace later with `{ action: "analyze", filePath }`
|
|
405
|
+
instead of recording again.
|
|
406
|
+
|
|
407
|
+
**4. Read the LCP breakdown before you call anything else.**
|
|
408
|
+
|
|
409
|
+
It has already done the step this recipe used to send you off to do. Four spans, each with its share
|
|
410
|
+
of the total, and the cause and fix lines name the biggest one:
|
|
411
|
+
|
|
412
|
+
| Biggest span | What it means | What to do |
|
|
413
|
+
|---|---|---|
|
|
414
|
+
| **time to first byte** | The server was still thinking; nothing on the page could start | Nothing in the browser will fix this — it is the request handler or the cache |
|
|
415
|
+
| **resource load delay** | The image was discovered late, not downloaded slowly | Look at how it is referenced: a preload or a plain `<img>` in the initial HTML |
|
|
416
|
+
| **resource load time** | The image itself is too big for the connection | `browser_network_requests` for its size, then a smaller format |
|
|
417
|
+
| **render delay** | The bytes arrived and the page still could not paint | The render-blocking list right below it, and the long tasks above |
|
|
418
|
+
|
|
419
|
+
**5. Let any remaining failing vital pick the next call.**
|
|
420
|
+
|
|
421
|
+
| Vital | What it means | Next call |
|
|
422
|
+
|---|---|---|
|
|
423
|
+
| **CLS** high | The layout moved after paint | `browser_screenshot { frames: 8, intervalMs: 150, filePath: … }` — see the jump |
|
|
424
|
+
| **INP** slow, or long tasks | The main thread was blocked | The trace's long-task list already names the durations |
|
|
425
|
+
|
|
426
|
+
**6. If it is only slow after a while of use, it is a different question.** That is
|
|
427
|
+
`browser_perf_trace { action: "memory" }`, in the memory section above — and that one needs no
|
|
428
|
+
debugger at all.
|
|
429
|
+
|
|
430
|
+
**7. Turn advanced mode off before you interact again.**
|
|
431
|
+
|
|
432
|
+
```
|
|
433
|
+
browser_advanced_mode { enable: false }
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
With it on, `browser_click` and `browser_press_key` **refuse** on a background tab, which is where you
|
|
437
|
+
normally work. Profiling is the only thing you needed it for.
|
|
438
|
+
|
|
439
|
+
## Recipe: auditing a page for accessibility
|
|
440
|
+
|
|
441
|
+
**1. Snapshot first — this is not optional.**
|
|
442
|
+
|
|
443
|
+
```
|
|
444
|
+
browser_snapshot
|
|
445
|
+
browser_issues { audit: "a11y" }
|
|
446
|
+
```
|
|
447
|
+
|
|
448
|
+
Refs are written onto the page by the snapshot. An audit run before any snapshot still reports every
|
|
449
|
+
finding, but names them with CSS selectors, so you cannot hand a failing element to another tool.
|
|
450
|
+
|
|
451
|
+
**2. Page through, worst first.** Twenty rules per page, most severe first, and the footer gives you
|
|
452
|
+
the exact next call. Stop when the impact drops below what you were asked to care about — you rarely
|
|
453
|
+
need the `minor` pages.
|
|
454
|
+
|
|
455
|
+
**3. Read the real markup before you propose a fix.**
|
|
456
|
+
|
|
457
|
+
```
|
|
458
|
+
browser_get_html { ref: "e34" }
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
The rule tells you what axe checked; the markup tells you why it failed. Guessing between those two is
|
|
462
|
+
how a fix gets proposed for something that is not actually broken.
|
|
463
|
+
|
|
464
|
+
**4. Audit dark mode too, if the site has one.** Contrast is the most common failure and it is
|
|
465
|
+
theme-specific, so a light-mode pass says nothing about the other theme:
|
|
466
|
+
|
|
467
|
+
```
|
|
468
|
+
browser_advanced_mode { enable: true }
|
|
469
|
+
browser_emulate { colorScheme: "dark" }
|
|
470
|
+
browser_snapshot
|
|
471
|
+
browser_issues { audit: "a11y" }
|
|
472
|
+
browser_emulate { clear: ["colorScheme"] }
|
|
473
|
+
browser_advanced_mode { enable: false }
|
|
474
|
+
```
|
|
475
|
+
|
|
476
|
+
`colorScheme` needs advanced mode — `prefers-color-scheme` cannot be overridden from page JavaScript
|
|
477
|
+
at all. Reading and auditing still work fine while it is attached; only clicking on a background tab
|
|
478
|
+
does not.
|
|
479
|
+
|
|
480
|
+
**5. Report the count of rules needing human review**, printed at the end of the audit. A high number
|
|
481
|
+
means the page needs a person, not another call.
|
|
482
|
+
|
|
483
|
+
**6. Never write "accessible".** Automated rules catch roughly a third of real barriers, and they
|
|
484
|
+
check that attributes exist rather than that they are right. The honest sentence is *"the automatable
|
|
485
|
+
third found nothing"* — followed by what a person still has to check: keyboard traversal, focus order,
|
|
486
|
+
whether the alt text actually describes the image.
|