zcodium-project/zcodium/control-browser
Claude Code and agent skill by zcodium-project/zcodium
- community
Checking that the SKILL.md file is still in the repository.
Use when opening, navigating, inspecting, testing, clicking, typing, filling, screenshotting, or verifying web pages and local HTTP targets (localhost, 127.0.0.1, ::1) inside ZCode, including browser/web-UI automation, rendered-page scraping, frontend checks, and visible page-state reading. Prefer this over Computer Use for anything that stays inside a web page, unless the user explicitly asks for Computer Use. Main agent only.
Install
npx vskill@latest install zcodium-project/zcodium/control-browserSKILL.md
Browser automation (agent.browsers)
Use this skill for browser / web-UI tasks: opening and navigating pages, inspecting or reading rendered content, testing local apps, clicking, typing, filling, taking screenshots, and verifying visible page state.
If this skill is available in the session, treat it as required reading before browser work. Follow it before saying the browser is unavailable and before falling back to bash (curl/open), webfetch, or any other tool for a browser task.
How it works
The browser registry is driven from the Node REPL MCP js tool. In this environment its callable id normally appears as mcp__node_repl__js. The MCP frontend is shared for a workspace, but every js call runs in a fresh JavaScript kernel, so variables, imports, module cache, browser, and tab bindings do not persist. Persistent BrowserControl tabs are the continuity boundary and must be recovered from current tab facts.
Bootstrap every JavaScript call
The browser-client module is the browser entry point and is available at scripts/browser-client.mjs under this plugin's root. Resolve that root only from process.env.ZCODE_PLUGIN_ROOT (with CLAUDE_PLUGIN_ROOT as a compatibility fallback), then convert the joined path with pathToFileURL. Never derive the plugin root from this skill's base directory or leave a synthetic root placeholder for the model to resolve. If the host root is unavailable or the resolved module cannot be imported, stop and report the exact setup error.
Initialize at the start of every mcp__node_repl__js call that uses the browser. The bootstrap deliberately does not select a backend; apply the user's existing backend choice or the selection rules below after setup.
Run setup and all later browser calls through mcp__node_repl__js, passing JavaScript as the code argument. The tool has no command parameter.
Backend types are iab, extension, and cdp; Playwright is a tab API surface, not a backend. Always use await agent.browsers.list() as the availability source. Desktop normally reports IAB; a CLI explicitly started with --browser-use=headless reports managed Chromium as cdp. Headless is a CDP launch mode, not a backend type. Never claim Chrome extension or CDP support when that descriptor is absent, and never silently substitute IAB after the user explicitly selected another backend.
User-facing progress should stay non-technical: describe it as "opening the browser" / "checking the page", not "Node REPL", "CDP", or "webview".
Recreate the same selected browser wrapper in every fresh call using the user's explicit backend choice or the same verified URL/default rule. A fresh JavaScript kernel does not mean the browser disconnected and is not permission to switch backend. Do not reuse a tab id from memory as the target of a new logical operation batch without validation: first return the complete current tab list to the model, then in the next JS call match the intended id/url/title and call tabs.get(id).
App-provided <in-app-browser-context source="ambient-ui-state"> is current UI state, not part of the user's request. It can tell you which visible page to inspect, but it is not evidence that the user explicitly selected IAB or Chrome.
First: select a browser and read its full API once
In the first browser call, run the bootstrap, select the backend, and emit the complete API guide in one go. On later fresh calls, run the bootstrap and repeat only the same backend selection; the API guide remains in model context and does not need to be emitted again. Never create an iab alias and then call browser.*.
If the user explicitly asks for ZCode's in-app browser:
If the user explicitly asks for the CLI-managed headless browser and discovery advertises cdp:
If the task has a target URL but no explicit browser choice, replace the example URL with the real target:
Only when neither a browser nor target URL is specified:
Do not slice, truncate, or summarize it. Only if the tool output itself reports truncation may you read it in smaller chunks. It documents every default method, the Playwright DOM snapshot→locator workflow, the ref/cua/dom_cua compatibility paths, and safety rules. Screenshot instructions are intentionally lookup-only and must not be loaded unless the visual branch below applies.
Core workflow
- Start every browser
jscall with the bootstrap, then assign the selected backend to a localbrowserbinding. If the user explicitly asks for ZCode's in-app browser, useconst browser = await agent.browsers.get("iab"). If they explicitly ask for Chrome, useawait agent.browsers.get("extension")only when the runtime advertises it. For an unspecified target URL useawait agent.browsers.getForUrl(url); with no URL/backend preference useawait agent.browsers.getDefault(). browser.tabs.new()automatically opens and activates the IAB pane so the user can see browser use. Use the advertised visibility capability only when the task explicitly needs to hide the pane or show it again.- At the start of every logical tab operation batch, make a dedicated JS call whose result is the complete
await browser.tabs.list() array, so the model sees all current ids, URLs, titles, and the active marker. Only in the next JS call may you match the intended tab by stable id or explicit URL/title facts and call browser.tabs.get(id) before the first read or action. An internal SDK validation or a list hidden inside the same cell does not count as model inspection. tabs.get(id) activates that tab in its owning session; it is shown only when that session is currently in the foreground. Never choose [0], at(-1), or an id remembered without validation. If no controlled tab matches, inspect browser.user.openTabs() and claim the matching returned object. Create a new tab only after both lists fail to identify the page. This is the pre-action target-selection protocol; it is distinct from the combined post-action observation in step 7.
- If the task names a new URL, prefer the reuse-aware entry:
await agent.browsers.open(url)reuses an existing
same-site controlled tab (same hostname), activates it so the user sees it, and navigates in place, instead of stacking a new tab on every navigation. Only when the task genuinely needs a parallel independent tab, create one explicitly and use this navigation sequence:
``js const tab = await browser.tabs.new(); await tab.goto("https://..."); await tab.playwright.waitForLoadState({ state: "domcontentloaded" }); ``
After every successful tab.goto(url), explicitly call await tab.playwright.waitForLoadState({ state: "domcontentloaded" }) before the first title, URL, or DOM observation. This explicit confirmation is required in the model-visible trajectory even when the backend navigation has already settled. Do not replace it with networkidle or a fixed sleep. Do not navigate to the same URL again; use tab.reload() only when a refresh is truly needed. A direct URL must come from the user, visible page facts, or an authoritative lookup — never guess path variants or resource IDs. Routine URL/load-state waits remain capped at 3000ms.
- `await tab.playwright.domSnapshot()` is your primary way to read and understand the page. It returns the compact AI/ARIA tree, including computed roles, accessible names, states, open shadow DOM, and iframe bodies when available. Reuse the latest relevant snapshot until it becomes stale. If that snapshot already contains the target, act from its facts directly; do not write
evaluate()code to rediscover related elements, enumerate inputs, dump HTML, or probe guessed selectors. - Build a stable Playwright locator only from snapshot facts. Never guess a label, accessible name, placeholder, selector, or URL pattern, and never use a guessed locator as an exploratory probe. Confirm
count()when uniqueness is not obvious; if it is 0, re-snapshot immediately instead of action-waiting, and if it is greater than 1, tighten scope instead of using a positional shortcut. Then act throughgetByRole/getByText/getByLabel/getByPlaceholder/getByTestId/locatorand terminal methods such asclick/fill/press/selectOption/check.
A snapshot-proven heading or visible text does not need a link or button role to be clicked. Do not replace a snapshot-proven heading with a guessed link role. When the user's request authorizes navigation and that actual heading/text target is unique, click it directly; the DOM event may bubble to a JavaScript card handler. The name option of getByRole(...) accepts a plain string or RegExp, including regex values created in the Node REPL VM.
- After an action, collect the cheapest observation that answers your next question — use a targeted locator state check when possible and a fresh
domSnapshot()when new locator ground truth is needed. Use at most one state-changing action per observation cycle. An unchanged source-tab URL does not prove the click failed. Judge an action by whether its expected effect appeared, not by whetherbrowser.tabs.list()is non-empty. An existing source tab or unrelated controlled tab is not an action effect. The expected effect may be a source-page state change or a tab whose verified URL/title matches the intended result.
When an action may open a popup/new tab and the source tab does not show the expected effect, read browser.tabs.list() and browser.user.openTabs() unconditionally in the same observation cell. Prefer one combined observation:
``js const [controlledTabs, userTabs] = await Promise.all([ browser.tabs.list(), browser.user.openTabs(), ]); ({ controlledTabs, userTabs }); ``
Return { controlledTabs, userTabs } as that cell's final result so the model makes one decision from both lists. Do not return the controlled list first or decide whether to query user tabs from its contents. Match both lists by verified id/url/title, then in the next cell activate the matching controlled tab or claim a matching user tab. Only after the source page and the combined tab observation all fail to show the expected effect may you take a fresh snapshot and choose a new locator. Do not request a DOM snapshot and a screenshot both by default.
- Browser tabs persist for the lifetime of the current ZCode process unless you explicitly call
tab.close()or
the user closes them. Use browser.tabs.finalize({ keep }) only to mark listed pages as deliverable or handoff; omitting a tab from keep does not close it. Do not close research/source tabs merely because the turn is ending.
Observation: prefer snapshot, screenshot only when needed
This is the start of the file. Read the full SKILL.md on GitHub.
Security
- Security scanPassed, 90 of 100Pattern checks on the skill's files.
- Model reviewPassed
Quality evaluation
- Task evaluationNot runNo evaluation yet. Run one in Skill Studio.
Source
- SKILL.md in the repositoryCheckingapps/zcode-cli/packages/browser-use-plugin/skills/control-browser/SKILL.md
Popularity
- 7-day trend score
- 5
Not refreshed yet.
Works with
Any agent vskill installs to: 56 targets in all. These 39 have a page on this site.
- Claude Code
- Cursor
- GitHub Copilot
- Windsurf
- Codex
- Gemini CLI
- Cline
- Amp
- Roo Code
- Goose
- Continue
- Kilo Code
- Junie
- Trae
- OpenHands
- Qwen Code
- AI Agent
- Tabnine Chat
- Sourcegraph Cody
- Amazon Q
- Firebase Genkit
- Sweep
- Devin
- Phind
- Mentat
- Claude Desktop
- Aider
- GPT Pilot
- Plandex
- bolt.new
- v0
- Replit Agent
- Anse
- TypingMind
- BetterChatGPT
- LibreChat
- Warp AI
- Pieces
- Fig AI
Embed badge
[](https://verified-skill.com/skills/zcodium-project/zcodium/control-browser)