SeleniumでE2Eテスト自動化を運用しているQAエンジニアが実際に経験した「運用崩壊」の失敗談を7選で解説します。ChromeDriverバージョン管理・Wait混在・xpathセレクタ依存・属人化など、Selenium固有の「動いていたのに壊れていく」パターンと立て直し策を実務経験をもとに紹介します。なお「保守負債」とは修正コストが雪だるま式に増えていく状態のことを指します。
「最初は動いていたのに、いつのまにか誰も触れないコードになっていた」——Seleniumの運用崩壊は、突然起きるのではなく、じわじわと進行します。
📌 この記事はこんな方におすすめ
- SeleniumのE2Eテストを運用しているが、なんとなく不安定になってきた方
- 「なぜかテストが突然落ちる」「メンテナンスが追いつかない」と感じているQAエンジニア
- Selenium運用の健全化・立て直しを検討しているテストリード
- SeleniumからPlaywrightへの移行を判断する材料が欲しい方
✅ この記事を読むと得られること
- Selenium固有の運用崩壊パターン7つと具体的な崩壊の過程がわかる
- 「まだ崩壊していないが予兆がある」状態を早期に発見する方法がわかる
- 各パターンの立て直し策・予防策がわかる
👤 この記事を書いた人
QAエンジニア・テスト自動化エンジニアとして15年以上の実務経験を持つ Yoshi が執筆。Seleniumを使った自動化の立ち上げから、運用が崩壊するまでの過程、そして立て直しまでを複数のプロジェクトで経験しています。
📖 関連記事との使い分け
- テスト自動化でよくある失敗5選:ツール非依存の「戦略・設計」レベルの失敗 → 「何を自動化すべきか」の判断で失敗した方はこちら
- Playwright導入で失敗した話7選:Playwright固有の導入ミス → Playwrightに移行後に失敗した方はこちら
- この記事:Selenium固有の「運用継続中にじわじわ崩壊していくパターン」
📌 結論(3つのポイント)
- Seleniumの運用崩壊は「ChromeDriver管理」「Wait設計」「セレクタ設計」というSelenium固有の3つの地雷から始まる
- 崩壊は突然起きず、「なんとなく不安定」「修正が追いつかない」という予兆がある。予兆の段階で対処できる
- Seleniumが悪いのではない。運用設計がないまま拡大したことが崩壊の本質
「最初の半年は順調だった。でも1年後、誰もそのテストコードに触れなくなっていた」——複数のプロジェクトで繰り返し目撃してきた光景です。
Seleniumの運用悪化に共通するのは、「突然壊れる」のではなく「じわじわと壊れていく」ことです。この記事では、保守負債の7つのパターンと、それぞれの立て直し策を実体験をもとに解説します。
🔍 まず確認:あなたのSelenium運用の予兆チェック
- CIが週1回以上 ChromeDriver起因で落ちる
- コード内に
implicitly_waitとWebDriverWaitが設計方針なく混在している - xpathのフルパスやclass依存セレクタが大量にある
- 同じログイン処理が複数箇所にコピー&ペーストされている
- 「このテストはXXさんに聞かないとわからない」状態がある
- ローカルでは動くがCI(ヘッドレス)では落ちるテストがある
requirements.txtでバージョンを固定していない
| ✅ 0〜2個:健全 引き続き定期的に確認を | ⚠️ 3〜4個:注意 該当項目から優先対処 | 🚨 5個以上:危険 今すぐ立て直し計画を |
Selenium運用崩壊した話7選とは?早見表
| # | パターン | 根本原因 | CI影響 | 修正コスト |
|---|---|---|---|---|
| ① | ChromeDriverバージョン管理地獄 | バイナリ管理の仕組みなし | 🔴 高 | 🟢 低 |
| ② | implicitWait・explicitWait混在 | Wait設計思想の不統一 | 🔴 高 | 🟡 中 |
| ③ | xpathセレクタ依存 | 脆弱なセレクタ設計 | 🔴 高 | 🔴 高 |
| ④ | 1000行スクリプトの肥大化 | 設計なしの「動けばいい」開発 | 🟡 中 | 🔴 高 |
| ⑤ | 担当者退職で誰も触れないコード | 属人化したまま運用拡大 | 🟡 中 | 🔴 高 |
| ⑥ | ヘッドレス化できずCI組み込み不可 | ローカル前提の実装設計 | 🔴 高 | 🟡 中 |
| ⑦ | Selenium 4アップグレードで全テスト停止 | 依存管理・移行計画の欠如 | 🔴 高 | 🟡 中 |
① ChromeDriverバージョン管理地獄でCIが毎週壊れたとは?
何が起きたか
月曜の朝、CIが突然落ちています。ログを見ると SessionNotCreatedException: This version of ChromeDriver only supports Chrome version XX。金曜から週末の間にChromeが自動更新され、ChromeDriverとバージョンが合わなくなっているのです。
最初は「ChromeDriverをダウンロードして差し替えれば直る」と思っていました。しかしこれが毎週繰り返されます。ダウンロードページを探す→バージョンを確認する→差し替える→CIを再実行する——この作業に毎週月曜の午前が消えていきました。
崩壊の予兆
- ChromeDriverのパスをハードコードしている
requirements.txtにChromeDriverのバージョンが記載されていない- CI環境のChromeバージョン管理が「なんとなく最新」になっている
- ChromeDriverのダウンロードが手動作業になっている
# ❌ 崩壊パターン:ChromeDriverのパスをハードコード
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
driver = webdriver.Chrome(service=Service("/usr/local/bin/chromedriver"))
# バージョンが合わないと毎回ここでエラーになる
# ✅ 選択肢①:Selenium 4.6+ の Selenium Manager(標準搭載)
# Selenium 4.6以降はChromeDriver自動管理が標準機能として搭載
# ただしCI・Docker環境ではOS依存や権限設定により追加調整が必要な場合もある
from selenium import webdriver
driver = webdriver.Chrome()
# 追加ライブラリ不要(ただし環境依存の調整が必要なケースあり)
# ✅ 選択肢②:webdriver-manager(既存プロジェクト・CI環境で広く利用)
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from webdriver_manager.chrome import ChromeDriverManager
driver = webdriver.Chrome(
service=Service(ChromeDriverManager().install())
)
# 既存環境やSelenium 4.6未満では引き続き有効- Chromeがインストールされていない:最小構成のDockerイメージではChrome自体がない
- PATH問題:コンテナ環境でChrome/ChromeDriverのパスが通っていない
- 権限制限:バイナリ取得がファイルシステムの制限でブロックされる
既存プロジェクトやCI環境では webdriver-manager が引き続き広く使われているため、チームの運用に合わせて選択してください。CIではDockerイメージのChromeバージョンを固定する方法も組み合わせると安定します。
② implicitWaitとexplicitWaitを混在させてFlaky地獄になったとは?
※ Flakyテストとは「成功したり失敗したりが不安定で再現性のないテスト」のことです。CIを走らせるたびに結果が変わるため、「実際のバグなのか不安定なのか」の判断が困難になります。
何が起きたか
「待機処理をしっかりやろう」とドキュメントを読んだ結果、implicitly_wait と WebDriverWait の両方をコード内に混在させてしまいました。動く日もあれば落ちる日もある——再現性のないFlakyテストが大量発生し、「テストが落ちても本当のバグかどうかわからない」状態になりました。
implicitly_waitはドライバー全体に設定されるグローバルな待機。一度設定するとセッション全体に影響するWebDriverWaitは特定要素を対象にした明示的な待機- 混在するとタイムアウトの予測が難しくなり、テストごとの待機挙動がバラバラになってデバッグが困難になる。Selenium公式も混在を避けるよう推奨している
- 実務では混在していても短期的に安定するケースはありますが、テスト数が増えるほどこのリスクが顕在化します。基本方針としてexplicitWaitに統一することで、待機設計が明確になりFlakyの発生を抑えられます
なお、Flakyの原因はWait設計だけではありません。ネットワーク遅延・レンダリング差異・テスト間の状態共有なども主要因になります。Wait設計の整理はあくまで「Flakyを引き起こす要素のひとつを潰す」作業です。
# ❌ 崩壊パターン:implicitWaitとexplicitWaitの混在
driver.implicitly_wait(10) # グローバル設定
# 別の場所で
wait = WebDriverWait(driver, 5)
element = wait.until(EC.presence_of_element_located((By.ID, "submit")))
# タイムアウト計算が干渉し、Flakyの原因になる
# ✅ 立て直し策:explicitWaitに統一する
# implicitly_wait は使用しない
# すべてWebDriverWaitで明示的に待機
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
wait = WebDriverWait(driver, 10)
element = wait.until(
EC.element_to_be_clickable((By.ID, "submit"))
)
element.click()implicitly_wait が混在しているなら、まずそれをすべて除去することから始めます。grep -r "implicitly_wait" ./tests で検索して全件洗い出しましょう。③ xpathセレクタ依存でUIリニューアルのたびに全壊したとは?
何が起きたか
「とにかく要素を指定できればいい」という方針で、xpathをフルパスで指定したテストコードが蓄積されていきました。半年後、フロントエンドのリニューアルでHTMLの構造が変わったとき、300件のテストのうち280件が同時に落ちました。xpathのパスが軒並み無効になったのです。
# ❌ 崩壊パターン:フルパスxpathへの依存
driver.find_element(
By.XPATH,
"/html/body/div[2]/main/section[1]/form/div[3]/button"
)
# HTMLの構造が少し変わるだけで全壊する
# ⚠️ 相対xpathでも脆弱な場合がある
driver.find_element(By.XPATH, "//div[@class='btn-wrapper']/button[1]")
# classが変わると壊れる
# ✅ 推奨:data-testidを使ったCSS Selectorが最も安定
driver.find_element(By.CSS_SELECTOR, "[data-testid='submit-btn']")
# data-testidはテスト専用の属性 → UIリニューアルでも変えないルールを作れる
# ✅ 次点:IDやname属性
driver.find_element(By.ID, "submit-btn")
driver.find_element(By.NAME, "submit")セレクタ安定性の比較
| セレクタ種類 | UIリニューアル耐性 | 推奨度 |
|---|---|---|
data-testid 属性 | ✅ 最強 | 開発チームと合意の上で必須化 |
| ID属性 | ✅ 強い | ID が付いている要素では積極的に使う |
| name属性 / aria-label | △ 中程度 | 存在する場合は積極活用 |
| 安定属性付き CSS/XPath 例: //button[@type='submit'] | △〜○ 条件付き | aria属性・type属性など変わりにくい属性を使えば実用的 |
| class依存 CSS/XPath | ⚠️ 弱い | デザイン変更で壊れやすい。最終手段 |
| xpath(フルパス) | ❌ 非常に低い | 基本的に避ける(変更頻度の低い管理画面など限定的な用途では例外的に使われることもある) |
data-testid 属性の付与をルール化してもらうのが最も根本的な解決策です。「テスト用の属性をHTMLに追加する」ことを、フロントエンドの開発標準に組み込む交渉をしましょう。既存のxpathは grep -r "By.XPATH" ./tests で洗い出して、優先度の高いテストから順次書き換えます。④ Page Object化せず1000行のスクリプトが誕生したとは?
何が起きたか
「まず動かすことが優先」という方針でテストを追加し続けた結果、1つのテストファイルが1000行を超えていました。ログインの処理が5箇所にコピー&ペーストされていて、ログイン画面のセレクタが変わると5箇所すべてを修正しなければなりません。修正漏れでバグが埋まるようになりました。
# ❌ 崩壊パターン:同じ操作がコード内に散在
def test_purchase():
driver.find_element(By.ID, "email").send_keys("user@example.com")
driver.find_element(By.ID, "password").send_keys("pass")
driver.find_element(By.ID, "login-btn").click()
# ...購入処理...
def test_profile():
driver.find_element(By.ID, "email").send_keys("user@example.com") # 重複
driver.find_element(By.ID, "password").send_keys("pass") # 重複
driver.find_element(By.ID, "login-btn").click() # 重複
# ...プロフィール処理...
# ✅ 立て直し策:Page Object Model で操作を集約
class LoginPage:
def __init__(self, driver):
self.driver = driver
def login(self, email: str, password: str):
self.driver.find_element(By.ID, "email").send_keys(email)
self.driver.find_element(By.ID, "password").send_keys(password)
self.driver.find_element(By.ID, "login-btn").click()
# テストコードは意図だけを書く
def test_purchase(driver):
LoginPage(driver).login("user@example.com", "pass")
# ...購入処理のみ...- UI変更頻度:変更が多い画面ほど集約価値が高い(変更箇所を1箇所にまとめられる)
- テストの再利用性:複数のテストシナリオから呼ばれる操作は優先してPage Object化する
- ドメインの境界:「ログイン」「商品検索」「カート操作」など、機能単位で分離すると保守しやすい
1000行になってから直すのは大工事ですが、300行の段階であれば比較的スムーズに整理できます。
⑤ 担当者が退職して誰も触れないコードになったとは?
何が起きたか
「このSeleniumのテストはAさんしかわからない」——Aさんが退職した翌月、テストが落ちても誰も直せない状態になりました。コードにコメントはなく、ローカル環境の構築手順もドキュメント化されていませんでした。「テストが落ちても放置する」という文化が生まれ、自動化が形骸化していきました。
属人化の具体的な症状チェックリスト
※ Yes/Noだけでなく「チームの何割が対応できるか」という視点でも確認してみてください。1人しか対応できない状態が属人化の本質です。
| チェック項目 | あなたのチームは? |
|---|---|
| READMEだけで環境構築が完了できる | ✅ / ❌ |
| 担当者以外の人がテストを追加・修正できる | ✅ / ❌ |
| テストコードのコードレビューが行われている | ✅ / ❌ |
| テストが落ちたとき、複数人が原因を調査できる | ✅ / ❌ |
| 「このテストはXXさんに聞かないとわからない」がない | ✅ / ❌ |
⑥ ヘッドレス化できずCIに組み込めなかったとは?
何が起きたか
「ローカルでは動く」ものの、CIサーバー(ヘッドレス環境)で動かすと軒並みエラーになりました。調査すると、ブラウザのウィンドウサイズや画面描画に依存した操作がテスト内に散在していることが判明。具体的には次のような問題が絡み合っていました。
- viewportサイズ差異:ローカルは1920×1080、ヘッドレスのデフォルトは800×600など — 要素がviewport外に出てクリック不可になる
- lazy rendering:スクロールするまで描画されない要素(遅延読み込み)がヘッドレスで未描画のまま
- sticky / fixed UI:ヘッダーやサイドバーがfixedで要素に重なり、クリックがブロックされる
- responsive breakpoint:画面幅が変わることでレイアウトが崩れ、想定の要素が表示されなくなる
# ❌ 崩壊パターン:ヘッドレスで動かない実装
# 画面サイズを前提にしたスクロール操作
driver.execute_script("window.scrollTo(0, 500)")
driver.find_element(By.ID, "submit").click()
# ヘッドレスでは500px先に要素がない場合がある
# ❌ viewport外の要素をクリック(ヘッドレスで失敗)
element = driver.find_element(By.ID, "footer-btn")
element.click() # 画面外の場合エラー
# ✅ 立て直し策①:スクロールしてから操作(ActionChains不要)
element = driver.find_element(By.ID, "submit")
driver.execute_script("arguments[0].scrollIntoView(true);", element)
element.click()
# ✅ 立て直し策②:ウィンドウサイズを明示的に設定
from selenium import webdriver
options = webdriver.ChromeOptions()
options.add_argument("--headless")
options.add_argument("--window-size=1920,1080") # CI環境でサイズを固定
driver = webdriver.Chrome(options=options)--window-size=1920,1080 を常にオプションに入れ、scrollIntoView を使った操作を標準化すると、ヘッドレス差異によるエラーが大幅に減ります。ただし fixed/stickyヘッダーが要素に重なる場合は scrollIntoView だけでは ElementClickInterceptedException が発生することがあります。その場合は JavaScript の arguments[0].click() で直接クリックするか、ヘッダー高さ分だけオフセットを調整する対処が必要です。⑦ Selenium 4アップグレードで全テストが動かなくなったとは?
何が起きたか
「セキュリティパッチがあるからSeleniumをアップグレードしよう」——pip install selenium --upgrade でSelenium 3からSelenium 4に上げた直後、200件のテストのうち150件以上が一斉に落ちました。Selenium 4では廃止されたAPIが複数あり、移行計画なしのアップグレードが大惨事を引き起こしました。
Selenium 3→4で廃止・変更された主なAPI
| Selenium 3の書き方 | Selenium 4での変更 |
|---|---|
driver.find_element_by_id("x") | driver.find_element(By.ID, "x") に統一 |
driver.find_element_by_xpath("//x") | driver.find_element(By.XPATH, "//x") |
webdriver.Chrome(executable_path="...") | Service オブジェクト経由が推奨 |
DesiredCapabilities | Options クラスに統合 |
requirements.txt でバージョンを固定していなかったことが、今回の崩壊の直接原因でした。# ❌ 崩壊パターン:Selenium 3の廃止APIを使い続けていた
driver.find_element_by_id("submit") # Selenium 4で削除
driver.find_element_by_xpath("//button") # Selenium 4で削除
driver.find_element_by_class_name("btn") # Selenium 4で削除
# ✅ Selenium 4対応の書き方(By.XXX を使う)
from selenium.webdriver.common.by import By
driver.find_element(By.ID, "submit")
driver.find_element(By.XPATH, "//button")
driver.find_element(By.CLASS_NAME, "btn")grep -r "find_element_by_" ./tests で旧APIの使用箇所を洗い出します。sed コマンドで一括置換も可能です。今後は requirements.txt にバージョンを固定し、アップグレードは専用ブランチで計画的に行いましょう。FAQ
Q. Seleniumの保守負債を防ぐために最初にやるべきことは何ですか?
優先度順に3つです。①requirements.txt でバージョンを固定する(すぐできる)、②Selenium 4.6+の Selenium Manager または webdriver-manager でChromeDriver管理を自動化する(1日で完了)、③implicitly_wait の使用箇所を検索して除去する(1〜2日)。この3つだけでも維持困難なリスクが大幅に下がります。
Q. メンテ不能になったSeleniumのテストスイートを全部書き直すべきですか?
一気に全部書き直すのは高リスクです。「最もよく実行されるテスト上位20件」から優先的に整理するアプローチが現実的です。全体の書き直しよりも「動いているテストを壊さずに改善していく」方針の方が、ビジネス的なリスクが低くなります。
Q. SeleniumはPlaywrightに置き換えられていくのですか?
Seleniumは「オワコン」ではありません。2024〜2025年もSelenium 4系の更新が続いており、Selenium GridやSelenium Manager など新機能も追加されています。求人市場でもSeleniumの需要は依然として高い状態が続いています。ただし新規プロジェクトでPlaywrightを採用するケースは増えており、両者の得意領域で使い分けるアプローチが現実的です。「どちらかが絶対に優れている」という話ではなく、チームの技術スタック・既存資産・プロジェクト要件で判断するのがベストです。
まず補足として:Selenium 4.6以降では Selenium Manager によりChromeDriver管理問題の多くは改善されています。「ChromeDriverが毎週壊れる」問題は、Selenium Manager または webdriver-manager への移行で解消できるケースもあります。
移行を検討すべき本質的な判断基準は「現在の保守負債の解消コスト」と「移行コスト」のどちらが大きいかです。「Flakyが多すぎてCIへの信頼が失われた」「テストを追加するたびに保守負債が増え続ける」「E2EとAPIテストを同一スイートで管理したい」という課題に対して、Seleniumのまま設計改善する方が低コストなケースもあります。移行前にテスト設計の問題を整理しないと、同じ問題がPlaywrightでも再発します。
