UIの自動テストや運用の自動巡回スクリプトが、ある日突然タイムアウトで落ちる。原因を追うと、要素のidやclassが変わっていただけだった、という経験を持つ運用担当者は少なくないはずです。この記事では、ブラウザ操作の自動化が抱える「セレクタの陳腐化」という障害パターンを、監視設計と根本原因分析の観点から整理します。
対象読者は、Webアプリの死活監視やE2Eテスト(本番相当の画面操作を通しで検証するテスト)を自動化しているSRE・QA・インフラ担当者です。フロントエンドのビルド構成やUI設計そのものには踏み込みません。あくまで「落ちたときにどう気づき、どう切り分けるか」という運用目線での整理です。
何が起きているのか:ビルドハッシュとセレクタの寿命
Playwright(Microsoftが開発するブラウザ自動操作ライブラリ)やSelenium(同様の老舗自動操作ツール)を使った監視スクリプトでは、ボタンや入力欄を指す「セレクタ」にidやclass名を使うのが一般的です。
問題は、CSS Modulesやstyled-componentsのようなフロントエンド構成では、デプロイのたびにclass名やidの末尾にハッシュ値が付与される点です。たとえば item-r8k7gx というclassが、次のデプロイで item-cr2ajb に変わる、といった具合です。
スクリプトが一度取得したidをキャッシュして使い回していると、次回実行時には画面上に見た目は同じ要素があるのに、指定したidが存在しないという状態になります。結果として得られるエラーは、たいてい情報量の少ないタイムアウトだけです。
locator.click: Timeout 3000ms exceeded.このエラーメッセージだけを見ると、ネットワーク遅延なのか、要素が本当に消えたのか、単なるid変更なのか区別がつきません。ここが障害対応を難しくしている根本部分です。
段階的に見る:なぜタイムアウトの原因特定が難しいか
まず、監視スクリプトの失敗は大きく3種類に分類できます。
- 本当にサービスが落ちている(サーバーエラー・応答なし)
- UIの要素は存在するが、参照方法(セレクタ)が古くなっている
- ネットワークやブラウザ側の一時的な不調
この3つを区別できないまま「監視スクリプトが落ちた=サービス障害」としてアラートを発報すると、オンコール対応者は毎回同じ空振りの調査をする羽目になります。いわゆる「アラート疲れ」の温床です。
次に、なぜセレクタの陳腐化が起きやすいかを整理します。id/classへの依存は、DOM(ブラウザが画面を表現する木構造)の実装詳細に監視スクリプトが直接結びついている状態です。実装詳細はフロントエンドのリリースサイクルで頻繁に変わりますが、監視側はそれを検知する術を持ちません。
つまり、監視対象システムの「内部実装」と「監視スクリプトの参照方法」が密結合している設計そのものが、障害の根本原因といえます。
関連技術との比較:role/textロケータとエージェント型操作
この問題への対策は大きく2段階に分けて考えられます。
一つ目は、Playwright自体が提供する getByRole(画面上の役割で要素を特定する機能)や getByText(表示テキストで特定する機能)です。たとえば「Submit」というボタンを page.getByRole("button", { name: "Submit" }) で指定すれば、classやidのハッシュがどう変わっても影響を受けません。これは既存のPlaywright資産に対して、実装コストの低い改善策です。
もう一つは、より根本的にアプローチを変える方法です。BrowserAct(AIエージェント向けに設計されたブラウザ操作プラットフォーム)は、セレクタという概念自体を使わない設計を採用しています。動作の流れは次の3ステップです。
- state コマンドで、今その瞬間の画面上にある操作可能な要素を、番号付きリストとして取得する
- 取得した番号(インデックス)を指定して click <番号> のように操作する
- 操作後は再度 state を呼び直し、画面の変化を前提に状態を読み直す
このインデックスは、その1回のスナップショットにのみ有効な一時的な参照です。次に画面が変化すれば、古いインデックスは自動的に無効化されます。id/classのようにハッシュ変化で壊れる「永続的な参照」を、そもそも持たない設計といえます。
| 方式 | 依存する情報 | ハッシュ変更への耐性 | 導入コスト |
|---|---|---|---|
| id/classセレクタ | DOM実装詳細 | 弱い(毎回壊れうる) | 低い(既存資産そのまま) |
| role/textロケータ | 表示上の役割・文言 | 強い | 低〜中(書き換えが必要) |
| 状態スナップショット方式 | 実行時点の画面状態 | 強い(構造的に無効化) | 中(ツール・運用の切替) |
検証環境としては browser-act-cli v1.1.0、Playwright 1.61.1、Python 3.12.13の組み合わせが使われています。セットアップは uv(Pythonのパッケージ・ツール管理コマンド)経由で以下のように行えます。
uv tool install browser-act-cli --python 3.12
browser-act --version
browser-act browser create --name "dom-drift-test" --type chrome --desc "local churn test"
browser-act browser listbrowser list はブラウザ作成後に実行して、以降のセッションコマンドで使うブラウザIDを確認するためのものです。他のコマンド出力にはこのIDが表示されないため、忘れずに控える必要があります。
運用視点での影響と今日確認できること
オンプレの監視サーバーからクラウド上のヘッドレスブラウザ実行環境へ移行しているチームであれば、この「セレクタ陳腐化」はクラウド移行後に急に表面化しやすい問題です。理由は、CI/CD(継続的インテグレーション・デリバリー)のデプロイ頻度がオンプレ時代より上がり、フロントエンドのビルドハッシュが変わる頻度も比例して上がるためです。
今日から確認できることを、具体的な手順として整理します。
1. 監視・E2Eスクリプト内でidやclassから始まるセレクタを検索し、依存箇所を棚卸しする
2. デプロイ前後でChrome DevToolsのElementsパネルを開き、対象要素のid/classが変化していないか比較する
3. 可能な箇所からgetByRoleやgetByTextへの置き換えを優先度順に進める
4. 監視のアラート定義で、タイムアウトの原因を「サーバー応答なし」と「要素未検出」で別ログに分けて記録できているか見直す
5. 頻繁にUIが変わる画面や、動的に生成されるDOMを扱う箇所については、状態スナップショット方式のツール導入を検討する
特に4番目は運用コストに直結します。原因不明のタイムアウトが月に何度も発生し、毎回人手で「id変更が原因でした」と結論づけているなら、それは監視ロジックの設計課題です。ログにセレクタ不一致の情報を残すだけでも、翌日の調査時間は大きく減らせます。
まとめ
ブラウザ監視・自動化の障害の多くは、サーバー側の問題ではなく、フロントエンドの実装詳細に監視ロジックが依存していることが根本原因です。
対策は段階的に選べます。まずは既存のPlaywrightスクリプトでrole/textロケータへの置き換えを進め、それでも頻繁な構造変化に追いつかない場合は、状態スナップショット方式のツール導入を比較検討する、という順序が現実的です。
次の一歩として、手元の監視スクリプトでid/classセレクタへの依存箇所を洗い出し、直近のタイムアウト障害ログが「サーバー障害」と「セレクタ不一致」を区別できているか確認してみてください。