Communitygithub.com

browser-use/browser-harness-js

Steuert Chrome über das DevTools-Protokoll mittels JavaScript. Führt JS-Snippets über das `browser-harness-js` CLI aus – es startet automatisch einen langlebigen bun-HTTP-Server, der eine vollständig typisierte CDP-`Session` hält, und jeder Aufruf (`browser-harness-js 'await session.Page.navigate(...)'`) wird gegen dieselbe persistente Verbindung ausgeführt.

Was ist browser-harness-js?

Dieses Skill ermöglicht die Automatisierung, Skriptsteuerung und Inspektion von Chrome über das Chrome DevTools Protocol (CDP) mit JavaScript. Es stellt ein CLI (`browser-harness-js`) zur Verfügung, das einen Hintergrund-HTTP-Server startet, der eine dauerhafte CDP-Sitzung verwaltet. Jeder CLI-Aufruf führt JavaScript-Code gegen dieselbe Sitzung aus, sodass Verbindungen, aktive Tabs und in `globalThis` gespeicherte Daten über mehrere Aufrufe hinweg erhalten bleiben. Es wird hauptsächlich in KI-Agenten wie Claude Code, Cursor und Codex eingesetzt, aber auch direkt in der Konsole. Das Skill erkennt automatisch laufende Chromium-basierte Browser (Chrome, Edge, Brave, Arc, Vivaldi, Opera, Comet, Canary) über die `DevToolsActivePort`-Datei und bietet darüber hinaus Methoden zur manuellen Verbindung. Es unterstützt die Steuerung mehrerer Tabs, das Abhören von Events und die Nutzung aller 652 CDP-Methoden aus 56 Domänen über vollständig typisierte Wrapper.

Funktioniert mit✓Claude Code~Codex CLI✓Cursor
npx skills add browser-use/browser-harness-js

In Ihrer bevorzugten KI fragen

Öffnet einen neuen Chat, in dem dieser Agent-Skill bereits geladen ist.

Dokumentation

CDP – browser-harness-js Skill

Maßgeschneidertes, codegeneriertes CDP-SDK (jede Methode aus browser_protocol.json + js_protocol.json erhält einen typisierten Wrapper) plus ein kleiner HTTP-Server, der eine persistente CDP-Session hält. Das browser-harness-js-CLI startet den Server beim ersten Gebrauch automatisch und leitet JS-Snippets an ihn weiter.

Das SDK befindet sich im sdk/-Verzeichnis des Skills. Im Rest dieses Dokuments bezieht sich <skill-verz> auf das Installationsverzeichnis des Skills (Claude Code: ~/.claude/skills/cdp; Cursor: ~/.cursor/skills/cdp; andere Agenten variieren). Das CLI sollte unter dem Befehl browser-harness-js im PATH verfügbar sein.

Einrichtung (einmalig, beim ersten Gebrauch)

npx skills add legt das Skill im Skills-Verzeichnis des Agenten ab, setzt es aber nicht in den PATH. Überprüfe vor dem ersten Aufruf, ob es erreichbar ist, und verlinke es bei Bedarf in ein beliebiges PATH-Verzeichnis:

# macOS (Apple Silicon + Homebrew)
command -v browser-harness-js >/dev/null || ln -sf <skill-verz>/sdk/browser-harness-js /opt/homebrew/bin/browser-harness-js

# macOS (Intel) / die meisten Linux-Systeme – ggf. sudo erforderlich
command -v browser-harness-js >/dev/null || ln -sf <skill-verz>/sdk/browser-harness-js /usr/local/bin/browser-harness-js

# Linux ohne sudo (stelle sicher, dass ~/.local/bin im PATH ist)
command -v browser-harness-js >/dev/null || { mkdir -p ~/.local/bin && ln -sf <skill-verz>/sdk/browser-harness-js ~/.local/bin/browser-harness-js; }

Das CLI installiert bun beim ersten Start automatisch, falls es fehlt (der Server ist Bun-nativ). Setze BROWSER_HARNESS_SKIP_BUN_INSTALL=1, um dies zu deaktivieren.

Verwendung

Führe einfach browser-harness-js '<JS>' aus. Der erste Aufruf startet den Server im Hintergrund; nachfolgende Aufrufe treffen denselben Prozess und verwenden daher dieselbe session, denselben WebSocket zu Chrome und alle gesetzten globalen Variablen.

browser-harness-js 'await session.connect()'
browser-harness-js 'await session.Page.navigate({url:"https://example.com"})'
browser-harness-js '(await session.Runtime.evaluate({expression:"document.title",returnByValue:true})).result.value'

Die Ausgabe ist der reine Ergebnisinhalt – kein {ok,result}-Umschlag.

Ergebnistypstdout
stringreiner Text, ohne JSON-Anführungszeichen (z.B. Example Domain)
number / boolean42, true
object / array (nicht leer)kompaktes JSON (z.B. {"frameId":"..."}, [1,2,3])
undefined / null / "" / {} / []leer (keine Ausgabe)

Fehler gehen nach stderr, Exit-Code 1. Die CDP-Fehlermeldung und der JS-Stacktrace werden unverändert ausgegeben, z.B.:

Error: CDP -32602: invalid params
    at _call (.../session.ts:117:33)
    ...

Erkenne Fehler mit if browser-harness-js '...'; then ...; else handle_error; fi oder durch Überprüfung von $?.

Mehrzeilige Snippets via stdin (Heredoc). Wichtig: Ein mehrzeiliges Snippet gibt den letzten Ausdruck nicht automatisch zurück – schreibe explizit return X. Einzelausdruck-Snippets, die als erstes Argument übergeben werden, geben automatisch zurück.

browser-harness-js <<'EOF'
const tabs = await listPageTargets();
globalThis.tid = tabs[0].targetId;
await session.use(globalThis.tid);
return globalThis.tid;
EOF

CLI-Befehle

BefehlVerhalten
browser-harness-js '<js>'Server bei Bedarf automatisch starten, JS auswerten, Ergebnis ausgeben.
browser-harness-js <<EOF…EOFGleiches, Code von stdin.
browser-harness-js --statusJSON-Status ausgeben (Laufzeit, verbunden, sessionId) oder Exit 1, falls nicht erreichbar.
browser-harness-js --startExpliziter Start (ohne Effekt, falls bereits aktiv).
browser-harness-js --stopSanftes Herunterfahren. Sitzungszustand wird verworfen.
browser-harness-js --restartStoppen + Neustart.
browser-harness-js --logstail -f des Server-Logs (/tmp/browser-harness-js.log).

Umgebungsvariablen: CDP_REPL_PORT (Standard 9876), CDP_REPL_LOG (Standard /tmp/browser-harness-js.log).

API-Oberfläche innerhalb von Snippets

Diese globalen Variablen sind vorinstalliert – keine Importe nötig:

  • session – die persistente Session. Sie enthält alle CDP-Domänen: session.Page, session.DOM, session.Runtime, session.Network, … insgesamt 56 Domänen, 652 Methoden.
  • listPageTargets() – listet echte Seiten-Targets über CDPs Target.getTargets auf (funktioniert auch mit Chrome 144+), wobei chrome://- und devtools://-URLs herausgefiltert werden. Keine Argumente – verwendet die verbundene Session.
  • detectBrowsers() – durchsucht betriebssystemspezifische Profilverzeichnisse nach laufenden Chromium-basierten Browsern mit aktiviertem Remote-Debugging. Gibt [{name, profileDir, port, wsPath, wsUrl, mtimeMs}] zurück, sortiert nach dem zuletzt gestarteten Browser.
  • resolveWsUrl(opts) – löst eine WS-URL aus {wsUrl} | {port, host?} | {profileDir} auf. Verwende für die argumentlose Auto-Erkennung stattdessen direkt session.connect().
  • CDP – die generierten Namespaces (CDP.Page, CDP.Runtime, …) für Typnamen-Referenzen.

Aufruf einer CDP-Methode

Jede Methode erwartet ein einzelnes Objekt-Argument, das den CDP-Drahtparametern entspricht; sie gibt den typisierten Rückgabewert zurück (kein result-Umschlag, keine id-Zuordnung – alles wird für dich erledigt).

// ohne Parameter
await session.DOM.enable()

// erforderliche Parameter
await session.Page.navigate({ url: 'https://example.com' })

// alle optionalen Parameter (Objekt ebenfalls optional)
await session.Page.captureScreenshot()
await session.Page.captureScreenshot({ format: 'png', quality: 80 })

// Rückgabewerte werden auf die typisierte Form reduziert
const { root } = await session.DOM.getDocument()
const { nodeId } = await session.DOM.querySelector({ nodeId: root.nodeId, selector: 'h1' })

Verbinden

Standard: Rufe einfach session.connect() ohne Argumente auf. Es erkennt automatisch laufende Chromium-basierte Browser (Chrome, Chromium, Edge, Brave, Arc, Vivaldi, Opera, Comet, Canary), indem es betriebssystemspezifische Profilverzeichnisse nach einer DevToolsActivePort-Datei durchsucht, sortiert nach dem zuletzt gestarteten Browser, und wählt den ersten aus, dessen WebSocket akzeptiert. Betriebssystemunabhängig – funktioniert unter macOS, Linux und Windows.

await session.connect()   // Auto-Erkennung

Verwende zuerst detectBrowsers(), wenn du sehen möchtest, was verfügbar ist (oder den Benutzer auswählen lassen möchtest), bevor du dich verbindest:

const found = await detectBrowsers()
// [{ name: 'Google Chrome', profileDir, port, wsPath, wsUrl, mtimeMs }, ...]

Explizite Formen – verwende diese nur, wenn die Auto-Erkennung den falschen Browser auswählt oder du bereits weißt, wohin du dich verbinden möchtest:

FormWann verwenden
{ profileDir }Einen bestimmten Browser ansprechen, wenn mehrere laufen. Liest <profileDir>/DevToolsActivePort direkt.
{ wsUrl }Du hast bereits ws://…/devtools/browser/<uuid> (z.B. von woanders weitergeleitet).
await session.connect({ profileDir: '/Users/<du>/Library/Application Support/Google/Chrome' })
await session.connect({ wsUrl: 'ws://127.0.0.1:9222/devtools/browser/<uuid>' })

Profilpfade nach Betriebssystem – verwende diese mit { profileDir }:

  • macOS: ~/Library/Application Support/<Browser> (z.B. Google/Chrome, Comet, BraveSoftware/Brave-Browser, Arc/User Data)
  • Linux: ~/.config/<browser> (z.B. google-chrome, chromium, BraveSoftware/Brave-Browser)
  • Windows: %LOCALAPPDATA%\<Browser>\User Data (z.B. Google\Chrome, Microsoft\Edge, BraveSoftware\Brave-Browser)

Das Zeitlimit pro Kandidat für den WS-Open beträgt standardmäßig 5s – aktive Browser antworten mit open/close innerhalb von ~100ms, daher sind 5s bereits großzügig. Der einzige Fall, in dem 5s zu kurz ist, ist, wenn Chrome den Allow-Dialog anzeigt und auf den Klick des Benutzers wartet. Wenn du das erwartest, übergib timeoutMs: 30000:

await session.connect({ profileDir: '/Users/<du>/Library/Application Support/Google/Chrome', timeoutMs: 30_000 })

Wenn du No detected browser accepted a connection siehst – die Browser haben DevToolsActivePort-Dateien, aber keiner bedient derzeit WS. Der häufigste Grund: Remote-Debugging ist aktiviert, aber der Benutzer hat noch nicht auf Allow im Prompt geklickt. Sage ihm, er solle auf Allow klicken und es dann erneut versuchen (oder erhöhe timeoutMs).

Auswählen eines Targets (Tabs)

Rufe nach connect() einmal session.use(targetId) auf; nachfolgende Aufrufe auf Seitenebene (Page/DOM/Runtime/Network etc.) werden automatisch an die sessionId dieses Targets weitergeleitet. Browser.*- und Target.*-Aufrufe treffen immer den Browser-Endpunkt.

const tabs = await listPageTargets()                     // keine Argumente; verwendet die verbundene Session
const sid  = await session.use(tabs[0].targetId)
await session.Page.enable()
await session.Page.navigate({ url: 'https://example.com' })

listPageTargets() verwendet CDPs Target.getTargets (nicht /json), daher funktioniert es auch mit Chrome 144+. Es filtert bereits chrome://- und devtools://-URLs heraus. Äquivalenter direkter Aufruf:

const { targetInfos } = await session.Target.getTargets({})
const tabs = targetInfos.filter(t => t.type === 'page' && !t.url.startsWith('chrome://') && !t.url.startsWith('devtools://'))

Zum Wechseln des Tabs: session.use(andereTargetId). Zum Trennen: session.setActiveSession(undefined).

Events

// Abonnieren (gibt eine unsubscribe-Funktion zurück)
const off = session.onEvent((method, params, sessionId) => { ... })

// Oder warten auf ein einzelnes passendes Event mit optionalem Prädikat + Zeitlimit
await session.Network.enable()
const ev = await session.waitFor(
  'Page.frameNavigated',
  (p) => p.frame.url.includes('example.com'),
  10_000
)

Zustand über Aufrufe hinweg speichern

Jedes Snippet wird innerhalb eines eigenen async-Wrappers ausgeführt, daher verschwinden seine let/const-Deklarationen, wenn es zurückkehrt. Um Daten weiterzutragen, hänge sie an globalThis an:

browser-harness-js '(await listPageTargets()).forEach((t,i)=>globalThis["tab"+i]=t.targetId)'
browser-harness-js 'await session.use(globalThis.tab0)'
browser-harness-js 'await session.Page.navigate({url:"https://example.com"})'

session selbst, die aktive sessionId und Event-Abonnenten werden bereits vom Server beibehalten – globale Variablen werden nur für Ad-hoc-Daten benötigt.

Verbinden zu einem laufenden Chrome (chrome://inspect-Ablauf)

Beim Anhängen an den bereits laufenden Browser des Benutzers:

  1. Versuche zuerst await session.connect() (keine Argumente) – die Auto-Erkennung behandelt jeden Chromium-basierten Browser über DevToolsActivePort. Wenn sie erfolgreich ist, bist du fertig.
  2. Wenn die Auto-Erkennung fehlschlägt mit No running browser with remote debugging detected, muss der Benutzer es aktivieren. Öffne die Inspect-Seite:
    # macOS – AppleScript gegenüber `open -a` bevorzugen (verwendet das aktuelle Profil wieder, vermeidet die Profilauswahl)
    osascript -e 'open location "chrome://inspect/#remote-debugging"'
    
    # Linux
    google-chrome 'chrome://inspect/#remote-debugging'     # oder: chromium, google-chrome-stable
    
    # Windows (PowerShell)
    Start-Process chrome 'chrome://inspect/#remote-debugging'
    
    Nur der macOS-AppleScript-Pfad vermeidet die Profilauswahl; Linux/Windows fordern den Benutzer möglicherweise auf, zuerst ein Profil auszuwählen.
  3. Aktiviere „Discovery network targets“ in chrome://inspect und klicke dann auf Allow, wenn Chrome dazu auffordert.
  4. Wenn die Auto-Erkennung den falschen Browser auswählt (mehrere laufen, du möchtest einen bestimmten): Liste sie mit await detectBrowsers() auf, dann await session.connect({ profileDir: <der gewünschte> }).
  5. Wenn session.connect() No detected browser accepted a connection zurückgibt, hat der Benutzer Remote-Debugging aktiviert, aber noch nicht auf Allow geklickt. Sage ihm, er solle darauf klicken und es erneut versuchen, oder übergib timeoutMs: 30000, um auf den Klick zu warten.

Arbeiten mit Targets (Tabs)

  • Chrome-Interna filtern. listPageTargets() verwirft bereits chrome://- und devtools://-URLs. Wenn du Target.getTargets() direkt aufrufst, filtere manuell.
  • CDP-Target-Reihenfolge ≠ sichtbare Tab-Leisten-Reihenfolge. Wenn der Benutzer „den ersten Tab, den ich sehen kann“ sagt, verwende einen Screenshot oder den Seitentitel, um ihn zu identifizieren – Target.activateTarget wechselt nur zu einer bekannten targetId.

Nachschlagen einer Methode

Die vollständig typisierte Oberfläche befindet sich in <skill-verz>/sdk/generated.ts (~655 KB, wird nur geladen, wenn du sie liest). Jede Methode hat ihre CDP-Beschreibung als JSDoc-Kommentar plus typisierte *Params- / *Return-Schnittstellen in domänenspezifischen Namespaces.

grep -n "navigate" <skill-verz>/sdk/generated.ts | head

Neugenerieren des SDK

Wenn sich die Upstream-Protokoll-JSONs ändern, ersetze sdk/browser_protocol.json und/oder sdk/js_protocol.json und führe erneut aus:

cd <skill-verz>/sdk && bun gen.ts
browser-harness-js --restart   # neue Bindungen übernehmen

Dateien

Alle Pfade sind relativ zu <skill-verz> (dem Installationspfad – siehe oben in diesem Dokument).

  • /usr/local/bin/browser-harness-js → <skill-verz>/sdk/browser-harness-js (das CLI)
  • sdk/repl.ts – HTTP-Server (Bun.serve auf 127.0.0.1:9876)
  • sdk/session.ts – Session-Klasse (Transport, Verbindung, Target-Routing, Events)
  • sdk/generated.ts – Codegen-Ausgabe: jede CDP-Methode als typisierter Wrapper
  • sdk/gen.ts – Codegen-Skript
  • sdk/{browser,js}_protocol.json – Upstream-Protokoll (eingefroren)

Verwandte Skills