2026年7月11日、私は個人用Macのブラウザ自動化を見直していました。最初に考えたのは、接続に失敗してもブラウザを再起動して処理を続ける方法です。止まらないほうが便利に見えました。
しかし、ログイン済みブラウザには、読んでいるページや入力途中の画面が残っています。接続を復旧するための再起動が、ユーザーの作業状態を壊すかもしれません。処理を続ける便利さと、既存の状態を守る安全性のどちらを優先するかで迷いました。
転換点は、復旧手順を増やすより「何もしないこと」をテストできると気づいたことです。強制再起動はFalseで止める。前面タブではなく、フォーカスしない専用pageを作る。終了時は自動化が所有するpageだけを閉じる。この3点を、実ブラウザを開かないテストで固定しました。
ここから使ったのが、PlaywrightとChromium系ブラウザのCDP接続です。クリックが成功することと、安全に運用できることは別です。2026年7月11日の見直しで、先に固定すべきなのは次の4点だと判断しました。
- 認証・アカウントが想定と違えば止める
- ユーザーが開いていたタブを操作対象にしない
- 外部へ影響する操作は読み取り・準備と分離する
- 接続失敗時にブラウザを勝手に再起動しない
この記事では、私が実際に使っている停止線、ローカルテストの結果、Playwrightの仕様との境界を、再利用できる形でまとめます。CAPTCHAや2段階認証を自動突破する方法は扱いません。
Playwrightの待機機能は「業務上押してよい」を判断しない
Playwrightのlocator.click()は、対象が1件に絞れているか、表示されているか、位置が安定しているか、クリックを受け取れるか、有効かを確認してから操作します。これは表示待ちのtime.sleep()を減らすうえで有効です。
ただし、この確認が保証するのは「要素を操作できる状態」です。「そのアカウントで投稿してよい」「この注文を確定してよい」「このタブを遷移してよい」といった業務判断までは行いません。
私は条件を次の2層に分けました。
| 層 | 確認すること | 失敗時 |
|---|---|---|
| Playwrightの操作条件 | 表示、安定、クリック可能、入力可能 | タイムアウトとして記録 |
| 運用の停止条件 | ドメイン、アカウント、対象ID、公開範囲、外部影響 | 再試行せず停止 |
force: trueでクリック条件を弱めても、運用の停止条件は解決しません。通常のクリックが通らない時に、理由を調べずforceへ切り替える運用にはしないほうが安全です。
私がhard stopにした8条件
次の条件は、同じ操作を繰り返しても安全に解消できません。私は「人が確認するまで状態を変えない」hard stopとして扱っています。
- ログイン画面へ戻った
- 2段階認証、Touch ID、CAPTCHA、Cloudflareの確認が出た
- 期待していた個人アカウントやチャンネルと一致しない
- 会社用ドメインや不明なアカウントが表示された
- 投稿、送信、購入、契約、削除、共有範囲変更の直前になった
- 対象の記事ID、動画ID、書籍IDが予定と一致しない
- APIキー、OAuthトークン、Cookieなどがログや画面へ露出しそうになった
- ユーザーが使っていた前面タブが想定外に遷移した
一方、要素の読み込み遅延や一時的な通信失敗はretryable errorです。ただし再試行回数には上限を置きます。私の定時処理では、同じ方法の再試行は原則1回です。2回目も失敗したら、エラーを保存して次回のcheckpointへ残します。
この区別がないと、認証切れを通信遅延と誤認して何度もログインボタンを押す、といった危険な動きになります。
既存タブを守るために「所有権」を付ける
CDPで既存ブラウザへ接続すると、既存のbrowser contextやpageが見えます。そこでcontext.pages[0]をそのまま使うと、ユーザーが読んでいるページや入力途中の画面を操作する可能性があります。
私の実装では、接続直後に既存pageを記録し、自動化が作ったpageへwindow.name = "codex-automation"という印を付けます。終了時に閉じるのは、その印が付いたpageだけです。
考え方を簡略化すると次のようになります。
AUTOMATION_PAGE = "codex-automation"
existing_pages = {id(page) for page in context.pages}
page = context.new_page()
page.evaluate(f"window.name = {AUTOMATION_PAGE!r}")
try:
page.goto(target_url, wait_until="domcontentloaded")
# 読み取り、検証、下書き作成を行う
finally:
for candidate in context.pages:
name = candidate.evaluate("window.name")
if name == AUTOMATION_PAGE and id(candidate) not in existing_pages:
candidate.close()
実運用では、背景targetを作れるCDP経路を使い、ユーザーのactive tabを切り替えないようにしています。背景targetを作れなかった時は、既存pageへフォールバックせず停止します。
ここで重要なのは、「最後に元のタブへ戻す」設計にしないことです。元に戻す操作自体が新しい誤操作になるため、自動化が所有するpage以外を最初から変更しません。
CDP接続では便利さより縮退動作を決める
Playwright公式では、connectOverCDP()はChromium系ブラウザの既存インスタンスへ接続する方法です。同時に、Playwright独自プロトコルのconnect()より接続の忠実度が低いとも説明されています。
そのため私は、CDPが使えない時にログイン済みブラウザを終了・強制再起動して復旧する代替を入れていません。既存ブラウザを触らずに済む、自動化専用プロファイルの起動がrunbookで許可されている処理だけを分けています。
実装上の分岐は次の通りです。
| 状態 | 動作 |
|---|---|
| 指定ポートで期待するブラウザが応答 | 専用pageを作って続行 |
| 指定ポートを別プロセスが使用 | 接続先違いとして停止 |
| CDPが応答せず、既存ブラウザの状態が不明 | 終了・強制再起動しない |
| 専用プロファイルの起動がrunbookで許可済み | 既存ブラウザを終了せず、前面を出さずに起動 |
| CDP接続後に例外 | 自動化pageだけ閉じ、既存pageは残す |
ログイン済みブラウザを自動終了すると、未送信フォームや調査中のタブまで失われます。接続を復旧するためにユーザーの作業状態を壊すのは、本末転倒です。
3つのローカルテストで確認したこと
2026年7月13日、実ブラウザを開かずに境界だけを確認するユニットテストを再実行しました。結果は3件すべて成功、実行時間は0.000秒でした。
確認したのは次の3点です。
- CDP再接続失敗時の再起動関数が必ず
Falseを返す - 終了処理が
codex-automationの印を持つpageだけを閉じる - 背景targetを
focus: falseで作り、自動化所有の印を付ける
2件目では、ユーザーpage、別ツールのpage、自動化pageの3つを用意しました。閉じられたのは自動化pageの1つだけでした。3件目では、実ブラウザの代わりにfake CDP sessionを使い、Target.createTargetへbackground: trueとfocus: falseが渡ることを確認しました。
これは実サイトでのE2E試験ではありません。ログイン、投稿、認証画面までは触っていないため、「すべてのWebサービスで安全」と証明するものでもありません。今回証明したのは、失敗時に既存タブを閉じないというコード上の境界です。外部サービスごとのアカウント確認は別のテストが必要です。
読み取りから外部変更までを5段階に分ける
私は1本のスクリプトで取得から投稿まで直行させず、次の5段階へ分けています。
1. Inspect:対象を読む
URL、対象ID、現在の状態、アカウント名を取得します。この段階では書き込みません。期待値と違えば停止します。
2. Isolate:専用pageか独立contextを作る
ユーザーの既存pageを再利用しません。テスト用アカウントで済む処理なら、独立browser contextを優先します。
3. Assert:操作直前にもう一度照合する
画面を開いた時点だけでなく、クリック直前にもURL、対象ID、公開範囲を確認します。SPAでは画面遷移なしに対象が変わることがあるためです。
4. Stage:ローカル成果物を完成させる
投稿本文、画像、メタデータ、差分、品質結果をローカルへ保存します。外部変更できない日でも、ここまでは成果として残せます。
5. Commit:許可された変更を1回だけ行う
送信や公開がrunbookで許可され、対象・アカウント・日次上限がすべて一致した時だけ実行します。成功後はURLや外部IDを読み戻し、ボタンを押した事実ではなく反映結果を保存します。
この分割にすると、認証切れで止まっても原稿や画像は失われません。次回はStageの成果物から再開できます。
認証状態ファイルを普通の設定ファイルとして扱わない
PlaywrightのstorageStateはログイン状態を再利用できて便利です。ただし公式ドキュメントも、保存ファイルにCookieやheaderが入り、アカウントのなりすましに使われ得ると警告しています。
最低限、次を固定します。
- 認証状態の保存先をGit管理から除外する
- 公開用ログやスクリーンショットへ内容を出さない
- 使うドメインとアカウントを分ける
- 有効期限切れを接続エラーとして連打しない
- 会社用と個人用の認証状態を同じフォルダに置かない
storageStateを使わない既存ブラウザ接続でも、Cookieへアクセスできる権限を持つ点は同じです。接続できること自体を許可の証拠にしません。
Traceとスクリーンショットは失敗時だけ残す
Playwright Testにはtrace: "retain-on-failure"やtrace: "on-first-retry"があります。毎回のtraceを永久保存するより、失敗したrunへ絞ると容量と確認時間を抑えられます。
import { defineConfig } from '@playwright/test';
export default defineConfig({
retries: 1,
use: {
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'on-first-retry',
},
});
Trace Viewerでは操作の時系列、DOM snapshot、network requestなどを確認できます。つまりtrace自体も機密情報を含む可能性があります。公開リポジトリへ置かず、保存期間と閲覧範囲を決めます。
ログへ残す項目は多くなくて構いません。
{
"run_id": "example-run",
"stage": "inspect",
"target_id": "expected-target",
"expected_account": "personal",
"external_action_attempted": false,
"stop_reason": "authentication_required",
"next_action": "resume_from_checkpoint"
}
パスワード、Cookie、OAuth token、入力した個人情報は記録しません。
そのまま使える公開前チェック
自動化コードを書く前に、次の質問へ答えられるか確認します。
- 対象URLと対象IDをコードで照合しているか
- 期待アカウントを画面またはAPIで確認できるか
- 既存pageと自動化pageを区別できるか
- 自動化pageだけを閉じられるか
- 2段階認証やCAPTCHAをhard stopにしているか
forceクリックを通常の復旧手段にしていないか- 書き込み直前に対象を再照合しているか
- 日次上限や二重実行防止IDがあるか
- 成功後に外部IDや公開URLを読み戻しているか
- 利用制限中もcheckpointが残るか
1つでも「確認できない」なら、まず読み取りとローカル保存までに範囲を戻します。
よくある質問
CDPへ接続できたら、開いているタブを使ってよいですか?
接続できることと、そのpageを操作してよいことは別です。既存pageは読む対象にもせず、自動化が作ったpageか独立contextを使うほうが事故を減らせます。
CAPTCHAが出た時だけ人に解いてもらい、そのまま続行できますか?
人が認証を完了したあとも、アカウント、対象ID、公開範囲を最初から再確認します。認証前のpage状態を信用して、送信直前から続けないようにします。
タイムアウトを長くすれば安定しますか?
読み込みが遅いだけなら改善する場合があります。しかし、対象違い、ログイン切れ、権限不足は待っても解決しません。まずhard stopとretryable errorを分類します。
完全自動化を諦める必要がありますか?
必要ありません。読み取り、準備、品質検査、checkpoint保存は全自動にし、外部変更だけを厳しい条件付きにできます。停止線があるほど、止まらない部分を広げやすくなります。
まとめ
Playwright運用で最初に書くべきなのは、成功手順より停止条件です。
- Playwrightのauto-waitと業務上の許可を分ける
- 認証・アカウント・対象不一致は再試行しない
- 既存pageに触らず、自動化pageへ所有権を付ける
- CDP失敗時にブラウザを勝手に再起動しない
- 読み取り、分離、照合、準備、外部変更の5段階にする
- 失敗証拠とcheckpointを残し、次回は続きから再開する
「押せるから押す」ではなく、「止める条件に当てはまらないと確認できた時だけ押す」。この順番に変えると、ブラウザ自動化を日々の運用へ載せやすくなります。
参照した公式資料
- Playwright Auto-waiting
- Playwright Authentication
- Playwright BrowserType.connectOverCDP
- Playwright Testのtrace・screenshot・video設定
ブラウザ自動化の境界を続けて読む
- WordPress更新で403になり、人へ戻して復旧した記録では、WAFを突破せず同じURL更新まで戻した実例を扱っています。
- 4日間の実測から、自動いいねを止める5つのルールを決めた記録では、ブラウザの所有権とは別に、外部へ影響する自動化をどこで止めるかを扱っています。