Browser Smoke
MCP browser smoke testing plugin for OpenCode — Playwright-based UI testing via browser automation
2
92
58 in 7 days
35.4
Multi-signal model
18 days ago
2026-09-17
Install and configure
opencode.jsonWrites to this project's opencode.json — applies to this repository only.
opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["browser-smoke@1.0.4"]
}Writes to ~/.config/opencode/opencode.json — applies to every project.
~/.config/opencode/opencode.json
{
"$schema": "https://opencode.ai/config.json",
"plugin": ["browser-smoke@1.0.4"]
}If you want to modify the plugin locally, install it into the project and reference the local path.
shell
pnpm add -D browser-smokeOpenCode loads npm dependencies through its embedded runtime on startup and caches them locally — no manual global install needed.
A Playwright browser your coding agent can drive: open a site, fill a form, scrape a page, or smoke-test a UI you just built.
MCP server id is smoke. In OpenCode the tools are smoke_browser_open, smoke_browser_snapshot, … Cursor and Claude Code call the same tools without the prefix (browser_open).
Results are compact JSON. Screenshots stay off unless you ask. The agent gets its own Google Chrome at ~/.browser-smoke/chrome-attach (not the Default profile where you already read Gmail, and not Playwright's testing Chromium). persist=false is the testing window. See What this is not.
Works with OpenCode, Cursor, Claude Code, and any MCP client.
Do not run npx smoke — that is a different npm package (a mock HTTP server). Install from this GitHub repo:
Quick start
Need Node.js 18+ and Python 3.10+. Chromium is installed by setup.
1. Install
npx github:sukirman1901/browser-smoke
Pick local or global, then which host (OpenCode, Cursor, Claude Code, or all). Setup copies the MCP server, creates a venv, and installs Playwright Chromium. The MCP key it writes is smoke.
Restart the host after install (quit/reopen OpenCode, Cursor, or Claude Code).
| Command | What it does |
|---|---|
npx github:sukirman1901/browser-smoke |
Interactive |
npx github:sukirman1901/browser-smoke -- --local --all |
This project, all three hosts |
npx github:sukirman1901/browser-smoke -- --local --cursor |
Cursor only |
npx github:sukirman1901/browser-smoke -- --local --claude |
Claude Code only |
npx github:sukirman1901/browser-smoke -- --global --opencode |
OpenCode user config |
npx github:sukirman1901/browser-smoke -- --print |
Print the stdio MCP entry |
After a version upgrade, run the same command again so MCP files refresh, then restart the host.
2. First task
Paste this into the agent:
Use smoke: open https://example.com, take a snapshot, tell me the title and the first five links. Do not screenshot.
3. How the agent should drive it
Same loop for a daily task and a smoke test (OpenCode names):
smoke_browser_openthe URL.smoke_browser_snapshot— this is the selector map:@1,@2,@3.- Click or type those refs. After navigation, if a ref errors, snapshot again.
- Prove the result with
smoke_browser_assert(text/url/visible/…). Snapshot is not the verdict. - Several steps: one
smoke_browser_script(or onesmoke_browser_run). Do not chain eight MCP execute calls. - Scrape with
smoke_browser_executereturning a small JSON array — notinnerHTML. - Several URLs or tabs at once: one
smoke_browser_parallel. Do not chainopen_tab. - Screenshot only for a visual bug. Never
screenshot_base64.
Daily task (window stays up)
This is the default. smoke_browser_open starts or reconnects Google Chrome at ~/.browser-smoke/chrome-attach (port 9222). You do not launch it by hand and you do not pass cdp=. Do not pass persist=true (it is already on).
smoke_browser_open url=https://example.com
smoke_browser_snapshot
smoke_browser_script js_code="await click('@1'); await type('@2', 'hi');"
Leave the window. Next chat, smoke_browser_open the next URL — same Chrome, cookies kept. smoke_browser_close disconnects; it does not quit Chrome. Pass shutdown=true only when you want the window gone.
Named sessions (session=work) if two tasks must not share tabs. Then pass session= on every tool.
Helpers inside smoke_browser_script: open, click, type, snapshot, wait, assert, execute, press, hover, scroll, dialog, download, upload, select, switchTab. wait("load") and wait("#ready") are fine. wait timeout is milliseconds. assert({ expect: "text", text: "Saved" }) is the verdict. scroll(800) is down 800px; scroll('@3') brings that ref into view.
Smoke test (after you ship a feature)
Throwaway browser — must persist=false or you pollute the living profile.
smoke_browser_open url=http://localhost:5173 persist=false session=test
smoke_browser_snapshot
smoke_browser_run actions_json='[{"action":"type","selector":"@1","text":"test@test.com"},{"action":"click","selector":"@3"}]'
smoke_browser_assert expect=text text="..."
smoke_browser_console
smoke_browser_errors
smoke_browser_report results_json
smoke_browser_close shutdown=true session=test
The app must already be running. Failures should block. Debug with console, errors, then smoke_browser_network_capture mode=get (no headers). Screenshot last.
Visual regression (opt-in):
smoke_browser_screenshot_diff name=homepage
Writes a baseline/diff under artifacts/. No PNG in the tool result unless you ask.
Scrape
smoke_browser_open url=https://example.com
smoke_browser_execute js_code="() => [...document.querySelectorAll('a')].slice(0,50).map(a => ({t:a.textContent.trim(), h:a.href}))"
Several sites together (max 8). Do not open_tab in a loop — those wait on each other:
smoke_browser_parallel urls='["https://example.com","https://example.org"]' js_code="() => ({title: document.title, href: location.href})"
Same JS on tabs already open: js_code only. Click/type still use the focused tab (switch_tab).
smoke_browser_close is optional; it does not quit the living window.
Keep a login (Playwright profile, not Chrome)
Default persist already keeps cookies in .browser-smoke/profiles/<session>. Log in once in that window.
Custom dir:
smoke_browser_open url=https://app.example.com user_data_dir=.browser-smoke/profile
Cookies live in that folder. Gmail already open in your Chrome will not appear here.
Need stock Chrome instead of bundled Chromium? channel=chrome (throwaway, not the living profile). cdp= attaches to a debug Chrome you launched.
Attach to a debug Chrome (cdp=)
Default browser_open already auto-starts ~/.browser-smoke/chrome-attach on port 9222. Use cdp= only for a Chrome you launched yourself on another port.
Chrome 136+ ignores --remote-debugging-port on the daily Default profile. chrome-attach is a separate dir on purpose.
- Quit daily Chrome if it is using the same binary and you hit a lock (optional on macOS if you only open the debug profile).
- Start debug Chrome (macOS):
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \
--remote-debugging-port=9222 \
--user-data-dir="$HOME/.browser-smoke/chrome-attach" \
--no-first-run --no-default-browser-check
- In the agent:
smoke_browser_open url=https://example.com cdp=9222
smoke_browser_snapshot
smoke_browser_close
cdp= also accepts http://127.0.0.1:9222 or a ws:// DevTools URL. You do not need persist=false. smoke_browser_close disconnects; it does not quit that Chrome. Do not combine cdp with channel.
Forms, files, dialogs, popups
Snapshot first, then:
smoke_browser_click selector=@4 dialog=accept
smoke_browser_click selector=@5 popup=true
smoke_browser_switch_tab index=0
smoke_browser_download url=https://images.unsplash.com/photo-xyz save_as=hero.jpg
smoke_browser_set_files selector=@7 paths=/abs/path/hero.jpg
smoke_browser_download selector=@8
Prefer dialog=accept on the click that opens the alert. Arm the next dialog only when the trigger is not a click.
What this is not
| You might expect | What you actually get |
|---|---|
| Agent uses the Chrome window you are looking at | A separate Chrome profile: ~/.browser-smoke/chrome-attach. Not Default. |
| Gmail / cookies from your daily Default Chrome | Empty. Log in once inside the chrome-attach window (or you already did). |
| Playwright “Chrome for Testing” on daily open | Only persist=false (smoke tests). Default open is Google Chrome. |
| Window dies when the chat ends | Only if you close with shutdown=true |
| Task Spaces / take over from the agent | Named MCP sessions. You do not share tabs with the agent |
| Screenshot on every click | Path on disk only when screenshot=true |
npx smoke |
Different npm package. Install from the GitHub command above |
Tools
OpenCode names below. Cursor / Claude Code: drop the smoke_ prefix.
See the page
| Tool | When to use |
|---|---|
smoke_browser_snapshot(scope?) |
Default. Accessibility @ref list; cross-origin iframes tagged iframe |
smoke_browser_execute(js_code) |
Scrape / inspect, return JSON |
smoke_browser_extract_dom() |
Buttons/inputs/links without @refs. Prefer snapshot |
smoke_browser_wait(state, selector?, url?, js?) |
Load, visible, URL glob, or waitForFunction. timeout is milliseconds. Not a test verdict. |
smoke_browser_assert(expect, text?, selector?, count?, negate?) |
Pass/fail: text url visible hidden count input_value. assert_fail ≠ tool error |
Move around
| Tool | When to use |
|---|---|
smoke_browser_open(url, persist?, session?, user_data_dir?, channel?, cdp?) |
Default = living Chromium. persist=false = test. cdp=9222 = debug Chrome |
smoke_browser_session |
current / use / list / close named sessions |
smoke_browser_script(js_code) |
One round trip with loops. Helper assert({ expect, text, selector }) |
smoke_browser_run(actions_json) |
JSON batch, no loops |
smoke_browser_parallel(urls?, js_code?, jobs_json?, tabs?) |
Open/scrape up to 8 tabs at once. MCP tools still queue; this is the overlap |
smoke_browser_open_tab / get_tabs / switch_tab |
Extra tabs, one at a time. Prefer parallel for several URLs |
smoke_browser_scroll / reload / hover / press |
Page scroll (default down 200px) or selector=@n into view, then snapshot. Click already scrolls its target. Hover menus, then snapshot. |
smoke_browser_close(shutdown?) |
Default leaves the living window. shutdown=true kills persist Chromium |
Click, type, files
| Tool | When to use |
|---|---|
smoke_browser_click(selector, dialog?, popup?) |
CSS or @1. Scrolls into view. Covered/off-screen is code=intercepted, not a silent force |
smoke_browser_type / paste / drag |
Fill, contenteditable chunk, drag @n onto @n |
smoke_browser_type_guess |
Dummy email/password for a smoke test |
smoke_browser_select_option |
<select> by value, label, or index |
smoke_browser_set_files / download |
File input; save under artifacts/downloads/ |
smoke_browser_handle_dialog |
Next JS dialog if the trigger is not a click |
Evidence
| Tool | When to use |
|---|---|
smoke_browser_console / errors |
Capped logs |
smoke_browser_network_capture / block_resources / inject_script |
Capture, block, mock |
smoke_browser_screenshot / screenshot_diff / highlight |
Visual, on demand |
smoke_browser_get_cookies / set_cookie / clear_cookies / storage |
Cookies and storage |
smoke_browser_report |
Writes artifacts/smoke-report.md |
smoke_browser_offscreen |
Hidden page in the same context |
screenshot=true on click/type/open writes a file path. screenshot_base64=true is an escape hatch.
Troubleshooting
| Problem | Fix |
|---|---|
| Chromium missing / launch error | Run the GitHub npx command again, or .browser-smoke/.venv/bin/playwright install chromium |
| Tools look stale, or every click returns a screenshot | Run setup again and restart the host |
OpenCode shows browser-smoke_browser_open |
Old MCP key. Setup writes id smoke |
| Two Chromium windows | Default open is persist. Do not also pass persist=false or cdp= unless you mean it |
| Smoke test reused login cookies | Pass persist=false session=test |
| CDP connect failed / Chrome 136+ | Daily Gmail Chrome cannot be attached. Launch debug Chrome with a non-default --user-data-dir and cdp=9222 |
cdp + channel errors |
Use only one |
| Connection refused | The target app is not running |
| Python not found | Install Python 3.10+ |
| Two sessions keep hitting the same tab | Pass session= on every tool |
| Tabs still open one-by-one | One browser_parallel with urls. open_tab is sequential |
npx smoke does the wrong thing |
That package is not this project. Use npx github:sukirman1901/browser-smoke |
Development
git clone https://github.com/sukirman1901/browser-smoke.git
cd browser-smoke
python3 -m unittest discover -s tests -v
npm link
After npm link, the CLI is smoke (alias browser-smoke).
Releases: CHANGELOG.md. Latest is v1.6.0.
License
MIT
Similar plugins
Screenshot Vision
opencode-screenshot-vision
OpenCode plugin: let a text-only LLM read browser screenshots via local Ollama with OpenCode Zen fallback.
Browser
opencode-browser
OpenCode plugin that integrates Browser MCP for browser automation
Browser Mcp
opencode-browser-mcp
OpenCode plugin that integrates Browser MCP for browser automation