Communitygithub.com

browser-use/browser-harness-js

透過 DevTools 協定從 JavaScript 驅動 Chrome。使用 `browser-harness-js` CLI 執行 JS 程式碼片段 — 它會自動生成一個長效的 bun HTTP 伺服器,持有一個完整型別的 CDP `Session`,每次呼叫都針對同一個持久連線執行。

browser-harness-js 是什麼?

該技能提供了一個強大的工具,用於從 JavaScript 環境(如 Claude Code、Cursor 等 AI 代理)自動化和檢查 Chrome 瀏覽器。它通過 `browser-harness-js` CLI 暴露了一個持久化的 CDP(Chrome DevTools 協定)會話,允許用戶在單個或多個標籤頁上執行任意 JS 程式碼片段,並在多次呼叫之間保持狀態(如全域變數、活動目標)。內部實現包括一個自定義的程式碼生成 SDK,封裝了所有 CDP 方法(超過 650 個),以及一個輕量級的 HTTP 伺服器,用於處理 JS 評估。該技能支援自動檢測運行中的基於 Chromium 的瀏覽器(Chrome、Edge、Brave 等),並通過 `DevToolsActivePort` 檔案進行連接,也支援顯式指定的 WS URL 或配置檔案目錄。此外,還提供了目標管理(標籤頁切換)、事件監聽、錯誤處理以及跨呼叫持久化狀態(通過 `globalThis`)的功能,非常適合需要持續與瀏覽器互動的場景,例如端到端測試、網頁抓取或自動化除錯。

相容平台✓Claude Code~Codex CLI✓Cursor
npx skills add browser-use/browser-harness-js

在你喜歡的 AI 中提問

開啟一個已預先載入此 Agent Skill 的新對話。

說明文件

CDP — browser-harness-js 技能

自定義程式碼生成的 CDP SDK(來自 browser_protocol.json + js_protocol.json 的每個方法都有型別包裝器),加上一個持有單一持久 CDP Session 的小型 HTTP 伺服器。browser-harness-js CLI 會在首次使用時自動啟動伺服器,並將 JS 程式碼片段轉發給它。

SDK 位於技能的 sdk/ 目錄中。在本文檔的其餘部分,<skill-dir> 指的是 npx skills add 安裝技能的位置(Claude Code:~/.claude/skills/cdp;Cursor:~/.cursor/skills/cdp;其他代理可能不同)。CLI 應在 PATH 上可用,名為 browser-harness-js。

設定(一次,首次使用)

npx skills add 會將技能放入代理的技能目錄,但不會將 CLI 加入 PATH。在第一次呼叫之前,請確認它可以被找到,如果沒有,則將其符號連結到 PATH 中的任何目錄:

# 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)/ 大多數 Linux — 可能需要 sudo
command -v browser-harness-js >/dev/null || ln -sf <skill-dir>/sdk/browser-harness-js /usr/local/bin/browser-harness-js

# Linux 沒有 sudo(確保 ~/.local/bin 在 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; }

CLI 在首次運行時會自動安裝 bun(如果缺少)。設定 BROWSER_HARNESS_SKIP_BUN_INSTALL=1 來選擇不安裝。

使用方法

直接執行 browser-harness-js '<JS>'。第一次呼叫會在背景啟動伺服器;後續呼叫會命中同一個進程,因此會重用相同的 session、相同的連接到 Chrome 的 WebSocket,以及你設定的任何全域變數。

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'

輸出是原始結果內容 — 沒有 {ok,result} 封裝。

結果類型stdout
字串純文字,無 JSON 引號(例如 Example Domain)
數字 / 布林值42, true
物件 / 陣列(非空)緊湊 JSON(例如 {"frameId":"..."}, [1,2,3])
undefined / null / "" / {} / []空(無輸出)

錯誤會輸出到 stderr,退出碼為 1。CDP 錯誤訊息和 JS 堆疊會照原樣列印,例如:

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

使用 if browser-harness-js '...'; then ...; else handle_error; fi 或檢查 $? 來檢測失敗。

透過 stdin(heredoc)的多行程式碼片段。 重要:多陳述式片段不會自動返回最後一個表達式 — 請明確撰寫 return X。作為第一個參數傳遞的單表達式片段會自動返回。

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

CLI 命令

命令行為
browser-harness-js '<js>'如有需要自動啟動伺服器,評估 JS,列印結果。
browser-harness-js <<EOF…EOF同上,程式碼來自 stdin。
browser-harness-js --status列印健康 JSON(執行時間、連線狀態、sessionId),若伺服器關閉則退出碼 1。
browser-harness-js --start明確啟動(如果已在運行則無操作)。
browser-harness-js --stop優雅關閉。丟棄 session 狀態。
browser-harness-js --restart停止 + 重新啟動。
browser-harness-js --logstail -f 伺服器日誌(/tmp/browser-harness-js.log)。

環境變數:CDP_REPL_PORT(預設 9876),CDP_REPL_LOG(預設 /tmp/browser-harness-js.log)。

片段內部的 API 介面

這些全域變數已預先載入 — 無需匯入:

  • session — 持久的 Session。掛載了所有 CDP 領域:session.Page、session.DOM、session.Runtime、session.Network、… 總共 56 個領域,652 個方法。
  • listPageTargets() — 透過 CDP 的 Target.getTargets 列出真實頁面目標(在 Chrome 144+ 上也適用),並已過濾掉 chrome:// 和 devtools:// URL。無需參數 — 使用已連線的 session。
  • detectBrowsers() — 掃描作業系統特定的設定檔目錄,尋找正在運行且啟用了遠端除錯的基於 Chromium 的瀏覽器。返回 [{name, profileDir, port, wsPath, wsUrl, mtimeMs}],按最近啟動排序。
  • resolveWsUrl(opts) — 從 {wsUrl} | {port, host?} | {profileDir} 解析 WS URL。對於無參數的自動檢測流程,請直接呼叫 session.connect()。
  • CDP — 生成的命名空間(CDP.Page、CDP.Runtime、…),用於型別名稱參考。

呼叫 CDP 方法

每個方法都接受一個與 CDP 線路參數匹配的單一物件參數;它解析為型別化的返回值(沒有 result 封裝,沒有 id 關聯 — 已為你處理)。

// 無參數
await session.DOM.enable()

// 必需參數
await session.Page.navigate({ url: 'https://example.com' })

// 全部為可選參數(物件也是可選的)
await session.Page.captureScreenshot()
await session.Page.captureScreenshot({ format: 'png', quality: 80 })

// 返回值已被剝離為型別化的形狀
const { root } = await session.DOM.getDocument()
const { nodeId } = await session.DOM.querySelector({ nodeId: root.nodeId, selector: 'h1' })

連線

預設:只需使用無參數呼叫 session.connect()。 它會透過掃描作業系統特定的設定檔目錄中的 DevToolsActivePort 檔案,自動檢測正在運行的基於 Chromium 的瀏覽器(Chrome、Chromium、Edge、Brave、Arc、Vivaldi、Opera、Comet、Canary),按最近啟動排序,並選取第一個其 WebSocket 接受連線的瀏覽器。作業系統無關 — 適用於 macOS、Linux、Windows。

await session.connect()   // 自動檢測

如果你想知道有哪些瀏覽器可用(或讓使用者選擇),請先使用 detectBrowsers():

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

明確形式 — 僅在自動檢測選錯瀏覽器時,或你已經知道要連線到哪裡時使用:

形式使用時機
{ profileDir }當多個瀏覽器正在運行時,鎖定特定瀏覽器。直接讀取 <profileDir>/DevToolsActivePort。
{ wsUrl }你已經有 ws://…/devtools/browser/<uuid>(例如從其他地方傳遞而來)。
await session.connect({ profileDir: '/Users/<you>/Library/Application Support/Google/Chrome' })
await session.connect({ wsUrl: 'ws://127.0.0.1:9222/devtools/browser/<uuid>' })

各作業系統的設定檔路徑 — 與 { profileDir } 搭配使用:

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

對每個候選者的 WS 開啟超時時間預設為 5 秒 — 正在運行的瀏覽器通常會在 100 毫秒內回應開啟/關閉,因此 5 秒已經很寬裕。唯一 5 秒太短的情況是當 Chrome 顯示允許彈出視窗並等待使用者點擊時。如果你預期這種情況,請傳入 timeoutMs: 30000:

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

如果你看到 No detected browser accepted a connection — 瀏覽器有 DevToolsActivePort 檔案,但目前沒有瀏覽器在提供 WS 服務。最常見的原因是:遠端除錯已啟用,但使用者尚未在提示上點擊允許。請告知他們點擊允許,然後重試(或增加 timeoutMs)。

選取目標(標籤頁)

在 connect() 之後,呼叫 session.use(targetId) 一次;後續的頁面級別呼叫(Page/DOM/Runtime/Network 等)會自動路由到該目標的 sessionId。Browser.* 和 Target.* 呼叫始終命中瀏覽器端點。

const tabs = await listPageTargets()                     // 無需參數;使用已連線的 session
const sid  = await session.use(tabs[0].targetId)
await session.Page.enable()
await session.Page.navigate({ url: 'https://example.com' })

listPageTargets() 使用 CDP 的 Target.getTargets(而不是 /json),因此它也可以在 Chrome 144+ 上運作。它已經過濾掉了 chrome:// 和 devtools:// URL。等效的原始呼叫:

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

切換標籤頁:session.use(otherTargetId)。解除連接:session.setActiveSession(undefined)。

事件

// 訂閱(返回一個取消訂閱函式)
const off = session.onEvent((method, params, sessionId) => { ... })

// 或者等待一個匹配的單一事件,帶有可選的斷言和超時
await session.Network.enable()
const ev = await session.waitFor(
  'Page.frameNavigated',
  (p) => p.frame.url.includes('example.com'),
  10_000
)

跨呼叫持久化狀態

每個程式碼片段在自己的非同步包裝器中執行,因此其 let/const 宣告在返回時會消失。要將資料傳遞到下一次呼叫,請附加到 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 本身、活動的 sessionId 和事件訂閱者已由伺服器保留 — 全域變數僅用於隨附資料。

連線到正在執行的 Chrome(chrome://inspect 流程)

當連接到用戶已正在運行的瀏覽器時:

  1. 首先嘗試 await session.connect()(無參數)— 自動檢測透過 DevToolsActivePort 處理所有基於 Chromium 的瀏覽器。如果成功返回,你就完成了。
  2. 如果自動檢測失敗,出現 No running browser with remote debugging detected,使用者需要手動開啟。開啟檢查頁面:
    # macOS — 偏好使用 AppleScript 而非 `open -a`(重用當前設定檔,避免設定檔選擇器)
    osascript -e 'open location "chrome://inspect/#remote-debugging"'
    
    # Linux
    google-chrome 'chrome://inspect/#remote-debugging'     # 或:chromium, google-chrome-stable
    
    # Windows(PowerShell)
    Start-Process chrome 'chrome://inspect/#remote-debugging'
    
    只有 macOS 的 AppleScript 路徑可以避免設定檔選擇器;Linux/Windows 可能會提示使用者先選取設定檔。
  3. 在 chrome://inspect 中勾選「Discover network targets」,然後在 Chrome 提示時點擊允許。
  4. 如果自動檢測選錯了瀏覽器(多個瀏覽器正在運行,你想要特定的那個):使用 await detectBrowsers() 列出它們,然後 await session.connect({ profileDir: <你想要的設定檔> })。
  5. 如果 session.connect() 返回 No detected browser accepted a connection,使用者已啟用遠端除錯但尚未點擊允許。告知他們點擊它並重試,或傳入 timeoutMs: 30000 等待點擊。

處理目標(標籤頁)

  • 過濾 Chrome 內部頁面。 listPageTargets() 已經丟棄了 chrome:// 和 devtools:// URL。如果你直接呼叫 Target.getTargets(),請手動過濾。
  • CDP 目標順序 ≠ 可見的標籤頁順序。 當使用者說「我看到的第一個標籤頁」時,請使用螢幕截圖或頁面標題來識別它 — Target.activateTarget 僅切換到已知的 targetId。

查詢方法

完整的型別化 API 在 <skill-dir>/sdk/generated.ts 中(約 655 KB,僅在你讀取它時才會載入)。每個方法都有其 CDP 描述作為 JSDoc 註解,以及每個領域命名空間中的型別化 *Params / *Return 介面。

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

重新生成 SDK

當上游協定 JSON 變更時,替換 sdk/browser_protocol.json 和/或 sdk/js_protocol.json 並重新執行:

cd <skill-dir>/sdk && bun gen.ts
browser-harness-js --restart   # 載入新的繫結

檔案

所有路徑都相對於 <skill-dir>(安裝路徑 — 請參閱本文檔頂部)。

  • /usr/local/bin/browser-harness-js → <skill-dir>/sdk/browser-harness-js(CLI)
  • sdk/repl.ts — HTTP 伺服器(Bun.serve 在 127.0.0.1:9876)
  • sdk/session.ts — Session 類別(傳輸、連線、目標路由、事件)
  • sdk/generated.ts — 程式碼生成輸出:每個 CDP 方法作為型別化包裝器
  • sdk/gen.ts — 程式碼生成腳本
  • sdk/{browser,js}_protocol.json — 上游協定(已帶入 Vendor)

相關技能