1.x 存在远程代码执行等已知漏洞且不再维护,必须升级到 2.0;Node 版本低于 22.19 会无法运行;需在目标页面打开 DevTools(F12)才开始采集,BrowserTools 面板只用于设置和状态;无 DevTools 的标签页工具需在弹窗中打开开关,导航和软输入还需 --allow-input 或 --allow-actions
browser-tools-mcp
AgentDeskAIMIT活跃维护把浏览器日志直接接入 Cursor 等支持 MCP 的 IDE,让 AI 编程助手能实时看到页面报错和网络请求,排查前端问题时不用来回切窗口。
Monitor browser logs directly from Cursor and other MCP compatible IDEs.
在 MCP 客户端配置:{"mcpServers":{"browser-tools":{"command":"npx","args":["-y","@agentdeskai/browser-tools-mcp@latest"]}}}
然后在 chrome://extensions 开启开发者模式,加载仓库的 chrome-extension 目录README 未说明显存或 GPU 要求;需本机安装 Chrome 并加载扩展,MCP 服务端需 Node 22.19 或更新版本
把真实 Chrome 会话的日志、网络和截图直接喂给 Cursor 等 MCP 客户端,省去重建登录态
把浏览器日志直接接入 Cursor 等支持 MCP 的 IDE,让 AI 编程助手能实时看到页面报错和网络请求,排查前端问题时不用来回切窗口。
核心能力
- 通过 DevTools 扩展附着已登录的 Chrome 会话,无需新建自动化浏览器
- 流式采集控制台日志、XHR/fetch 网络请求、截图与 Lighthouse 审计
- 日志工具支持关键词过滤与 limit/offset 分页,结果按最新优先返回
- 大体积历史数据以 MCP resources 暴露,避免撑爆上下文窗口
- 多标签页分别归属遥测,结果附带 tabId 与 url 便于核对来源
容易踩的坑
- v2.0.42.0.4最新正式版2026-10-27 · 2026-10-27 02:50 UTC
Low-severity hardening of the local connector, and the test runner is now Vitest 5.
- The token is a bearer token. The HTTP API and the extension WebSocket
accept it from
Authorization: Beareror, for the extension, a WebSocket subprotocol. A token in the URL is refused. The extension collects it fromPOST /.pair, which answers only a real browser-extension origin (chrome-extension://with a Chromium id, ormoz-extension:///safari-web-extension://with a UUID)./.identitystill does not include it.--extension-idlimits pairing and the extension socket to one extension. --onlyand--excludeapply to the local API, not only to the MCP tool list. A shared connector enforces the filters it was started with.- State-changing local API calls are refused by default. Reload,
screenshot, wipe, settings changes and tab selection need
--allow-actionsor the "Allow state-changing calls" checkbox in the BrowserTools panel. Closing DevTools withdraws the confirmation. Tools in the process that embeds the connector are not gated by this. - 1.x is unsupported. README and SECURITY.md tell anyone still on it to upgrade. The vulnerabilities fixed in 2.0 are not patched on 1.x.
Vitest 5 is a dev dependency. Node 22.12 or newer is required to run the tests, which the existing Node 22.19 floor already satisfies.
- The token is a bearer token. The HTTP API and the extension WebSocket
accept it from
- v2.0.32.0.3 — Security2026-10-26 · 2026-10-26 21:09 UTC
查看更新说明
Fixes a server-side request forgery in the four Lighthouse audit tools (
runAccessibilityAudit,runPerformanceAudit,runSEOAudit,runBestPracticesAudit), CWE-918, present in 2.0.0 through 2.0.2. Advisory: GHSA-fm7v-38h4-4q7w.The tools accept a
urland audited it in a headless Chrome on the user's machine after checking only that the scheme was http or https. A caller — in practice a model acting on prompt-injected page content — could point that browser at loopback services, the LAN or a cloud metadata endpoint, and the stored report (browser-tools://audit/{reportId}) handed the rendered page back as itsfinal-screenshot.- Audits no longer reach link-local, cloud metadata, or unvouched loopback
and private addresses. Link-local and metadata addresses (169.254.0.0/16,
metadata.google.internal, AWS IMDS over IPv6, Alibaba, and the rest) are never audited, in any spelling (Azure's WireServer at 168.63.129.16, which sits in public address space, included): IPv4-mapped, IPv4-compatible and IPv4-translated IPv6, NAT64, 6to4, decimal, hex and octal forms are all classified the same way. Every address on this machine's own network interfaces counts as this machine, so a service bound to0.0.0.0or::is not reachable through the machine's LAN or global IPv6 address either. Pages on localhost or a private network are audited only while that origin is open in a connected tab (the normal dev-server flow keeps working), or when allowlisted. A tab vouches only for a local name — an IP address,localhost, a single-label name, or a name under.test,.local,.internal,.lan,.home.arpaand the like — so a public site the user has open cannot rebind its own name to a LAN address and have that audited. A public name that points at a local address on purpose (lvh.me,nip.io) needs--audit-allow-origin. - Every request the audit browser makes is checked as it happens. The browser runs behind an in-process proxy that resolves and classifies each request — redirects, iframes, fetches and websockets included — and connects to the address it vetted, so a public page cannot redirect or frame its way inside, and a rebinding name cannot change address after the check. Lookups fail closed. WebRTC is kept off direct UDP, so it cannot go around the proxy either.
- A local page reaches only the local origins vouched for. Auditing
http://localhost:3000lets that page loadlocalhost:3000(and127.0.0.1:3000or[::1]:3000, either of which the name may reach) and the other origins open in a tab or allowlisted, not every loopback and private address, so a dev server's open redirect cannot carry the audit to another service. An address, or a*.localhostname, vouches for itself alone: a service bound only to[::1]:3000is not the one on127.0.0.1:3000, and a Host-routing proxy serves another site under each name. A page whose API runs on another local port loads it once that port is open in a tab or passed to--audit-allow-origin. A public page loads nothing local. - Fake-IP answers are refused. Behind a fake-IP proxy (Clash, mihomo,
sing-box or Surge in fake-ip mode) every name resolves to 198.18.0.0/15 and
the proxy picks the real address later, so where a name goes cannot be
checked, and loopback or LAN services would be reachable by name. Audits
refuse such answers, for the audited page and everything it loads, and the
error names the way out: turn fake-ip mode off while auditing, or pass
--audit-allow-private-network, which gives up checking local reach (see SECURITY.md). No tab or--audit-allow-originentry opens the range. - New flags
--audit-allow-origin <o,o>and--audit-allow-private-network(environment:BROWSER_TOOLS_AUDIT_ALLOW_ORIGINS,BROWSER_TOOLS_AUDIT_ALLOW_PRIVATE_NETWORK) for audits of local pages that are not open in a tab. Even with private access allowed, link-local and m
- Audits no longer reach link-local, cloud metadata, or unvouched loopback
and private addresses. Link-local and metadata addresses (169.254.0.0/16,
- v2.0.2v2.0.2 — five defects found while specifying 2.12026-08-12 · 2026-08-12 16:48 UTC
查看更新说明
Five defects, all found by writing the 2.1 capability specifications rather than by anything failing. Four were invisible in normal use, which is how they survived the 2.0 rewrite and its test suite.
No API changes. Upgrade with
npx @agentdeskai/browser-tools-mcp@latest.Fixed
-
HAR exports reported every request as starting when it finished. The capture stamps its timestamp at
onRequestFinished, and the HAR builder used that forstartedDateTime, so a two-second request looked instantaneous at the wrong moment. Requests now carrystartedAtfrom the DevTools entry's own start time, falling back to finish-minus-duration. Anything reading a HAR — including Chrome's own Network panel — was being misled. -
Reads came back in arrival order rather than event order. Telemetry is flushed in 100 ms batches per tab and buffered while the socket is down, so the order the connector receives entries genuinely differs from the order they happened. Queries slice the tail as "the newest", which could return the wrong entries outright and interleave two tabs wrongly on an
allTabsread. Reads are now ordered by event time. -
logLimitdefaulted to 50 entries per category per tab — less than a single real page load, so anything reading back over a session was silently clipped. Now 500. The configurable range is unchanged. -
The selected element was truncated in the page but never scrubbed there. Every console and network value goes through scrub-then-truncate; the selected element was sliced inside the page and sent as-is. The server still scrubbed on arrival, so nothing unredacted ever reached the model — but it crossed the socket unscrubbed, and truncating first is exactly the ordering that hid a JWT from the pattern meant to catch it in 1.x. That was the bug 2.0.0 fixed everywhere except here.
-
Audits ran one device and reported another.
formFactorandscreenEmulationwere set butthrottlingandemulatedUserAgentwere not, so a desktop audit ran a desktop viewport under Lighthouse's default mobile Slow-4G throttling while identifying itself as a phone, then reported"desktop"from a hardcoded field. Flags now come from Lighthouse's own presets so all three agree, and the report names the device it actually simulated.
Also
Lighthouse's device presets load on demand. Importing them eagerly cost about 30 ms at startup, on the path that answers
initialize.356 tests, up from 339.
-
- v2.0.0v2.0.0 — the security rewrite2026-08-10 · 2026-08-10 17:29 UTC
查看更新说明
Upgrade regardless of what else you want from 2.0 — 1.2.x contains a critical remote code execution vulnerability. See GHSA-xvrv-w8pg-f25f (CVSS 9.8).
2.0 is a rewrite. One process instead of three, no unauthenticated local server, credentials scrubbed before they leave the browser, and 400 tests where there were none.
Start here
- MIGRATION.md — what changes for you
- CHANGELOG.md — the full list
- browsertools.agentdesk.ai — installation and reference
{ "mcpServers": { "browser-tools": { "command": "npx", "args": ["-y", "@agentdeskai/browser-tools-mcp@latest"] } } }There is no second server to start. Reinstall the extension — 1.x and 2.0 are not compatible. Node 22.19+.
Security
The RCE is fixed by design change, not patch: loopback-only binding, a per-run bearer token, browser-extension origins only on the WebSocket,
Hostvalidation, and no captured value reaching a shell. The extension's private-network scan is gone. Credentials are redacted in the browser before anything is transmitted. All 24 vulnerable 1.x releases are deprecated on npm.Highlights
- Single process. The MCP server embeds the connector.
- Screenshots reach the model as images, within a byte budget that degrades PNG → JPEG → downscale rather than blowing past what a context can take.
- Multi-tab. Per-tab attribution and retention; every result reports its
tabIdandurl. - Resources. Full console/network history, HAR 1.2 export, screenshots and unabridged Lighthouse reports at
browser-tools://URIs, linked rather than inlined. - 16 tools with output schemas and annotations, plus keyword filters and paging. Guidance moved to MCP prompts.
--doctordiagnoses a broken setup;--verboseshows capture as it happens.- Audits work without Google Chrome — any Chromium-based browser.
Thanks
This release reimplements ideas from contributors whose PRs could not merge against rewritten code. Credit to @mirageN1349, @pntgoswami18, @ymrdf (stdio purity), @lukasvdberk (screenshots as images), @bryankthompson (tool annotations), @yj1438 (keyword filters), @isaiahbjork (
refreshBrowser), @zzh948498 (stringSizeLimit), @thepushkarp (browser storage), @bmacer (tool selection), @TargiX (undocked DevTools), @rogeriochaves (cross-browser design), @Jinsoo1004, @wer416182-afk, @xianzuyang9-blip.And to @notwo1f, @wing3e, @VitorLourenco and @AgentSeal, who reported the security issues and waited a long time for an answer.
Known limits
Network capture starts when DevTools opens — reload for a full page load. Firefox is built for but unverified; screenshots will not work there. Audits launch a separate browser and take up to a minute.
- v1.2.0v1.2.0 - SEO, Performance + NextJS Audits; Screenshot Auto-Paste; Bug fixes2025-03-10 · 2025-03-10 18:34 UTC
查看更新说明
-
You can now enable "Allow Auto-Paste into Cursor" within the DevTools panel. Screenshots will be automatically pasted into Cursor (just make sure to focus/click into the Agent input field in Cursor, otherwise it won't work!)
-
Integrated a suite of SEO, performance, accessibility, and best practice analysis tools via Lighthouse
-
Implemented a NextJS specific prompt used to improve SEO for a NextJS application
-
Added Debugger Mode as a tool which executes all debugging tools in a particular sequence, along with a prompt to improve reasoning
-
Added Audit Mode as a tool to execute all auditing tools in a particular sequence
-
Resolved Windows connectivity issues
-
Improved networking between BrowserTools server, extension and MCP server with host/port auto-discovery, auto-reconnect, and graceful shutdown mechanisms
-
Added ability to more easily exit out of the Browser Tools server with Ctrl+C
To start using this download the chrome extension from the zip file below and/or follow the instructions here: https://browsertools.agentdesk.ai/
Make sure to remove the older chrome extension and add this newly updated one.
Update to the latest MCP server version in Cursor, Windsurf or whatever client you're in to use this new version:
npx @agentdeskai/[email protected]Then run the usual command from your terminal to start the local node server:
npx @agentdeskai/browser-tools-server -
用户点评
登录用户的真实使用记录,可按版本和硬件筛选还没有人点评。第一个说出真实体验的人最有价值。
BrowserTools MCP
If you are still on 1.x, upgrade. 1.x is unsupported. It has known critical vulnerabilities (remote code execution, unauthenticated access to captured data) that were fixed in 2.0 and will not be patched on 1.x. See SECURITY.md and MIGRATION.md.
Give your AI coding agent eyes on the browser. BrowserTools MCP streams console output, network activity, screenshots and Lighthouse audits from your real Chrome session — the one already logged into your app — to any MCP-compatible client: Cursor, Claude Code, Windsurf, Cline, Zed, Gemini CLI, and others.
Version 2.0 is a rewrite. One process instead of three, no unauthenticated local server, credentials scrubbed before they leave the browser, and a real test suite. Coming from 1.x? Read MIGRATION.md.
Why this instead of a CDP-based server
Tools like Chrome DevTools MCP and Playwright MCP drive a fresh, automated browser. That is the right choice for writing tests. It is the wrong choice for debugging the app you are actually looking at, because since Chrome 136 the browser refuses remote debugging on your default profile — the one holding your logins. So you end up recreating your auth state in a throwaway profile before you can debug anything.
BrowserTools attaches to the session you are already in, through a DevTools extension. You stay logged in, on the page you were already on, and your agent reads what you see. It also reports Lighthouse-grade performance, accessibility and SEO data, which the automation-first servers do not.
Install
Two pieces: an MCP server (one command) and a Chrome extension.
1. Point your MCP client at the server
{
"mcpServers": {
"browser-tools": {
"command": "npx",
"args": ["-y", "@agentdeskai/browser-tools-mcp@latest"]
}
}
}
On Windows, if your client cannot find npx, use "command": "cmd" with
"args": ["/c", "npx", "-y", "@agentdeskai/browser-tools-mcp@latest"].
Requires Node 22.19 or newer. Check with node --version; if you use nvm or
asdf, make sure your editor inherits the same version.
2. Load the Chrome extension
- Download or clone this repository.
- Open
chrome://extensionsand turn on Developer mode. - Choose Load unpacked and select the
chrome-extensiondirectory.
That is the whole setup. There is no second server to start — the MCP server runs the connector itself.
3. Use it
Open Chrome DevTools (F12) on the page you want to inspect. Capture begins as soon as DevTools is open; the BrowserTools panel is only for settings and status. Then ask your agent something like "check the console for errors" or "run an accessibility audit on this page".
Not working? Run npx @agentdeskai/browser-tools-mcp --doctor, which reports
exactly which piece is missing.
To watch capture happen live — useful when checking a fresh install — start the
connector with --verbose:
npx @agentdeskai/browser-tools-server --verbose
· console error tab 42 Uncaught TypeError: total is not a function
· network 500 POST tab 42 https://myapp.local/api/pay (1310ms)
Without it the connector only reports connect and disconnect, so a working setup and a silent one look the same.
Tools
| Tool | What it does |
|---|---|
getConsoleLogs | Console output, filterable by keyword with paging |
getConsoleErrors | Error-level output and uncaught exceptions |
getNetworkLogs | XHR/fetch requests with status, timing and bodies |
getNetworkErrors | Only failed and 4xx/5xx requests |
getSelectedElement | The element selected in the Elements panel |
getPageInfo | Which page the browser is currently on |
getConnectionStatus | Whether the extension is connected, and capture counts |
listBrowserTabs | Tabs with DevTools open, addressed by a DevTools-connected tabId |
listTabs | Tabs chrome.tabs returns (not every open tab), addressed by chromeTabId |
readTab | Headings, excerpt, and a snapshot of a chromeTabId, without the debugger |
navigateBrowser | Load a URL in a DevTools-connected tab, without focusing it |
navigateTab | Load a URL in the background from listTabs, visible window only |
softClick / softType | Untrusted click or type. Result is worked or needs a real gesture |
takeScreenshot | Screenshot returned as an image, plus its file path |
refreshBrowser | Reloads the inspected tab |
getBrowserStorage | localStorage, sessionStorage and cookies (values gated) |
wipeLogs | Clears captured telemetry before a clean reproduction |
runAccessibilityAudit | Lighthouse accessibility audit |
runPerformanceAudit | Lighthouse performance audit with Core Web Vitals |
runSEOAudit | Lighthouse SEO audit |
runBestPracticesAudit | Lighthouse best-practices audit |
Three prompts ship alongside them — debuggerMode, auditMode and
nextjsSeoAudit — giving your agent a systematic workflow instead of a wall of
static text in every tool listing.
All tools declare MCP output schemas, so clients receive structured data rather than prose they have to parse, and read-only tools are annotated as such so clients can auto-approve them safely.
Tabs without DevTools
listTabs, readTab, navigateTab, softClick, and softType use chrome.tabs and chrome.scripting. They do not attach the debugger, so they do not show the debugging banner, and they do not activate a tab or focus a window.
chromeTabId comes from listTabs. tabId on the other tools comes from listBrowserTabs. They are different id spaces. Pass each id only to the tool that issued it.
listTabs reports the tabs chrome.tabs returns in the windows it returns. Other Arc Spaces, Little Arc, pinned favorites, split-view partners, and slept tabs may be missing. Incognito is skipped. This is not every open tab. Background navigation stays in the visible window until that is measured on Arc.
The popup switch has to be on. Navigation and soft input also need --allow-input on browser-tools-mcp, or --allow-actions on the browser-tools CLI. The same origin rules as other input apply. An asleep tab is reported and not reloaded. A soft action says worked or needs a real gesture and does not claim a failed action worked.
Several tabs at once
Every tab with DevTools open is tracked separately. Telemetry is attributed to the tab that produced it, and retention is per tab, so a chatty page cannot push out the history of the one you care about.
Tools act on the current tab — the one you most recently opened DevTools on.
A tab whose connection drops and comes back does not steal that position, which
is what used to make screenshots capture the wrong page. Every result reports
the tabId and url it came from, plus otherTabs, so a wrong-tab answer is
visible rather than silent. To target a specific tab, call listBrowserTabs and
pass its tabId to any tool; pass allTabs: true to read across every tab.
Large data stays out of the context window
Whole-history payloads are exposed as MCP resources rather than inlined, and
tools link to them with resource_link so your agent fetches them only when it
decides to:
| Resource | What it is |
|---|---|
browser-tools://console/{tabId|all} | Every console entry, with no per-call budget |
browser-tools://network/{tabId|all} | Every captured request, including bodies |
browser-tools://har/{tabId|all} | The same traffic as a HAR 1.2 file |
browser-tools://screenshot/{name} | A screenshot you captured earlier |
browser-tools://audit/{reportId} | The unabridged Lighthouse result behind a summary |
Log tools attach a link when a read had to be cut short; network reads always
offer the HAR; screenshots always link to the stored image, which is the only
way to see one too large to inline. The last 20 full Lighthouse reports are kept
under audits/ in the screenshot directory.
Keeping responses small
Log payloads are the usual cause of a blown context window. Every read tool
takes limit and offset, and the log tools take keyword filters:
getConsoleErrors({ keywords: ["hydration"], limit: 20 })
getNetworkLogs({ urlKeywords: ["/api/"], bodyKeywords: ["quota"], limit: 10 })
Results are returned newest-first and always report total alongside
returned, so an agent knows when it is only seeing part of the picture.
Privacy and security
This tool captures whatever your browser sees, so it treats that data carefully:
- Loopback only. The connector binds
127.0.0.1and refuses non-loopback addresses. In 1.x it bound0.0.0.0, reachable by anyone on your network. - The extension never leaves loopback. 1.x scanned private network ranges and adopted whichever host answered with a known string — meaning anyone on shared Wi-Fi could receive your logs and screenshots. That scan is gone.
- Authenticated. The HTTP API requires a per-run bearer token. A token in
the URL is refused. The extension WebSocket requires the same token; the
extension collects it from a loopback pairing route that answers only a
browser-extension origin, and
/.identitydoes not reveal it. Pass--extension-idto accept one extension and no others. - Credentials are scrubbed on the way in:
Authorization,Cookieand similar headers, plus JWTs, cloud keys and vendor tokens found anywhere in captured strings, become[REDACTED]. - Headers are off by default, per direction, and storage values are withheld unless explicitly requested.
- Cookie access is an optional permission you grant from the panel, not something the extension holds by default.
- The debugger banner stays visible. Chrome's "started debugging this browser" warning is a safety control. It is intentional, and this project never hides it. A capture path that does not attach the debugger simply does not raise the banner; that is not a way to silence it.
- Audits stay off your internal network. An audit accepts a url, and the
caller may be a model reading untrusted pages. Link-local and cloud metadata
addresses are never audited. Pages on localhost or a private network are
audited only while open in a connected tab (or allowlisted with
--audit-allow-origin), and every request the audit browser makes — redirects and subresources included — is checked again as it happens. A local page may load only the local origins that are open in a tab or allowlisted, so if your app calls an API on another local port, open that too or allowlist it. Audits connect directly, not through a system proxy. Behind a fake-IP proxy (Clash, sing-box, Surge), which hides where names really go, audits are refused unless you pass--audit-allow-private-network; see SECURITY.md.
Report vulnerabilities per SECURITY.md.
Configuration
Flags, or the matching BROWSER_TOOLS_* environment variables:
| Flag | Purpose |
|---|---|
--port <n> | Connector port (default 3025) |
--screenshot-dir <path> | Where screenshots are written |
--only <a,b> | Expose only these tools, on MCP and on the local API |
--exclude <a,b> | Hide these tools, on MCP and on the local API |
--allow-actions | Let the local API reload, capture, wipe, change settings and select a tab. Otherwise confirm that in the BrowserTools panel. The MCP tools in the process that embeds the connector are not gated by this |
--extension-id <id> | Only this extension may pair and connect |
--doctor | Check the setup and exit |
--verbose | Print each captured entry as it arrives |
--host <addr> | Loopback address to bind (default 127.0.0.1) |
--connect <url> | Attach to a connector already running elsewhere |
--token <t> | Auth token to use with --connect |
--no-redact | Disable credential scrubbing (not recommended) |
--audit-allow-origin <o,o> | Loopback/private origins audits may target or load without an open tab |
--audit-allow-private-network | Let audits reach any loopback/private address except the audit's own ports (not recommended); also needed behind a fake-IP proxy |
To share one browser session between several MCP clients, start the connector
once with npx @agentdeskai/browser-tools-server and every client will attach to
it automatically. The local API uses the --only, --exclude,
--allow-actions and --extension-id of that process. An attached client's
--only / --exclude still hide tools from that client.
Known limits
- Network capture starts when DevTools opens. Requests that finished before then are not recorded — reload the page to capture a full page load.
- Screenshots are held to a byte budget (
screenshotMaxBytes, 3 MB by default). A capture that would exceed it is re-encoded as JPEG and, if still too large, downscaled. A viewport capture of dense content on a high-DPI display can otherwise run past 13 MB, which is more than a model's context can take and past the read buffer newer MCP stdio transports enforce. If an image still cannot fit, it is written to disk and the tool returns the path instead of inlining it. - Console capture defaults to the DevTools protocol, which makes Chrome show a "started debugging this browser" banner. That banner is intentional and is never hidden. Switch the panel's capture mode to Wrap page console to capture the console without attaching the debugger.
- Firefox is not verified. The extension is written cross-browser — a
browser/chromeshim,browser_specific_settings, and a capture mode that does not needchrome.debugger— but it has never been loaded in Firefox, and nothing in the test suite covers it. Screenshots in particular go through the DevTools protocol and will not work there. Treat Firefox as unsupported until someone has actually run it; a report either way is welcome. - Audits launch a separate browser and take up to a minute. Any Chromium-based
browser works — Chrome, Chromium, Brave, Edge, Vivaldi, Opera or Arc — and
--doctorreports which one will be used. SetCHROME_PATHto override. Arc is supported on a best-effort basis and has not been verified headless.
Development
npm install
npm run build
npm test # unit + integration, no browser required
npm run test:e2e # real Chromium with the extension loaded (headless)
npm test runs in seconds. The end-to-end suite launches Chromium with the
extension installed, drives fixture pages, and asserts the whole capture path.
It runs headless; set HEADED=1 to open a visible window. Run
npx playwright install chromium first.
License
MIT