# browser-harness-js: the CDP protocol as the API, with no click() helper in sight

> browser-use/browser-harness-js exposes all 652 Chrome DevTools Protocol methods as typed JavaScript calls over one persistent WebSocket. It is built for agents that can write protocol calls themselves, and it refuses to guess what a click means.

**browser-use/browser-harness-js** — Self-healing browser harness that enables LLMs to complete any task

- Repository: https://github.com/browser-use/browser-harness-js
- Stars: 488 · Forks: 39
- Language: TypeScript
- License: MIT
- Published: 2026-09-10 · Updated: 2026-09-10 · Language: en
- Canonical page: https://hysenlabs.com/projects/browser-use-browser-harness-js

## The problem browser-harness-js solves: helpers that lie about CDP

Most browser automation layers for LLMs ship a vocabulary of verbs: click, goto, upload_file. Each verb is a fixed signature over a protocol call that has many more parameters. The README makes the argument directly: click(x, y) hides Input.dispatchMouseEvent, which has 14 parameters the model might need, including button, clickCount, modifiers, pointerType, force and tangentialPressure. A harness that exposes three of them quietly limits what the agent can do. That is the case this project answers.

The intended user is not a QA engineer writing a test suite. It is an agent runtime where the model already knows, or can look up, the Chrome DevTools Protocol. The README describes the project as the thinnest possible bridge from the LLM to Chrome, with no harness, no recipes and no rails. The bet is that a capable model with the typed method list does better than a model constrained to a curated set of verbs. If your model cannot write session.Input.dispatchMouseEvent({...}) from a schema, this project gives it nothing to hold on to.

## One WebSocket, 56 domains, 652 typed wrappers

The architecture is small enough to read in an afternoon. sdk/repl.ts is a Bun HTTP server that holds one persistent Session. sdk/session.ts is the Session class itself, covering transport, connect, target routing and events. sdk/gen.ts is the codegen step: it reads browser_protocol.json and js_protocol.json and emits sdk/generated.ts, where every CDP method appears as session.<Domain>.<method>(params). The CLI at sdk/browser-harness-js auto-spawns the server and forwards snippets.

Because the wrappers are generated from the upstream protocol JSON, the README states that new Chrome methods appear as soon as you swap the JSON. That is the version-drift argument: there is no hand-maintained binding layer to fall behind. The README also claims the JSDoc carried into the generated types is the same as the CDP reference, so autocomplete on session.Page.navigate( shows the exact parameters.

The repository adds three things CDP itself does not provide: listPageTargets(), which filters chrome:// and devtools:// entries out of Target.getTargets; resolveWsUrl({wsUrl|port|profileDir}), which reads DevToolsActivePort for Chrome 144 and later; and the routing primitives session.use(targetId) and session.waitFor(method, pred, timeout). Everything else is the protocol, unmodified.

## Installing the cdp skill and driving your first tab

The README gives one installation path: an agent skill rather than an npm package. The command below adds the skill from the repository. The README notes that the CLI auto-installs bun on first run if it is missing, and that setting BROWSER_HARNESS_SKIP_BUN_INSTALL=1 opts out of that behaviour.

```bash
npx skills add https://github.com/browser-use/browser-harness-js --skill cdp
```

The README then suggests symlinking browser-harness-js into a directory on your PATH. After that, Chrome must be started with remote debugging enabled; the README includes a screenshot (docs/setup-remote-debugging.png) showing the checkbox Chrome may ask you to tick, and states that this is how the agent attaches.

The README also offers a paste-into-your-agent prompt that installs the skill, symlinks the CLI and runs a first task: look at all open tabs, group them by topic, and screenshot the most interesting one. Once the CLI is on your PATH, the first call a reader will write by hand is a target listing, because everything else routes through a target id. The README's diagram shows the shape of these calls, for example session.Input.dispatchMouseEvent({...}) and session.DOM.setFileInputFiles({...}).

listPageTargets() is the project's own filter over Target.getTargets, so chrome:// and devtools:// pages do not appear in the result. Day-to-day usage, including how to connect, pick a tab, call methods and persist state, is documented in SKILL.md.

## Where the no-helper design costs you

The absence of helpers is the feature and the failure mode at the same time. There is no wait-for-selector primitive, no retry on a detached node, no implicit settle after navigation. session.waitFor(method, pred, timeout) waits on a protocol event matching a predicate; it does not know what a loaded page means to your task. If your agent is not good at reading a method signature and reasoning about parameters, it will produce malformed calls, and nothing in the repository intercepts them.

The second cost is operational. The launch path depends on Bun: the CLI auto-spawns the server, and the README says Bun is installed on first run if absent. Teams that cannot install a second JavaScript runtime on their machines need BROWSER_HARNESS_SKIP_BUN_INSTALL=1 and a Bun they manage themselves. The connection path also assumes remote debugging is reachable, and resolveWsUrl reads a DevToolsActivePort file when given a profileDir, which the README ties to Chrome 144 and later. On older Chrome builds, or in an environment where that file is not present, you are expected to pass wsUrl or port explicitly. The README does not document what happens when the WebSocket drops mid-task, and it does not describe a rollback or recovery path.

Finally, this is the wrong tool if you wanted a test framework. There is no assertion layer, no test runner and no reporting. It is a transport with a generated type surface.

## browser-harness-js vs Playwright: different layers, different contracts

Playwright is the obvious comparison, and the difference is not speed or coverage. Playwright is a testing framework with its own actionability model: it waits for elements to be visible, stable and enabled before acting, and it ships a selector engine on top of the browser. That model is a contract about what a click means, and it is the contract browser-harness-js explicitly refuses.

Here the agent writes the CDP call itself. If a button is covered by an overlay, Playwright's click would wait or fail according to its own rules; with browser-harness-js the agent decides, because Input.dispatchMouseEvent takes coordinates and modifiers and does not know what a button is. The README frames the trade plainly: every helper is a lie about what CDP already gives you. The price of that honesty is that all the judgment Playwright encodes now lives in your prompt, your model and your interaction-skills recipes.

A second difference is versioning. Playwright pins a browser build and its own API surface. browser-harness-js regenerates from protocol JSON, so the method list tracks whatever Chrome you attach to. That is convenient for chasing new protocol domains and inconvenient if you wanted a stable API to test against.

## Maintenance, licence and what upgrading actually involves

The repository is not archived, and the last push was on 2026-08-30. There are no retrieved releases, so versioning is done from the main branch rather than from tagged artifacts. The licence is MIT, which permits commercial use and modification provided the copyright notice and permission notice are retained; that is a statement about the licence text, not legal advice for your situation.

The upgrade story follows from the codegen design. Because sdk/generated.ts is produced by sdk/gen.ts from browser_protocol.json and js_protocol.json, keeping current means swapping the protocol JSON and regenerating, and the README says new Chrome methods appear as soon as you do. The cost is not the regeneration; it is that your agent's prompts and your interaction-skills recipes may reference methods whose parameter lists changed. There is no changelog in the repository entries listed, so a reader tracking a Chrome update has to diff the generated file. The README invites contributions in the form of new interaction-skills recipes, with the instruction to keep them in pure CDP and to lead with the shortest method call that works.

## Who this repository is actually for

It suits a team building an agent that can be trusted with a typed schema and a live browser, where the interesting failures come from the task rather than from the wrapper. It suits anyone who has hit the ceiling of a helper API and needs the parameter that was not exposed. It does not suit a team that wants a test suite, a selector engine or a managed retry policy, and it does not suit a runtime that cannot host Bun or reach a Chrome instance with remote debugging enabled. The repository states its position without hedging: no harness, no recipes, no rails. Take that literally before you take the dependency.

## Conclusion

Adopt browser-harness-js if your agent writes its own CDP calls and you want the full parameter surface of the protocol instead of a three-argument click() wrapper. Do not adopt it if you want a test runner, a selector engine or a retry loop; the repository ships none of those, and the README says the protocol is the API. Before wiring it into anything, verify that your Chrome build exposes a DevToolsActivePort file, because resolveWsUrl reads it for Chrome 144 and later, and confirm your runtime can execute the Bun CLI, since the launcher auto-installs Bun on first run unless BROWSER_HARNESS_SKIP_BUN_INSTALL=1 is set.

## FAQ

### What does browser-harness-js do?

It is a thin bridge from an LLM to Chrome that exposes every Chrome DevTools Protocol method as a typed JavaScript call, generated from the upstream protocol JSON. The README describes it as one persistent WebSocket covering 56 domains and 652 typed wrappers, with no pre-baked helpers such as click() or goto().

### How do I install browser-harness-js?

The README gives one installation path: run npx skills add https://github.com/browser-use/browser-harness-js --skill cdp, then symlink browser-harness-js into a directory on your PATH. The CLI auto-installs Bun on first run unless BROWSER_HARNESS_SKIP_BUN_INSTALL=1 is set.

### Does browser-harness-js work with any LLM?

The repository does not recommend a model. Its design assumes the model can write CDP calls itself, for example session.Input.dispatchMouseEvent({...}), so a model that needs pre-built verbs will not get them here.

### How does browser-harness-js compare with Playwright?

Playwright is a testing framework with its own actionability and selector model, while browser-harness-js exposes the protocol directly and leaves those decisions to the agent. The README argues that every helper hides parameters of the underlying CDP method, which is the trade it makes.

## Sources

- [browser-use/browser-harness-js on GitHub](https://github.com/browser-use/browser-harness-js)
- [Issues](https://github.com/browser-use/browser-harness-js/issues)
- [License: MIT](https://github.com/browser-use/browser-harness-js/blob/main/LICENSE)
- [README](https://github.com/browser-use/browser-harness-js/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/browser-use-browser-harness-js
