Codex AppのCDP capabilityでChatGPT WebのDOM・通信・APIレスポンスを観測してみた
どうも!オペ部の西村祐二です!
Codex の Browser Developer mode では、ブラウザ操作用の高レベル API に加えて、Chrome DevTools Protocol(CDP)を直接呼び出せる cdp capability を使えます。
この Developer mode / full CDP access は、手元では Codex App の openai-bundled browser / chrome 26.609.71450 で確認しました。
事前にCDPを有効化しておく必要があります。

今回は Codex App からログイン済みの ChatGPT を開き、実際に短いメッセージを投稿しながら、CDP でどこまで観測できるのかを試しました。
この記事で扱うのは、次の7項目です。
- DOM 変化の観測
- ネットワーク通信の監視
- API レスポンス本文の取得
- スクリーンショット取得
- PDF 生成
- コンソールログ取得
- パフォーマンス指標の取得
結論から言うと、クリック操作も、コンソールログも、ネットワーク通信も、Agent側から操作・観測できました。そのうえで実用上は、クリックや入力は tab.playwright などの高レベル API に任せ、通信・レスポンス・PDF・Performance metrics の観測を CDP に任せる分担が扱いやすそうでした。
何を試したかったか
CDP は Chrome DevTools が内部で使っているプロトコルです。Network、Runtime、Page、Performance などのドメインにコマンドを送り、ブラウザで起きていることを外側から取得できます。
今回見たかったのは、「Codex App から認証済み Web アプリを操作しながら、DevTools の Network タブや Performance タブで見るような情報を同じ流れで取れるか」です。
対象にした ChatGPT Web は、メッセージ送信後にストリーミング応答が返り、画面も動的に更新されます。DOM、Network、API レスポンス、Performance をまとめて見る題材としてちょうどよさそうでした。
環境
- Codex CLI 0.140.0
- Codex App Browser / Chrome Plugin(openai-bundled browser / chrome 26.609.71450)
- Browser Developer mode / full CDP access 有効
- Google Chrome のログイン済み ChatGPT セッション
- macOS 15.7.4
- 確認日: 2026-06-16
まずCodexに観測したい内容を依頼する
今回は Codex App のチャットで、ChatGPT Web を対象に CDP 観測をしてほしいと依頼しました。
依頼した内容は、ざっくり次のようなものです。
ChatGPT の Web サイトを対象に、CDP(Chrome DevTools Protocol)を使ってブラウザ上の挙動を観測して。
実際にチャットへメッセージを投稿し、その際に発生する以下の内容を確認します。
- DOM 操作
- ネットワーク通信の監視
- API レスポンスの直接取得
- スクリーンショット取得
- PDF 生成
- コンソールログの取得
- パフォーマンス計測
CDP は取れる情報が多いので、先に見る範囲を決めておくほうがログを整理しやすくなります。
全体の流れ
実際の検証は、次の流れで進めてくれました。特に詰まることなく操作してくれました。
- Chrome Plugin 経由で ChatGPT Web を開く
- CDP の
Page、DOM、Runtime、Network、Performanceなどを有効化する - 送信前の CDP event cursor を取得する
- ChatGPT に短いテスト投稿を送る
- cursor 以降の Network event と Runtime event を読む
- 必要な requestId から API レスポンス本文を取得する
- スクリーンショット、PDF、Performance metrics を保存する
最小化すると、CDP 側の準備は次のような形です。
const cdp = await tab.capabilities.get("cdp");
for (const [method, params] of [
["Page.enable", {}],
["DOM.enable", {}],
["Runtime.enable", {}],
["Log.enable", {}],
["Network.enable", {}],
["Performance.enable", {}],
]) {
await cdp.send(method, params);
}
一方で、ChatGPT の入力欄へテキストを入れて送信する部分は、CDP ではなく tab.playwright を使っていました。
const prompt = "CDP観測テストです。短く『観測OK』とだけ返してください。";
const input = tab.playwright.getByRole("textbox", {
name: "Chat with ChatGPT",
});
await input.fill(prompt);
const sendButton = tab.playwright.getByRole("button", {
name: "Send prompt",
});
await sendButton.click();
ページ操作まで CDP に寄せるより、入力やクリックは高レベル API、観測は CDP と分けたほうが読みやすく、失敗時の切り分けもしやすいです。
画面上の結果
ChatGPT には次のテスト投稿を送信しました。
CDP観測テストです。短く『観測OK』とだけ返してください。
画面上の応答は 観測OK でした。下記のスクリーンショットもAgentが取得してくれました。

ここから先は、この1回の投稿で裏側に何が起きていたかをAgent側で取得し、内容をまとめてもらいました。それを見ていきます。
DOMでは何が見えたか
DOM 変化は、Runtime.evaluate でページ内に MutationObserver を置いて観測しました。対象は main 要素配下です。
結果は次の通りです。
| 項目 | 結果 |
|---|---|
| mutation 合計 | 37 |
| childList | 33 |
| characterData | 4 |
| 追加ノード | 19 |
| 削除ノード | 14 |
Thinking 表示が出てから回答本文が表示されるまで、childList と characterData の変化として追えました。
さらに DOMSnapshot.captureSnapshot で、その時点の DOM 構造も取得しました。
| 項目 | 結果 |
|---|---|
| document | 3 |
| node | 1,563 |
| layout node | 1,085 |
| string table | 1,381 |
MutationObserver は「何が変わったか」を追う用途、DOMSnapshot は「その時点でどういう構造だったか」を残す用途に向いていそうです。
Networkでは何が見えたか
Network event は、メッセージ送信前に cursor を取り、送信後にその cursor 以降だけを読みました。
const before = await cdp.readEvents({
limit: 1,
timeoutMs: 1000,
});
// このあとメッセージを送信する
const observed = await cdp.readEvents({
afterSequence: before.cursor,
limit: 1000,
methods: [
"Network.requestWillBeSent",
"Network.responseReceived",
"Network.loadingFinished",
"Network.loadingFailed",
],
timeoutMs: 5000,
});
送信前 cursor 以降では、CDP event 381 件、Network request 163 件を観測しました。
主に見えたエンドポイントは以下です。
| メソッド | パス | 観測した内容 |
|---|---|---|
| POST | /backend-api/f/conversation |
メッセージ送信と回答ストリーム |
| POST | /backend-api/f/conversation/prepare |
会話送信前の準備 |
| GET | /backend-api/conversation/{id}/stream_status |
ストリーム状態 |
| GET | /backend-api/conversation/{id}/textdocs |
textdocs の取得 |
| GET / POST | /backend-api/sentinel/* |
sentinel 関連 |
| POST | /ces/v1/* |
telemetry / event 系 |
目的の通信は /backend-api/f/conversation ですが、実際の Web アプリでは、関連する準備処理、状態確認、telemetry なども同時に流れます。CDP で読むときは、URL、method、mimeType で絞る前提にしておくと扱いやすいです。
APIレスポンス本文では何が見えたか
Network.responseReceived で requestId を控えておくと、Network.getResponseBody でレスポンス本文を取得できます。
今回の主役は /backend-api/f/conversation の text/event-stream でした。
const responseBody = await cdp.send("Network.getResponseBody", {
requestId: conversationRequestId,
});
const lines = responseBody.body
.split("\n")
.filter((line) => line.startsWith("data: "))
.map((line) => line.slice("data: ".length));
取得できた SSE 本文は 24,750 文字でした。data: event は 27 件で、最後に [DONE] も確認できました。
画面上では 観測OK と表示されましたが、API ストリーム上でも assistant の回答 delta として同じ文字列を確認できました。
assistant delta: $.v[0].v = "観測OK"
補助的な API も確認できました。
| API | 結果 |
|---|---|
/backend-api/conversation/{id}/stream_status |
{"status":"IS_STREAMING"} |
/backend-api/conversation/{id}/textdocs |
[] |
/backend-api/f/conversation/prepare |
{"status":"ok", ...} |
conversation/prepare のレスポンスには短期トークンが含まれていたため、値は保存せず、status: ok だったことだけを記録しました。
スクリーンショットとPDFを保存する
スクリーンショットは、Codex App 側の高レベル API である tab.screenshot() を使い取得していました。
const screenshot = await tab.screenshot({
fullPage: false,
clip: {
x: 270,
y: 0,
width: 1260,
height: 768,
},
});
最初は画面全体を保存しましたが、サイドバーの個人領域も含まれました。公開記事に使う画像は、会話本文側だけをクロップしています。
PDF は CDP の Page.printToPDF で生成しました。
const pdf = await cdp.send("Page.printToPDF", {
printBackground: true,
preferCSSPageSize: true,
});
今回生成した PDF は 2 ページ、約 49 KB でした。スクリーンショットは目視確認用、PDF は検証時点のページ状態を残す成果物として使いやすそうです。

ConsoleとPerformanceも取れる
コンソールログは Runtime.consoleAPICalled を購読して取得しました。今回はログ取得の確認として、CDP 経由で console.info を1件発火し、その event を読みました。
結果として、Runtime.consoleAPICalled の info event を1件取得できました。今回は自然発生したアプリエラーの調査ではなく、コンソール event を拾えるかの確認です。
Performance metrics は、メッセージ送信前後で Performance.getMetrics を呼び、差分を見ました。
| 指標 | 差分 |
|---|---|
LayoutCount |
+234 |
RecalcStyleCount |
+2,675 |
LayoutDuration |
+0.064289 |
RecalcStyleDuration |
+0.835087 |
ScriptDuration |
+0.837414 |
TaskDuration |
+4.095959 |
Nodes |
+2,087 |
この数値は1回の実行結果なので、厳密なベンチマークではありません。ChatGPT の新規チャットから会話 URL への遷移、初期化後のフレーム整理、応答描画が混ざった値です。
一方で、同じ操作を複数回実行して比較する、変更前後で傾向を見る、といった用途には使えそうです。
使い分けの整理
今回試した範囲では、すべてを CDP に寄せるより、役割を分けるほうが実用的でした。
| やりたいこと | 使いやすかった手段 |
|---|---|
| クリック、入力、画面上の要素操作 | tab.playwright |
| DOM 変化の観測 | Runtime.evaluate + MutationObserver |
| 通信の監視 | Network.* event |
| API レスポンス本文の取得 | Network.getResponseBody |
| PDF 生成 | Page.printToPDF |
| パフォーマンス指標 | Performance.getMetrics |
CDP は「ブラウザを操作するための唯一の手段」というより、DevTools 相当の観測を Codex App の作業ログに組み込むための道具として使うのが合いそうです。
試してみた感想
これまで Web アプリの裏側を確認するときは、ブラウザの DevTools を手で開いて Network タブや Console タブを目視していました。Codex App の CDP capability を使うと、その確認を Agent 側で確認してもらえるため、調査などが楽になりました。また手順として書き出せるので、同じ観測を後からたどり直しやすくなりました。
特に良かったのは、画面に見えている結果と、API ストリーム上の結果を、ツールを切り替えずに一続きで突き合わせられた点です。今回は画面上に 観測OK と表示され、SSE の assistant delta でも同じ 観測OK を確認できました。表示と通信が一致していることを、ひとつの流れの中で確認できました。
一方で、認証済みセッションを扱うと、画面に出ていない機微な値まで取得できてしまいますので注意が必要です。
まとめ
Codex App の Chrome Plugin 経由で ChatGPT Web を開き、CDP capability を使って以下を確認しました。
- DOM 変化を MutationObserver と DOMSnapshot で観測できた
- Network event から
/backend-api/f/conversationの SSE 通信を追えた Network.getResponseBodyで回答 delta の観測OKを直接確認できたPage.printToPDFで会話ページを PDF 化できたRuntime.consoleAPICalledとPerformance.getMetricsも取得できた
Web アプリの「画面に出ている結果」と「裏側で起きている通信」を同じ検証ログを確認してほしいとき、Codex App の CDP capability は実用的に使えそうです。
誰かの参考になれば幸いです。
参考リンク:
- Codex GitHub リポジトリ
- Codex app - In-app browser: Developer mode
- Codex app settings - Browser
- Codex managed configuration - browser_use_full_cdp_access
- OpenAI Developers announcement mirror: Developer mode for browser use
- Chrome DevTools Protocol ドキュメント
- Chrome DevTools Protocol - Network domain
- Chrome DevTools Protocol - Page domain
- Chrome DevTools Protocol - Performance domain





