Communitygithub.com

browser-use/browser-harness-js

Controla o Chrome via Protocolo DevTools a partir de JavaScript. Execute trechos JS através da CLI `browser-harness-js` – ela inicia automaticamente um servidor HTTP Bun de longa duração que mantém uma `Session` CDP totalmente tipada, e cada chamada executa na mesma conexão persistente.

O que é browser-harness-js?

O skill `browser-harness-js` fornece uma interface de linha de comando e um servidor HTTP persistente para automatizar e inspecionar o navegador Chrome (e outros baseados em Chromium) usando o Protocolo DevTools (CDP). Ele é ideal para desenvolvedores e agentes de IA que precisam executar scripts JavaScript arbitrários contra uma única sessão do navegador, mantendo estado entre chamadas. O skill utiliza um SDK customizado gerado a partir dos protocolos `browser_protocol.json` e `js_protocol.json`, oferecendo wrappers tipados para todos os métodos CDP. Suporta múltiplas abas, detecção automática de navegadores em execução, conexão a navegadores remotos, e persistência de dados entre execuções via `globalThis`. Compativel com Claude Code, Cursor, Codex e outros agentes que suportam o sistema de skills da Anthropic.

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

Perguntar na sua IA favorita

Abre um novo chat com esta habilidade de agente já pré-carregada.

Documentação

CDP — skill browser-harness-js

SDK CDP customizado com geração de código (cada método de browser_protocol.json + js_protocol.json recebe um wrapper tipado) mais um pequeno servidor HTTP que mantém uma Session CDP persistente. A CLI browser-harness-js inicia automaticamente o servidor no primeiro uso e encaminha trechos JS para ele.

O SDK está no diretório sdk/ do skill. No restante deste documento, <skill-dir> refere-se ao local onde npx skills add instalou o skill (Claude Code: ~/.claude/skills/cdp; Cursor: ~/.cursor/skills/cdp; outros agentes variam). A CLI deve estar no PATH como browser-harness-js.

Configuração (uma vez, primeiro uso)

npx skills add coloca o skill no diretório de skills do seu agente, mas NÃO coloca a CLI no PATH. Antes da primeira chamada, verifique se ela está acessível e crie um link simbólico para qualquer diretório no seu PATH, se necessário:

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

# macOS (Intel) / maioria Linux — pode precisar de sudo
command -v browser-harness-js >/dev/null || ln -sf <skill-dir>/sdk/browser-harness-js /usr/local/bin/browser-harness-js

# Linux sem sudo (certifique-se de que ~/.local/bin está no PATH)
command -v browser-harness-js >/dev/null || { mkdir -p ~/.local/bin && ln -sf <skill-dir>/sdk/browser-harness-js ~/.local/bin/browser-harness-js; }

A CLI instala automaticamente o bun na primeira execução se estiver faltando (o servidor é nativo do Bun). Defina BROWSER_HARNESS_SKIP_BUN_INSTALL=1 para desativar.

Como usar

Basta executar browser-harness-js '<JS>'. A primeira chamada inicia o servidor em segundo plano; chamadas subsequentes atingem o mesmo processo e, portanto, reutilizam a mesma session, o mesmo WebSocket para o Chrome e quaisquer globais que você definiu.

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'

A saída é o conteúdo bruto do resultado — sem envelope {ok,result}.

Tipo de resultadostdout
stringtexto simples, sem aspas JSON (ex.: Example Domain)
número / booleano42, true
objeto / array (não vazio)JSON compacto (ex.: {"frameId":"..."}, [1,2,3])
undefined / null / "" / {} / []vazio (nenhuma saída)

Erros vão para stderr, código de saída 1. A mensagem de erro CDP e o stack JS são impressos literalmente, ex.:

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

Detecte falhas com if browser-harness-js '...'; then ...; else handle_error; fi ou verificando $?.

Trechos multilinha via stdin (heredoc). Importante: um trecho com várias instruções NÃO retorna automaticamente a última expressão — escreva return X explicitamente. Trechos de expressão única passados como primeiro argumento retornam automaticamente.

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

Comandos da CLI

ComandoComportamento
browser-harness-js '<js>'Inicia servidor automaticamente se necessário, avalia o JS, imprime resultado.
browser-harness-js <<EOF…EOFMesmo, código do stdin.
browser-harness-js --statusImprime JSON de saúde (uptime, connected, sessionId) ou sai com 1 se estiver parado.
browser-harness-js --startInicialização explícita (no-op se já estiver rodando).
browser-harness-js --stopDesligamento gracioso. Descarta estado da sessão.
browser-harness-js --restartPara + inicia do zero.
browser-harness-js --logstail -f o log do servidor (/tmp/browser-harness-js.log).

Variáveis de ambiente: CDP_REPL_PORT (padrão 9876), CDP_REPL_LOG (padrão /tmp/browser-harness-js.log).

Superfície da API dentro de trechos

Estas globais são pré-carregadas — sem necessidade de imports:

  • session — a Session persistente. Possui todos os domínios CDP montados: session.Page, session.DOM, session.Runtime, session.Network, … 56 domínios, 652 métodos no total.
  • listPageTargets() — lista alvos de página reais via Target.getTargets do CDP (funciona também no Chrome 144+), com URLs chrome:// e devtools:// filtradas. Sem argumentos — usa a sessão conectada.
  • detectBrowsers() — varre diretórios de perfil específicos do SO em busca de navegadores baseados em Chromium em execução com depuração remota ativada. Retorna [{name, profileDir, port, wsPath, wsUrl, mtimeMs}], ordenados pelo mais recentemente iniciado.
  • resolveWsUrl(opts) — resolve uma URL WS a partir de {wsUrl} | {port, host?} | {profileDir}. Para o fluxo de detecção automática sem argumentos, chame session.connect() diretamente.
  • CDP — os namespaces gerados (CDP.Page, CDP.Runtime, …) para referência de nomes de tipos.

Chamando um método CDP

Cada método recebe um único argumento objeto correspondente aos parâmetros CDP; ele resolve para o valor de retorno tipado (sem envelope result, sem correlação id — tratado para você).

// sem parâmetros
await session.DOM.enable()

// parâmetros obrigatórios
await session.Page.navigate({ url: 'https://example.com' })

// parâmetros opcionais (objeto também opcional)
await session.Page.captureScreenshot()
await session.Page.captureScreenshot({ format: 'png', quality: 80 })

// retornos são simplificados para a forma tipada
const { root } = await session.DOM.getDocument()
const { nodeId } = await session.DOM.querySelector({ nodeId: root.nodeId, selector: 'h1' })

Conectando

Padrão: apenas chame session.connect() sem argumentos. Ele detecta automaticamente navegadores baseados em Chromium em execução (Chrome, Chromium, Edge, Brave, Arc, Vivaldi, Opera, Comet, Canary) varrendo diretórios de perfil específicos do SO em busca de um arquivo DevToolsActivePort, ordenados pelo mais recentemente iniciado, e escolhe o primeiro cujo WebSocket aceite. Independente de SO — funciona no macOS, Linux, Windows.

await session.connect()   // detecção automática

Use detectBrowsers() primeiro se quiser ver o que está disponível (ou deixar o usuário escolher) antes de conectar:

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

Formas explícitas — use-as apenas quando a detecção automática escolher o navegador errado, ou quando você já souber onde se conectar:

FormaQuando usar
{ profileDir }Segmentar um navegador específico quando vários estiverem em execução. Lê <profileDir>/DevToolsActivePort diretamente.
{ wsUrl }Você já tem ws://…/devtools/browser/<uuid> (ex.: redirecionado de outro lugar).
await session.connect({ profileDir: '/Users/<you>/Library/Application Support/Google/Chrome' })
await session.connect({ wsUrl: 'ws://127.0.0.1:9222/devtools/browser/<uuid>' })

Caminhos de perfil por SO — use estes com { profileDir }:

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

O tempo limite padrão de abertura WS por candidato é 5s — navegadores ativos respondem com abertura/fechamento em ~100ms, portanto 5s já é generoso. O único caso em que 5s é curto demais é quando o Chrome está exibindo o popup Permitir e aguardando o clique do usuário. Se você espera isso, passe timeoutMs: 30000:

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

Se você receber Nenhum navegador detectado aceitou uma conexão — os navegadores têm arquivos DevToolsActivePort, mas nenhum está servindo WS no momento. A causa mais comum: a depuração remota está ativada, mas o usuário ainda não clicou em Permitir no prompt. Diga a ele para clicar em Permitir e tentar novamente (ou aumente timeoutMs).

Escolhendo um alvo (aba)

Após connect(), chame session.use(targetId) uma vez; chamadas subsequentes no nível da página (Page/DOM/Runtime/Network/etc.) roteiam automaticamente para o sessionId desse alvo. Chamadas Browser.* e Target.* sempre atingem o endpoint do navegador.

const tabs = await listPageTargets()                     // sem argumentos; usa a sessão conectada
const sid  = await session.use(tabs[0].targetId)
await session.Page.enable()
await session.Page.navigate({ url: 'https://example.com' })

listPageTargets() usa Target.getTargets do CDP (não /json), portanto funciona também no Chrome 144+. Ele já filtra URLs chrome:// e devtools://. Chamada bruta equivalente:

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

Para alternar abas: session.use(outroTargetId). Para desanexar: session.setActiveSession(undefined).

Eventos

// Inscrever-se (retorna uma função canceladora)
const off = session.onEvent((method, params, sessionId) => { ... })

// Ou aguardar um único evento correspondente com predicado opcional + tempo limite
await session.Network.enable()
const ev = await session.waitFor(
  'Page.frameNavigated',
  (p) => p.frame.url.includes('example.com'),
  10_000
)

Persistindo estado entre chamadas

Cada trecho é executado dentro de seu próprio wrapper assíncrono, portanto suas declarações let/const desaparecem quando ele retorna. Para transportar dados adiante, anexe a globalThis:

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 em si, o sessionId ativo e os inscritos em eventos já são preservados pelo servidor — globais são necessárias apenas para dados ad-hoc.

Conectando a um Chrome em execução (fluxo chrome://inspect)

Ao anexar ao navegador já em execução do usuário:

  1. Tente await session.connect() primeiro (sem argumentos) — a detecção automática lida com todos os navegadores baseados em Chromium via DevToolsActivePort. Se retornar, você terminou.
  2. Se a detecção automática falhar com Nenhum navegador em execução com depuração remota detectado, o usuário precisa ativá-la. Abra a página de inspeção:
    # macOS — prefira AppleScript a `open -a` (reutiliza o perfil atual, evita o seletor de perfil)
    osascript -e 'open location "chrome://inspect/#remote-debugging"'
    
    # Linux
    google-chrome 'chrome://inspect/#remote-debugging'     # ou: chromium, google-chrome-stable
    
    # Windows (PowerShell)
    Start-Process chrome 'chrome://inspect/#remote-debugging'
    
    Apenas o caminho AppleScript do macOS evita o seletor de perfil; Linux/Windows podem solicitar que o usuário escolha um perfil primeiro.
  3. Marque "Descobrir destinos de rede" em chrome://inspect, depois clique em Permitir quando o Chrome solicitar.
  4. Se a detecção automática escolher o navegador errado (vários em execução, você quer um específico): liste-os com await detectBrowsers(), depois await session.connect({ profileDir: <o que você quer> }).
  5. Se session.connect() retornar Nenhum navegador detectado aceitou uma conexão, o usuário tem depuração remota ativada, mas ainda não clicou em Permitir. Diga a ele para clicar e tentar novamente, ou passe timeoutMs: 30000 para aguardar o clique.

Trabalhando com alvos (abas)

  • Filtrar internos do Chrome. listPageTargets() já descarta URLs chrome:// e devtools://. Se você chamar Target.getTargets() diretamente, filtre manualmente.
  • Ordem dos alvos CDP ≠ ordem da barra de abas visível. Quando o usuário disser "a primeira aba que vejo", use uma captura de tela ou título da página para identificá-la — Target.activateTarget apenas alterna para um targetId conhecido.

Procurando um método

A superfície tipada completa está em <skill-dir>/sdk/generated.ts (~655 KB, carregado apenas se você o ler). Cada método tem sua descrição CDP como comentário JSDoc mais interfaces *Params / *Return tipadas em namespaces por domínio.

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

Regenerando o SDK

Quando os JSONs de protocolo upstream mudarem, substitua sdk/browser_protocol.json e/ou sdk/js_protocol.json e execute novamente:

cd <skill-dir>/sdk && bun gen.ts
browser-harness-js --restart   # usar as novas ligações

Arquivos

Todos os caminhos são relativos a <skill-dir> (o caminho de instalação — veja o topo deste documento).

  • /usr/local/bin/browser-harness-js → <skill-dir>/sdk/browser-harness-js (a CLI)
  • sdk/repl.ts — servidor HTTP (Bun.serve em 127.0.0.1:9876)
  • sdk/session.ts — classe Session (transporte, conexão, roteamento de alvos, eventos)
  • sdk/generated.ts — saída da geração de código: cada método CDP como um wrapper tipado
  • sdk/gen.ts — script de geração de código
  • sdk/{browser,js}_protocol.json — protocolo upstream (incorporado)

Habilidades Relacionadas