Communitygithub.com

browser-use/browser-harness-js

Chrome DevTools Protocol(CDP)을 JavaScript에서 직접 제어합니다. `browser-harness-js` CLI를 통해 JS 스니펫을 실행하면, 자동으로 장기 실행 Bun HTTP 서버가 생성되고, 모든 호출이 동일한 영구 CDP 세션에서 실행됩니다.

browser-harness-js란 무엇인가요?

이 스킬은 CDP(Chrome DevTools Protocol)를 JavaScript 환경에서 직접 제어할 수 있는 강력한 도구입니다. `browser-harness-js` CLI는 자체적으로 Bun 기반 HTTP 서버를 백그라운드에서 실행하며, 단일 영구 CDP `Session`을 유지합니다. 사용자는 간단한 CLI 명령어 한 줄로 Chrome 브라우저를 자동화하고, 탭을 관리하며, 페이지를 탐색하고, JavaScript를 평가할 수 있습니다. 전체 CDP API(56개 도메인, 652개 메서드)가 타이핑된 형태로 제공되며, `session.Page.navigate()`, `session.DOM.querySelector()` 등의 메서드를 직관적으로 호출할 수 있습니다. 다중 탭 지원, 이벤트 구독, 전역 상태 유지 등 고급 기능도 제공하며, Chrome, Edge, Brave, Arc 등 주요 크로미움 기반 브라우저에서 자동 감지 기능을 지원합니다. 주로 Claude Code, Cursor, Codex 등 AI 에이전트 환경에서 사용됩니다.

지원 대상✓Claude Code~Codex CLI✓Cursor
npx skills add browser-use/browser-harness-js

즐겨 사용하는 AI에게 물어보기

이 에이전트 스킬이 미리 로드된 새 채팅을 엽니다.

문서

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는 browser-harness-js로 PATH에 있어야 합니다.

설정 (최초 1회)

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이 없으면 자동으로 설치합니다(서버는 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
문자열따옴표 없는 일반 텍스트 (예: 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 또는 $? 확인을 통해 수행합니다.

여러 줄 스니펫 (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) 또는 다운 시 exit 1.
browser-harness-js --start명시적 시작 (이미 실행 중이면 아무 동작 안함).
browser-harness-js --stop정상 종료. 세션 상태 해제.
browser-harness-js --restart중지 후 새로 시작.
browser-harness-js --logs서버 로그 tail -f (/tmp/browser-harness-js.log).

환경 변수: CDP_REPL_PORT (기본값 9876), CDP_REPL_LOG (기본값 /tmp/browser-harness-js.log).

스니펫 내 API 표면

다음 전역 변수는 사전 로드되어 있습니다 — import가 필요 없습니다:

  • session — 영구 Session. 모든 CDP 도메인이 마운트되어 있습니다: session.Page, session.DOM, session.Runtime, session.Network, … 총 56개 도메인, 652개 메서드.
  • listPageTargets() — CDP의 Target.getTargets를 통해 실제 페이지 대상을 나열합니다(Chrome 144+에서도 작동). chrome:// 및 devtools:// URL은 필터링됩니다. 인자 없음 — 연결된 세션을 사용합니다.
  • detectBrowsers() — OS별 프로필 디렉토리에서 원격 디버깅이 활성화된 실행 중인 크로미움 기반 브라우저를 스캔합니다. [{name, profileDir, port, wsPath, wsUrl, mtimeMs}]를 반환하며, 가장 최근에 실행된 순서로 정렬됩니다.
  • resolveWsUrl(opts) — {wsUrl} | {port, host?} | {profileDir}에서 WebSocket URL을 확인합니다. 인자 없는 자동 감지 흐름의 경우 대신 session.connect()를 직접 호출하세요.
  • CDP — 생성된 네임스페이스(CDP.Page, CDP.Runtime, …)로, 타입 이름 참조용입니다.

CDP 메서드 호출

모든 메서드는 CDP 와이어 파라미터와 일치하는 단일 객체 인자를 받으며, 타이핑된 반환 값으로 resolve됩니다(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()를 호출하기만 하면 됩니다. 실행 중인 크로미움 기반 브라우저(Chrome, Chromium, Edge, Brave, Arc, Vivaldi, Opera, Comet, Canary)를 OS별 프로필 디렉토리에서 DevToolsActivePort 파일을 스캔하여 자동 감지하고, 가장 최근에 실행된 순서대로 WebSocket을 수락하는 첫 번째 브라우저를 선택합니다. OS에 구애받지 않음 — 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>' })

OS별 프로필 경로 — { 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)

후보별 WebSocket 열기 타임아웃 기본값은 5초입니다 — 실행 중인 브라우저는 약 100ms 내에 열기/닫기로 응답하므로 5초면 충분합니다. 5초가 너무 짧은 유일한 경우는 Chrome이 Allow 팝업을 표시하고 사용자 클릭을 기다리는 경우입니다. 이를 예상한다면 timeoutMs: 30000을 전달하세요:

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

No detected browser accepted a connection 메시지가 표시되는 경우 — 브라우저에 DevToolsActivePort 파일은 있지만 현재 WebSocket을 서비스하고 있지 않습니다. 가장 일반적인 원인: 원격 디버깅이 활성화되어 있지만 사용자가 프롬프트에서 Allow를 아직 클릭하지 않은 경우입니다. 사용자에게 Allow를 클릭한 후 재시도하라고 안내하거나 timeoutMs를 늘리십시오.

대상(탭) 선택

connect() 후, session.use(targetId)를 한 번 호출하면 이후 페이지 레벨 호출(Page/DOM/Runtime/Network 등)이 해당 target의 sessionId로 자동 라우팅됩니다. Browser.* 및 Target.* 호출은 항상 브라우저 엔드포인트에 도달합니다.

const tabs = await listPageTargets()                     // 인자 없음; 연결된 세션 사용
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를 통해 처리합니다. 성공하면 완료입니다.
  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이 프롬프트를 표시하면 Allow를 클릭합니다.
  4. 자동 감지가 잘못된 브라우저를 선택한 경우 (여러 개 실행 중, 특정 브라우저를 원하는 경우): await detectBrowsers()로 목록을 확인한 다음 await session.connect({ profileDir: <원하는 프로필> })를 사용합니다.
  5. session.connect()가 No detected browser accepted a connection을 반환하는 경우, 사용자가 원격 디버깅은 켰지만 Allow를 아직 클릭하지 않은 것입니다. 클릭한 후 재시도하라고 안내하거나 timeoutMs: 30000을 전달하여 클릭을 기다리십시오.

대상(탭) 작업

  • Chrome 내부 필터링. listPageTargets()는 이미 chrome:// 및 devtools:// URL을 제거합니다. Target.getTargets()를 직접 호출하는 경우 수동으로 필터링하세요.
  • CDP 대상 순서 ≠ 표시된 탭 순서. 사용자가 "내가 볼 수 있는 첫 번째 탭"이라고 말하면 스크린샷이나 페이지 제목을 사용하여 식별하세요 — Target.activateTarget은 알려진 targetId로만 전환됩니다.

메서드 조회

전체 타이핑된 표면은 <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 on 127.0.0.1:9876)
  • sdk/session.ts — Session 클래스 (전송, 연결, 대상 라우팅, 이벤트)
  • sdk/generated.ts — 코드 생성 출력: 모든 CDP 메서드의 타이핑된 래퍼
  • sdk/gen.ts — 코드 생성 스크립트
  • sdk/{browser,js}_protocol.json — 업스트림 프로토콜 (벤더됨)

관련 스킬