CDPのアビス — 全4編
現在地:地上編
Executive Summary — 地上への帰還報告
- 地上で観測したのは、Playwrightの
TargetClosedErrorとDownload.save_as()の失敗だった。- しかし、実際にはその前にMicrosoft Edge自体がネイティブクラッシュしていた。画面に出たエラーは原因ではなく、深層で起きた事故が上層へ伝わった結果だった。
- 新しいプロファイルで5件を続けてダウンロードするだけなら5/5成功した。異変は、同じプロファイルでEdgeを終了・再起動した後に現れた。
- 本連載では、地上のアプリコードからPlaywright、CDP、通信経路、Edge内部へと一層ずつ降り、最後に安全な帰還路となる回避策を探す。
この層の呪い:深層の原因が、地上へ戻るまでに一般的な
TargetClosedErrorへ圧縮される。
この層の祝福:エラーの発生順を逆にたどれば、降りるべき層が見えてくる。
深層碑文:「地上で見えるエラーは、深層で起きた事故が落とした影にすぎない。」
深度0:地上で鳴った警報
調査の出発点は、ログに残った2行だった。
browser_disconnected reason=unexpected
Download.save_as: Target page, context or browser has been closed
日本語にすると、次のような意味になる。
- ブラウザとの接続が予期せず切れた
- 保存しようとしたが、操作対象はすでに閉じていた
このログを地上から眺めると、save_as() の使い方かPlaywrightのライフサイクル管理が怪しく見える。
だが、警報器が鳴った場所と、事故が起きた場所は同じとは限らない。
地上 業務アプリに見えた TargetClosedError
│
浅層 PlaywrightのPage / Context / Download
│
中層 プロファイル再利用とブラウザ再起動
│
深層 CDPとpipe / portの通信経路
│
最深部 Edgeプロセスとmsedge.dllのネイティブ処理
今回のエラーは、最深部で起きた事故が上の層へ伝わる途中で、情報を失いながら TargetClosedError へ姿を変えたものだった。
このアビスの登場人物
専門用語を、探索装備として先に整理しておく。
| 用語 | 役割 | 探索上のたとえ |
|---|---|---|
| Playwright | ブラウザをプログラムから操作する道具 | 地上から指示を出す操縦者 |
| Microsoft Edge | ページを開き、実際にダウンロードするブラウザ | 深層で動く大型機械 |
| CDP | Chromium系ブラウザへ命令を送る共通プロトコル | 層をまたいで届く共通言語 |
| プロファイル | Cookie、設定、ログイン状態などを保存する場所 | 探索のたびに持ち帰る荷物 |
| pipe / port | CDPメッセージを運ぶ二つの経路 | 深層へ続く別々の縦穴 |
| Crashpadダンプ | 異常終了時に残される記録 | 深層から回収した記録石 |
Playwrightの launch_persistent_context() は、指定したプロファイルを使ってブラウザを起動する。Cookieやローカルストレージを維持できるため、ログイン状態を保つ自動処理で便利なAPIである。
一方、便利な一つのAPIの下には、複数の層が隠れている。地上からは一行に見えても、内部ではPlaywrightがEdgeを起動し、CDPで命令し、EdgeがOS上でファイルを書き込む。
上昇負荷:深層の情報は地上へ戻るほど失われる
ここでいう「上昇負荷」は、深い層の異常が上位APIへ返る過程で、原因情報が一般的なエラーへ圧縮されることを指す。
最深部: msedge.dllで無効なメモリ読み取り
↓
深層: Edgeプロセスが終了し、CDP接続が切断
↓
浅層: Browser / Context / Pageが閉じた状態になる
↓
地上: Download.save_as() がTargetClosedErrorを返す
地上のエラーは嘘ではない。確かに対象は閉じている。
ただし、「なぜ閉じたのか」という最も重要な情報は、そこには残っていない。地上のログだけを直しても、深層の事故は止められない。
最初に見つけた不自然な地形
当初、プロファイルによって結果が違って見えた。
| 条件 | 結果 |
|---|---|
| 以前から使っていたプロファイル | 同じダウンロード処理で繰り返し異常終了 |
| 新しく作ったプロファイルで、1回のEdge起動中に5件ダウンロード | 5/5成功 |
ここだけなら、「古いプロファイルが壊れていた」で話を終えられる。
しかし、新しいプロファイルを持ち帰り、Edgeを終了し、同じプロファイルで再び起動すると状況が変わった。
新規プロファイルの初回起動 → 5件成功
同じプロファイルを再利用した起動 → 2回目または3回目にクラッシュ
異変を起こしたのは5件という数ではなかった。「一度使ったプロファイルを持って、もう一度潜ること」が重要な条件だった。
地上で立てた仮説
この時点で、複数の容疑者がいる。
- 古いプロファイルだけが壊れているのではないか
- 画面を表示しないheadless実行が不安定なのではないか
-
Download.save_as()がEdgeを閉じているのではないか - Playwrightの起動引数やCDP命令順に問題があるのではないか
- Edge自体がクラッシュしているのではないか
地上から推測を重ねても答えは出ない。次に必要なのは、装備を一つずつ外し、条件を一つずつ変えることだった。
検証環境
本連載の回数と結果は、2026年9月3日に次の環境で観測した値である。
| 項目 | バージョン |
|---|---|
| OS | Windows 11 Home x64、build 26200 |
| Microsoft Edge Stable | 152.0.4191.53、x64 |
| Playwright for Python | 1.62.0 |
| Playwright同梱Chromium | 151.0.7922.34 |
| Python / Node.js | 3.12.14 / 24.18.1 |
すべてのEdge環境で発生すると結論したわけではない。原因コンポーネントと回帰範囲も、まだ確定していない。
次の層へ
次編では浅層から中層へ降りる。
古いプロファイル、headless、save_as() という分かりやすい容疑者を、一人ずつ実験で帰していく。そこで残るのは、「同じプロファイルを持ってEdgeを再起動する」という地形そのものだった。
深度案内
- 地上編:
TargetClosedErrorは最深部の事故を説明しない(本記事) - 浅層編:新品のプロファイルも、再利用すると様子が変わる
- 深界編:Playwrightを置いてraw CDPでpipe / port分岐へ降りる
- 最深部編:Crashpadの記録石を読み、帰還路を選ぶ