mcpField
B
browser-control

browser-control

github.com/anomalycoエージェントワークフロー4671.0k 回インストール3 日前に更新
リポジトリ

browser-control について

Browser Control は、ユーザーに表示されているブラウザーで決定的な Playwright コードを実行するドライバーで、操作内容はエージェントが決めます。ページの確認と結果の検証を重視し、後続作業のためにセッションを保持します。

主な機能

  1. 01決定的な Playwright 実行
  2. 02確認・操作・検証のワークフロー
  3. 03名前付きブラウザーセッション
  4. 04既存タブの引き継ぎ
  5. 05制限付き操作での人間への引き継ぎ
  6. 06実行ジャーナル

活用シーン

  1. 01認証済みタブでの操作の自動化
  2. 02フォーム送信や遷移先の検証
  3. 03セキュリティ確認をユーザーに依頼
  4. 04プロセスをまたいだブラウザー作業の継続

MCP 対応

OpenCode プラグインとして MCP サーバーを登録し、パッケージ化されたワークフローを `@browser` スキルとして公開します。MCP 呼び出しは既定で暗黙のプロセスセッションを使い、複数セッションには明示的な ID を指定できます。

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/anomalyco/browser-control --skill browser-control

インストール後、Claude Code で/browser-controlを入力して読み込まれたか確認します。

詳細

翻訳後に原文が更新されています。最新の内容は原文をご確認ください。

ブラウザーコントロール

Browser Control はドライバーであり、エージェントではありません。呼び出し元のエージェントが実行する内容を決定し、Browser Control はユーザーに表示されているブラウザーで決定論的な Playwright コードを実行します。

Browser Control を OpenCode プラグインとしてインストールすると、プラグインはこの MCP サーバーを登録し、パッケージ化されたワークフローを @browser スキルとして公開します。このスキルを別途インストールするようユーザーに求めたり、MCP の設定を追加したりしないでください。ブラウザー拡張機能の承認は、引き続き別途ユーザーが行う必要があります。

一貫して、調査、実行、検証のサイクルを使用します。ロケーターを選ぶ前に実際のページを調査し、最も範囲が狭く安定したコントロールを通じて操作し、その後 URL または最新のページ読み取りによって結果を検証します。クリックの成功や人間による確認を、タスクが成功した証拠として扱ってはいけません。

基本的なワークフロー

1. タスクを直接実行する

まず、依頼されたブラウザー操作に取りかかります。リレー経由のコマンドはデタッチされたリレーを起動し、拡張機能を待機します。先に browser-control serve を起動しないでください。

browser-control execute 'return { url: page.url(), title: await page.title() }'

セットアップまたは実行時の動作が不明な場合に限り、browser-control doctor を使用します。 status と doctor は状態を確認するだけで、リレーは起動しません。

MCP の起動、ツールの検出、skill、session_current はリレーに接続しません。最初の運用ツール呼び出しで、必要に応じてリレーが起動します。リレー経由の状態確認ツールは、リレーを起動する代わりに、利用できないことを報告します。

リレーを外部で管理する場合は、BROWSER_CONTROL_AUTOSTART=false を設定すると、リレーが存在しないときに通常の呼び出しが別のプロセスを起動せず、失敗するようになります。既存のリレー接続と明示的な relay restart は引き続き動作します。

通常の CLI/MCP/SDK 呼び出しが、実行中のリレーを置き換えることはありません。ビルドが一致しない場合は、browser-control relay restart を実行する前に、他のエージェントと調整してください。このコマンドはブラウザーのタブと永続セッションを保持しますが、JavaScript の状態とスナップショットの参照をリセットします。ドレイン処理がビジー状態またはタイムアウトになった場合、古いリレーはそのまま実行されます。強制停止するのではなく、録画やキャプチャを完了し、生の CDP クライアントを切断してください。

browser-control doctor
browser-control status --json

完了条件:1 回の execute でページの結果と読み取り可能なセッション ID が返されるか、doctor が具体的なセットアップの失敗を特定します。

2. ページを意図的に選択する

同梱の shim 0.0.25 は、再接続時にデバッガーの所有権を確認します。この shim の更新をインストールした後、展開済みの拡張機能を再読み込みしてください。DevTools が接続されているタブは、Browser Control のインベントリから除外されます。ページの実行コンテキストを利用できない状態が続くと、page.title() の読み取りは 5 秒後にタイムアウトします。読み取りのタイムアウトによって、タブが閉じられたり置き換えられたりすることはありません。

引数を指定しない CLI の execute は、セッションが所有する新しいページを作成し、正確な --session <id> 継続コマンドを出力します。または、説明的なセッション名を渡すこともできます(絵文字は任意で指定でき、ブラウザーのタブグループとページ内のステータスピルにそのまま表示されます)。以降の CLI 呼び出しでは、必ずその ID を渡すか、BROWSER_CONTROL_SESSION を設定してください。引数を指定しない execute が、人間のシェルの現在の状態からセッションを推測することはありません。

browser-control session new "🎙️ elevenlabs"
browser-control execute --session "🎙️ elevenlabs" 'return page.url()'
browser-control execute 'return page.url()'

MCP では、暗黙のプロセスセッションが 1 つ維持されます。通常の方法では session を省略してください。1 つの MCP プロセスで複数のセッションが必要な場合は、session_new を呼び出し、明示的な ID を渡してください。

ユーザーのブラウザーで既に開いているタブを操作するには、そのタブで Browser Control のツールバーボタンをクリックするようユーザーに依頼してください。1 回の execute で使用するか、固定して再利用するために採用します(ユーザーのタブが 1 つだけ接続されている場合は、--target-url / targetUrl を省略してください)。

browser-control session adopt --session github
browser-control session adopt --target-url github.com --session github
browser-control execute --target-url github.com 'return page.url()'

execute --target-url は、その呼び出しに限ってページを選択します。--session だけで続行すると、セッションのデフォルトページが使用されます。このページは about:blank のままの場合があります。既存のユーザータブで複数の手順を実行する場合は、先にそのタブを採用してください。空のスナップショットを診断するときは、必ず page.url() を含めてください。スナップショットのラベルは簡潔な説明です。テキストが Playwright のアクセシブル名と完全に一致すると決めつけず、操作には ref() を使用してください。

targetUrl と targetIndex は、既存の接続済みページを選択します。これらによってページが移動することはありません。URL セレクターはページ 1 つのみに一致する必要があり、URL セレクターとインデックスセレクターは併用できません。採用すると、そのタブがセッションのデフォルトになり、セッションが以前にリレーで作成したページは閉じられます。また、1 つの Browser Control セッションのみがそのタブを使用できます。リセットまたは削除を行うと、採用したユーザータブは閉じずに解放されます。

採用時には、自動化の初期化前に所有権と対象の正確な ID が関連付けられます。最初の execute でページの解決が完了するまでは、セッションのステータスは connected: false と pageUrl: null を報告します。adoptedUrl はレジストリで選択された URL であり、ページから新たに読み取った値ではありません。ビジー状態のページでは、execute 時に session-page/adopted-initialization-timeout が発生する場合があります。この場合、ユーザーコードは実行されておらず、採用された対象はそのまま保持されています。ページが落ち着いてから再試行してください。復旧のためにリセットしたり、別のタブを採用したりしないでください。

新しいページでログインを再現するよりも、認証済みのブラウザー状態を採用してください。

各リレーが一度に制御できるブラウザー/プロファイルは 1 つです。2 つ目の拡張機能接続が、正常に動作しているアクティブな接続に置き換わることはありません。status または doctor が競合する接続の拒否を報告する場合は、意図したブラウザー/プロファイルだけで拡張機能を有効にしてください。ブラウザーを切り替えるには、先に既存の拡張機能接続を切断してください。新しい execute セッションを作成しても、ブラウザーは切り替わりません。

完了条件:選択したページの URL が目的のページであり、その後の作業で返されたセッション ID を引き継ぐか、MCP プロセスセッションを意図的に使用していること。

3. 調査、操作、確認

役割やセレクターを推測する前に調査してください。

return await snapshot()

次に、返された構造に基づいて操作し、移動先を確認してください。

await ref("e12").click()
await page.getByRole("heading", { name: "Settings" }).waitFor()
if (!page.url().includes("/settings")) {
  throw new Error(`Unexpected destination: ${page.url()}`)
}
return { url: page.url(), heading: await page.getByRole("heading").first().innerText() }

まず通常の Playwright を使用してください。開いているメニュー、選択中の行、ホバー状態、入力途中のフォームなど、一時的な UI に依存する操作は、1 回の execute 内でまとめて行ってください。

ネイティブの locator.fill() がハングする場合は、明示的な input、textarea、contenteditable の代替手段を使う前に、失敗の原因を調査してください。target/cross-extension-page 診断が発生した場合は、まず人間による終了操作または完了が必要です。fillInput は保護された拡張機能の UI を回避する手段ではありません。

await fillInput(page.getByPlaceholder("Username"), "standard_user")

完了条件:最終的な戻り値に、操作を試みた証拠だけではなく、要求された結果が得られた証拠が含まれていること。

フォームと外部への変更

クリックの戻り値、HTTP 200、リダイレクト、または入力欄がクリアされたことだけでは、送信が完了した証拠にはなりません。一度クリックする前に、想定されるナビゲーションを登録し、遷移先を読み取って、ワークフロー固有の受領確認または確認メッセージを検証してください。拒否や確認のチェックは、実際に確認したページに合わせてください。ドライバーは、ページ上の汎用的なテキストから業務上の成功を推測できません。

await Promise.all([
  page.waitForURL(expectedDestination, { waitUntil: "domcontentloaded", timeout: 15_000 }),
  ref(sendRef).click({ timeout: 15_000 }),
])
const observed = await snapshot()
await expectedReceipt.waitFor({ state: "visible", timeout: 5_000 })
return { url: page.url(), observed, receipt: await expectedReceipt.innerText() }

expectedDestination、sendRef、expectedReceipt は、推測したセレクターではなく、確認済みのワークフローに基づく値です。同じ URL へのナビゲーションでは、メインフレームのナビゲーションまたはレスポンスを待機する処理を登録してください。マウス座標によるクリックでは、ナビゲーションを待機する必要はありません。直後に取得したスナップショットが、まだ古いドキュメントのものである場合もあります。

送信がタイムアウトした場合や、遷移先を読み取れない場合は、未検証と報告し、再度クリックせずに一度だけ確認してください。正確なタブ/コンテキストが落ち着いてから、読み取り直してください。不確かな送信、予約、購入、支払いを再実行してはいけません。サイトから明示的に拒否された場合は、送信完了ではなく拒否です。人間による操作が必要な保護機能がある場合は、通常のページをユーザーに引き継いでください。保護トークンを変更したり、人間の操作を装ったり、セキュリティ境界を回避するために DOM クリックを実行したりしてはいけません。

4. 適切に続行または終了する

名前付きセッションは、短時間で終了する CLI および MCP プロセス間でも、デフォルトのページを保持します。また、リレーの再起動後も維持されます。Browser Control は、ID、読み取り専用モード、正確なデフォルトターゲットを復元します。JavaScript の state とスナップショットの参照はプロセス内に限定され、明示的な警告とともにリレーの再起動後にリセットされます。

browser-control session list
browser-control session reset github
browser-control session delete github

明示的なセッション ID に対する削除は冪等です。そのため、セッションがすでに存在しない場合も、安全にクリーンアップを再試行できます。

すべての execute は、~/.browser-control/sessions/<id>/journal.jsonl に記録されます。ジャーナルには、コード、ステータス、実行時間、URL の変化、警告、引き継ぎ、上限付きの診断情報が記録されます。認証情報を execute のソースに直接記述してはいけません。

browser-control journal --session github --limit 50

完了時:後続の作業が見込まれる場合に限りセッションを保持し、それ以外の場合はセッション所有のページをリセットまたは削除し、後続の作業に影響する警告があれば報告してください。

認証済みフローの標準パターン

Browser Control の特徴的なワークフローは、認証済みのタブと人間のみが対応できるプロンプトを組み合わせます。

既存のタブに接続して引き継ぎ、実際の UI を調べ、通常のフィールドに入力してから、WebAuthn、2FA、CAPTCHA、または支払い UI を起動する前に handoff を登録します。ユーザーが操作を完了したら、認証後の遷移先を確認します。MCP プロセスまたはリレーを再起動した後も、同じセッションを続行できます。

プロンプトを起動するアクション自体がブロックされる可能性がある場合は、そのアクションのみを start に指定します。Browser Control は、アクションを呼び出す前に WAIT を表示して確認します。

await handoff("Complete the security-key prompt, then continue", {
  timeoutMs: 600_000,
  start: () => page
    .getByRole("button", { name: /passkey|security key|sign in/i })
    .click({ timeout: 600_000 }),
})

await page.waitForURL((url) => !url.pathname.startsWith("/login"))
await page.getByRole("heading", { name: /account|dashboard/i }).waitFor()
return { authenticatedUrl: page.url(), title: await page.title() }

ハンドオフの解決後、Browser Control は遷移先のコンテキストが一時的に置き換わる間も待機してから戻るため、この確認は同じ execute 内で行えます。

start を指定すると、ユーザーがすでに Continue を押していても、アクションが完了するまでハンドオフの期限とターゲットのキャンセルが有効です。早期の確認によって、アクションが無期限に保留されることはありません。

別のページでハンドオフを行うには、{ page: otherPage } を渡します。準備状況のチェック対象はそのページであり、セッションのデフォルトページではありません。デフォルト以外のページが置き換えられたり閉じられたりした場合は、残っているページを調べてください。古い Playwright の参照が置き換え後のページを指すとは限りません。

どのアクションの完了を待っているかをユーザーに伝えてください。人間による確認は検証ではありません。handoff の後は、必ず想定した URL または安定した要素を確認してください。アクションがすでに完了しており、人間による操作だけが残っている場合は、start を指定せずに handoff(message) を呼び出します。デフォルトのタイムアウトは 10 分です。

完了条件:WAIT の登録後にのみプロンプトが表示され、アクションが完了し、認証後の結果が個別に検証されていること。

ユーザーが実演したフローを再利用可能な Playwright コードにするには、demonstrate() を使用します。同じタブへの正確なハンドオフを使い、クリック、編集、チェックボックスやセレクトの変更、同じタブ内でのナビゲーションを記録し、編集可能なコードを返します。

return await demonstrate("Perform the workflow once, then continue")

パスワードフィールドは値をコピーせず、シークレットの取得元を示すコメントとして明示されます。生成されたコードを再利用する前にセレクターを確認し、結果のアサーションを追加してください。実演で記録されるのはアクションであり、ワークフローの成功を証明するものではありません。

パスワードマネージャーのプロンプト

通常の Web ページのフィールドと、アクセス可能なオープンシャドウルートのコントロールは引き続き使用できます。1Password のインラインメニューは通常の Web ページの DOM ではなく、拡張機能が所有する iframe です。Chromium では、ある拡張機能が別の拡張機能のページをデバッグすることはできません。ツールバーのポップアップや、ネイティブのロック解除、Touch ID、Windows Hello のプロンプトは、Playwright で操作できる対象ではありません。

カード番号や認証情報のフィールドにフォーカスしたり入力したりするだけで、サードパーティの支払い iframe 内であっても、インラインメニューが開くことがあります。メニューが開いている間、Chrome はそのタブに対するすべての自動化コマンドを拒否します。Playwright ではこれが「Execution context was destroyed」またはロケーターのタイムアウトとして表示されるため、execute の結果には target/cross-extension-page の診断と警告が含まれ、browser-control status ではそのタブが protected-ui=true と表示されます。

target/cross-extension-page は権限の境界を示します。ユーザーにプロンプトの完了または閉じる操作を依頼し、その後もう一度試してください。回避するためにページをリセットしたり、ボールトの内容を読み取ったり、ブラウザーのセキュリティを弱めたりしないでください。ページ自体は正常です。この失敗を応答しないタブとして扱ったり、新しいページを作成したりしないでください。可能であれば、人間のみが対応できるプロンプトを起動する前に、元の Web ページで handoff を登録してください。プロンプトによってすでにアタッチできない場合は、ページ内でハンドオフを表示できると決めつけず、ユーザーに必要な操作を直接伝えてください。プロンプトの完了後に、想定した Web ページの状態を確認します。

ページに残っているパスワードマネージャーのステータステキストは、そのメニューがまだ開いている証拠にはなりません。人間が閉じた後は、リレーの最新の診断情報と通常のページの新しい読み取りを使用し、拡張機能の非公開フレームの内容は調べないでください。

検査ツール

質問に答えるために必要な、最もコストの低い表示を使用します。

  • snapshot() は、操作前に読み取るための簡潔なデフォルト表示です。セマンティックなグループ、アラート、リスト、テーブル、見出し、リンク、コントロールを優先します。テキスト入力とテキストエリアの値は省略されます。
  • デフォルトのスナップショットでは、表示されているモーダルがある場合、そのモーダルのみが対象になります。main の外にあるポータルダイアログも検出できます。背景コンテンツが意図的に必要な場合は、明示的に within スコープを指定してください。繰り返し表示されるリストのラッパーには上限付きの領域が割り当てられるため、情報量の多いページでも商品リンクを表示できます。
  • ネイティブの数値入力と検索入力は、それぞれ spinbutton/searchbox です。ネイティブの開閉コントロールには summary というラベルが付けられます。これは ARIA ロールではなく要素の種類です。操作には、返された ref() を使用してください。
  • ref("e12") は、最新のスナップショットからコントロールを解決します。ページ遷移後、または互換性のない DOM の変化が発生した場合、参照は安全のため失敗します。互換性のある参照は、同じドキュメントのスナップショットを繰り返し取得しても同じ ID を維持します。
  • snapshot({ diff: true }) は、互換性のある直前のベースラインからのセマンティックな変更を報告します。snapshot({ delta: true }) は、最初のベースライン全体を返し、その後は簡潔な差分を返します。既存の互換性のある参照は引き続き使用できます。
  • snapshot({ find: "checkout", context: 2 }) は、範囲が制限されたセマンティックなスナップショットを検索し、一致する行を周辺のコンテキストおよび操作可能な参照とともに返します。
  • ariaSnapshot(target?, { timeout }) は、簡潔なスナップショットで必要な構造が省略されている場合に、Playwright の詳細な YAML aria ツリーを返します。ネイティブのテキストコントロールの値、カスタム ARIA 範囲値、編集可能なコンテンツは省略されるため、ツールの出力には含まれません。個別に await してください。同じページで他の操作を並行して実行しないでください。
  • screenshotWithLabels({ page?, path? }) は、レイアウトが重要な場合に視覚的なラベルとメタデータを追加し、e1..eN ラベルを ref() に登録します。
  • screenshotDiff({ baseline, path?, threshold?, fullPage? }) は、保存済み PNG(絶対パスまたは Buffer)と、現在のセッションページを CSS ピクセル単位で比較します。matches、changedPixels、changedRatio(0..1)、寸法、赤く強調表示された PNG を返します。画像を execute メディアとして返すには path を省略してください。それ以外の場合は、新しい絶対パスの .png を指定してください。既存の出力ファイルが上書きされることはありません。
return await snapshot({ within: "main", maxItems: 200 })
return await snapshot({ find: /checkout|payment/i, context: 2 })
return await snapshot({ delta: true })
// When layout matters, return the image through MCP so it can be inspected.
return await screenshotWithLabels({ page })

画像を保存して "ok" のみを返しても、確認できるのはファイルが作成されたことだけであり、視覚的に正しいことの証明にはなりません。視覚的な証拠が必要な場合は、スクリーンショットのバッファーを MCP 経由で返してください。

視覚的なリグレッションチェックを行う場合は、UI を変更する前にベースラインを保存してください。

await page.screenshot({ path: "/absolute/before.png", scale: "css" })
// After the intended UI change, in the same viewport:
return await screenshotDiff({ baseline: "/absolute/before.png" })

しきい値のデフォルトは 0.1 で、許容される変更領域ではなく、ピクセルごとの色の許容誤差を制御します。ピクセルを完全一致させるには 0 に設定してください。アンチエイリアスの変化もカウントされます。アニメーションは自分で停止させ、両方のキャプチャで同じビューポートと fullPage 設定を使用してください。寸法が一致しない場合は明示的に失敗し、画像がリサイズされることはありません。比較対象は、それぞれ 32 MiB/16 メガピクセルまでの PNG に限られます。スクリーンショットと差分には、ページの表示内容が含まれます。共有する前に、個人情報が含まれていないか確認してください。

実行インターフェース

実行コードでは、page、context、browser、永続的な state、modules 経由で選択された Node モジュールと、fs や path などのエイリアスに加え、ここで説明する Browser Control ヘルパーを使用できます。実行コードは Node で動作します。window、document、ストレージ、ページ Cookie を使用した同一オリジンの fetch には、page.evaluate を使用してください。単一の式は自動的に返されます。複数の文を含むスクリプトでは return が必要です。長いスクリプトには --file を使用します。

browser-control execute --session github --file ./perform-flow.js

人が読む CLI 出力には、ログ、警告、簡潔な事後情報が含まれます。別のコマンドで ok、value、error、warnings、aftermath に基づいて分岐する必要がある場合は、--json を使用してください。

browser-control execute --json --session github '({ url: page.url() })' | jq .value.url

page.waitForEvent("download") は、Chrome のネイティブな保存確認ダイアログを開くことなく、Content-Disposition: attachment レスポンス(CDP Fetch 経由)と <a download>/blob:/data: リンクの両方をインターセプトし、suggestedFilename()、await dl.path()、await dl.saveAs(targetPath) を備えた Download を返します。

const dl = page.waitForEvent("download", { timeout: 30_000 })
await page.getByRole("button", { name: /Generate a private key/i }).click()
const download = await dl
await download.saveAs("/tmp/private-key.pem")
return { filename: download.suggestedFilename(), savedPath: await download.path() }

WebMCP が有効なページでは、構造化されたページツールを公開できます。実行ヘルパーを通じて検出し、呼び出してください。名前、説明、スキーマ、結果はページから取得されます。

const tools = await webmcp.list()
const result = await webmcp.call("search_catalog", { query: "adapter" })
return { tools, result }

検出はすべてのフレームを対象とします。同じ名前が複数のフレームにある場合は、報告されたフレームラベルを { frame } として正確に渡してください。Browser Control は呼び出しの直前にツールを再検出するため、古くなった登録はそのまま失敗します。

安全性

Browser Control は、ブラウザーを閉じる操作や Cookie/キャッシュの消去など、共有ブラウザーの状態を破壊する CDP コマンドをブロックします。これらのガードレールを回避してはいけません。

検査のみを行う場合は、読み取り専用セッションを使用してください。

browser-control session new inspect-prod --read-only
browser-control execute --session inspect-prod 'await page.goto("https://example.com"); return page.title()'

読み取り専用セッションでは Input.* が拒否されるため、Playwright 経由でクリックしたり入力したりすることはできません。page.evaluate では引き続き DOM を変更できます。読み取り専用によって防げるのは信頼できるユーザーによる誤操作であり、悪意のあるコードではありません。

破壊的な UI 操作には、次の2段階の 読み取り、確認、検証 フローを使用してください。

  1. 対象候補を読み取り、正確で安定した識別子または行テキストを返します。
  2. その対象を正確に指定して、ユーザーの承認を得ます。
  3. 承認済みの対象のみを再選択し、選択数を検証します。
  4. 確認ダイアログを読み取り、承認された操作と一致しない場合は例外を投げます。
  5. 確定した後、新しいページの読み取り、または独立した CLI/API 経由で検証します。

ユーザーが正確で安定した識別子をすでに承認している場合を除き、同じスクリプト内で破壊的な操作の候補を検出して確定してはいけません。ネイティブダイアログを全体的に自動承認してはいけません。想定したダイアログを待ち、その種類とメッセージを検証してから承認してください。

TypeScript クライアント

アプリケーションや CLI ツールでは、セッションページによって認証された同一オリジンの JSON リクエストを直接行うために、BrowserControlClient.origin() を使用できます(デフォルトヘッダーの指定や、セッションの期限切れまたはログインへのリダイレクト時のページ内 handoff による自動復旧も可能です)。

import { BrowserControlClient } from "@opencode-ai/browser-control"

const ubereats = BrowserControlClient.origin("https://www.ubereats.com", {
  session: "🥤 karma-cafe",
  startUrl: "/feed",
  headers: { "x-csrf-token": "x" },
  handoffOnAuthFailure: true,
})

const store = await ubereats.post("/_p/api/getStoreV1", {
  storeUuid: "78cb1602-9f58-57bb-ad08-f6b8f80bb788",
  diningMode: "DELIVERY",
})

スキーマのデコードや sensitive: true の Redacted レスポンスを扱う Effect ネイティブのアプリケーションでは、BrowserControlClient.Service を使用してください。

import { BrowserControlClient } from "@opencode-ai/browser-control"

const sensitive = yield* origin.json({
  path: "/api/session",
  method: "POST",
  body: {},
  response: SessionResponse,
  sensitive: true,
})
const session = BrowserControlClient.reveal(sensitive)

認証済みネットワークキャプチャ

ブラウザーでの認証やワークフローの特定が必要で、同じ処理を直接 HTTP で繰り返すほうが高速かつ確実な場合は、ネットワークキャプチャを使用します。定数とパラメーターを区別できるよう、入力を変えて各フローを少なくとも 2 回キャプチャしてください。

browser-control network start --session github --url /api/ \
  --resource-type fetch --resource-type xhr
browser-control execute --session github --file ./perform-flow.js
browser-control network status --session github
browser-control network stop --session github \
  --output ./github.har --secrets github

生成されるファイルでは、認証情報を含むヘッダー、Cookie、クエリフィールド、OAuth フラグメントフィールド、構造化された本文フィールドが ${BC_SECRET_1} のような安定した参照に置き換えられます。 --secrets github を指定すると、復元可能な値がモード 0600 のシークレットプロファイルに別途保存されます。プロファイルの値をソース、出力、診断情報、ジャーナルにコピーせず、認証情報を意図的に返したり、ログに記録したりしないでください。

編集済みのファイルをオフラインで確認し、観測されたフローごとに型付き関数を生成してから、無害なライブリクエストで各関数を検証します。値を公開せずに、生成されたクライアントを実行してください。

browser-control secrets status github
browser-control secrets run github -- ./github-cli repositories

生成された TypeScript アプリケーションでは、公開 SDK を通じて、このラッパーを内部で管理できます。

import { SecretProfile } from "@opencode-ai/browser-control"
import { Effect } from "effect"

const result = await Effect.runPromise(SecretProfile.run({
  name: "github",
  command: process.execPath,
  args: ["./github-cli.js", "repositories"],
}))

信頼されたワーカーは BC_SECRET_N 変数を受け取り、その出力は上限が設定されたうえで秘匿情報が編集されます。公開 SDK で公開されるのはプロファイルのメタデータのみで、プロファイルの生の値は公開されません。

通常はページの再読み込みで更新される認証情報を、次のように更新します。

browser-control secrets refresh github --session github --url /api/

更新にログインまたは人間によるプロンプトが必要な場合は、採用したタブで再認証し、同じプロファイルを使ってキャプチャを繰り返してください。MCP では、同等の network_* ツールと secrets_* ツールを利用できます。

完了条件:ファイルに認証情報の値ではなく参照が含まれていること、生成された操作が無害なライブチェックに合格すること、ソースや出力にシークレットの値が現れないこと。

録画

アタッチされたタブ、またはセッション所有のタブを録画するには、次を実行します。

browser-control recording start ./tmp/demo.mp4 --session github --mode cdp
browser-control recording status --session github
browser-control recording stop --session github

開始、停止、ステータスの各コマンドでは --json を指定できます。CDP の停止/ステータス結果には quality レシートが含まれます。ここには、出力の解像度/フレームレート、ソースと保持画像の数/レート、統合/ドロップされたフレーム数、screenshotFallback が記録されます。フォールバックは、コンポジターフレームが届かず、動画が停止時のスクリーンショット1枚を保持することを意味します。これを動きのある録画として提示しないでください。ソースカウンターは、視覚的に異なるフレームではなく、コンポジターイベントを数えます。静的なページでは、レートが低くても正常な場合があります。タブキャプチャと旧リレーでは、品質テレメトリーは省略され、測定値が捏造されることはありません。

明示的なフレームレートは、どちらのモードでも1~60の整数である必要があります。無効な値は暗黙に制限されず、エラーになります。開始結果には選択されたレートが表示されます。

--mode auto は、ユーザー所有のタブにはタブキャプチャを、リレー所有のタブには CDP を使用します。タブキャプチャでは音声を含められますが、CDP には ffmpeg が必要で、音声には対応していません。形式とカーソルのオプションについては、コマンドの --help を使用してください。

Playwright のマウス操作では、ページ上の Ghost Cursor が自動的に表示されます(distance-glide の動き+クリック時の衝撃波 tactile-bloom)。ユーザー向けの証跡動画や PR の手順紹介動画を録画する場合は、execute 内で ghostCursor ヘルパーを有効にして、重要な手順と検証済みの事後条件に注目を集めてください。

await showGhostCursor()
await ghostCursor.caption("Verify cluster & promote release", { step: "01", tone: "neutral" })
await ghostCursor.zoom("#release-card", { scale: 1.45 })
await page.locator("#promote-btn").click()
await ghostCursor.keys("⌘+⇧+P", "Promote Release")
await ghostCursor.resetZoom()
await ghostCursor.spotlight("#status-badge", { label: "Verified", detail: "200 OK", tone: "success" })

CDP 録画では、開始時の CSS ビューポートが保持され(固定の720pキャンバスではありません)、高品質なソースフレームが使用されます。デフォルトは60 fpsです。ファイルサイズを小さくするには、--frame-rate 30 を使用してください。実際の動きは、Chrome が新しいフレームを配信するかどうかにも左右されます。出力が60 fpsでも、60個の異なるフレームが得られるとは限りません。ビューポートを大きくすると、CPU、転送帯域幅、ストレージの負荷が増えます。録画を始める前にビューポートを設定し、録画中にビューポートやエミュレーションを変更しないでください。奇数の解像度は偶数に切り下げられます。

再現が難しい問題に備えて、CDP のフレームバッファーを循環させ、問題が発生した後で直近の履歴を保存できます。

browser-control flight-recorder start --session github --retention-ms 60000
browser-control flight-recorder status --session github
browser-control flight-recorder save-last ./tmp/failure.mp4 --session github --duration-ms 30000
browser-control flight-recorder cancel --session github

保存してもバッファリングは停止しません。レコーダーはメモリー使用量に上限があり、保持時間/フレーム数/バイト数/ドロップ数のカウンターを報告し、各クリップの隣に JSON レシートを書き込みます。また、同じタブでの通常の録画とは併用できません。CLI 録画とフライトレコーダーのライフサイクル操作は、MCP ツールとしても利用できます。

共有する前に、エンコード済みのフレームを元のサイズで確認してください。ビューポート全体がフレームいっぱいに表示され、小さな文字が読め、動きが同じ静止画の繰り返しになっていない必要があります。低解像度のキャプチャを切り抜いて拡大し、HD と呼ばないでください。古いリレーがインストールされており、ページが縮小されて余白のある隅に表示される場合は、その不具合を記録してレコーダーの更新を調整してください。ファイルの解像度を変えても修正にはなりません。

完了時は、レコーダーを停止し、ファイルが存在するかだけでなく、生成されたメディアを確認して、実際にテストしたビューポート、状態、操作経路を報告してください。

トラブルシューティング

  1. browser-control doctor を実行します。パッケージのメタデータ、CLI/リレーのビルド識別情報、拡張機能のプロトコル互換性、セッション、ターゲット、成果物を確認できます。
  2. status --json を使用して、正確なセッションとターゲットの所有者を確認します。
  3. コードを変更する前に、最小限の execute を使って一度再現します。

よくある原因:

  • session-page/context-read-timeout; operation=page.title; timeoutMs=5000: タイトルの読み取りが、割り当てられた時間内に完了しませんでした。実行コンテキストが利用できないか、ビジー状態の可能性がありますが、レンダラーがフリーズしたことを示すものではありません。ウォッチドッグは、進行中の読み取りをキャンセルしたり、ページの置き換えを開始したりしません。キャッシュされた page.url() の読み取りは引き続き動作する場合があります。ページが落ち着いてから、ページの読み取りを再試行してください。通常のロケーター不在によるタイムアウトには、この診断は適用されません。
  • connected:false:リレー経由のコマンドを実行し、拡張機能の起動またはアラームによるウェイクアップで再接続されるまで待ちます。スリープ中の拡張機能は30秒間隔のアラームでウェイクアップするため、コマンドは最大35秒間待機します。このループで復旧しない場合に限り、展開済みの拡張機能を再読み込みしてください。
  • 拡張機能のプロトコルに互換性がない:拡張機能または npm パッケージを更新してください。拡張機能とリレーのリリースバージョンを完全に一致させる必要はありません。
  • ブラウザー/プロファイルへの接続が競合している:アクティブなブラウザーはそのまま維持され、追加の接続は拒否されます。リレーごとにブラウザー/プロファイルを1つ使用してください。セッションの作成やタブのリセットを繰り返しても、ブラウザーは切り替わりません。
  • 古いリレーのビルド:doctor を確認してから、明示的な browser-control relay restart を調整してください。これは、正確に指定された管理対象インスタンスと、安全なシャットダウンプロトコル2を必要とします。レガシーリレーは、一度だけ連携して手動で停止する必要があります。フォアグラウンド/ソースリレーや新しいリレーを強制終了したり、ダウングレードしたりすることはありません。不一致がある場合も、MCP の観測ツールは引き続き利用できます。
  • 予期しない再起動:プライベートなエンドポイントごとの ~/.browser-control/relays/<port>/lifecycle.jsonl を確認し、リクエスト元/ビルド/インスタンスのメタデータを調べてください。開発用候補の準備や選択によって、デーモンが再起動されることはありません。Browser Control 自体を開発する際は、稼働中のチェックアウトへのリンクではなく、分離された runtime:prepare / runtime:select を使用してください。
  • Target not found:対象のタブを接続してから、一意の URL 部分文字列または明示的なインデックスを使って選択または取り込んでください。
  • すべてのターゲットが消えた:Chromium のデバッグバナーを閉じると、すべてのタブが切断されます。ツールバーから再接続してください。
  • リレーが再起動した:名前付きセッションは正確なターゲットを再取得しますが、JavaScript の state とスナップショットの参照はリセットされます。警告の後も作業を続けてください。
  • 拡張機能の更新後、リセット/削除では、ターゲットが再通知されるまで短時間待機する場合があります。完了した一覧に古いリレー所有のターゲットが存在しない場合、Browser Control は推測したタブを閉じることなく、使われなくなった ID を忘れます。
  • 実行コンテキストのエラーが繰り返し発生する:短い後続処理を1回実行し、Browser Control がページのヘルスチェックを行えるようにしてください。ページが応答している場合は維持されます。Browser Control は再接続して同じタブを1回再解決し、ページがまだ応答しなければ session-page/*-unresponsive の診断を返して失敗します。空白または不明な URL は維持されます。未保存のコンテンツが含まれている可能性があるためです。クラッシュしたページまたは chrome-error:// のリレー所有ページのみを閉じて再作成します。取り込み済みのユーザータブを置き換えることはありません。メインフレームのナビゲーションでは以前のクラッシュ診断がクリアされますが、子フレームのナビゲーションではクリアされません。ページが応答しない状態が続く場合(ボット対策されたサイトでは、人間には通常どおり表示されていても、自動化向けのメインワールドが停止することがあります)、context.newPage() で新しいタブを開くか、タブをユーザーに引き渡してください。
  • 引き渡しの後に "page execution context did not become available" となる:ユーザー側の処理は完了しています。古くなっているのは、タブに対する Browser Control の表示だけです。タブが失われたと決めつけず、短い後続の execute を実行してページを再確認してください。
  • ログインフィールドへの入力がタイムアウトする:まず確認し、セレクターまたはロケーターが解決されることを確認してから、fillInput を試してください。文字列セレクターは開いた Shadow Root を再帰的に検索します。閉じた Shadow Root は利用できません。
  • 検証済みのダイアログ背景によってクリックがブロックされる:まず正確な対象とブロッカーを確認してください。対象が承認済みのアクションで、Playwright のアクショナビリティ判定だけが古くなっている場合は、await locator.evaluate((element) => element.click()) を実行してから結果を確認してください。ディスパッチによるクリックをデフォルトにしないでください。
  • 非表示のチェックボックス/ラジオ入力:関連付けられた表示中のラベルまたはラッパーをクリックし、isChecked() で確認してください。非表示の入力を強制クリックしたり、任意の祖先要素を推測したりしないでください。
  • ダウンロード待機が失敗する:fetch と fs を使用してください。拡張機能経由の Playwright では、ネイティブのダウンロード成果物を保持できません。
  • 無限にアニメーションする対象へのホバー:Playwright が要素を安定状態と判定できない場合があります。現在の getBoundingClientRect() を読み取り、座標入力が適切な場合は page.mouse.move() を使用してください。
  • Chrome ウェブストアの開発者ダッシュボードなどの Chromium 保護ページでは、chrome.debugger が切断されることがあります。この境界を越えて再試行や回避を行わず、手動操作のためにページを開いてください。 リレーをより詳しく診断するには、BROWSER_CONTROL_DEBUG=1 を設定して再起動してください。デバッグトレースには、式、引数、結果、ヘッダー、Cookie、フォームの値を決して含めないでください。

Browser Control が失敗したり、応答しなくなったり、ページ/セッションを置き換えたり、予期しない動作をしたりした場合は、Browser Control のバージョン、安全なセッション/ページのコンテキスト、正確なエラー、再現手順、期待される動作と実際の動作、試した復旧方法を記載して、browser-control プロジェクトの ToDo を作成または更新してください。認証情報、フォームの値、非公開のアカウント情報は決して含めないでください。

コメント