Selenium運用崩壊した話7選|QAエンジニアが実務で経験した失敗と立て直し策

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_waitWebDriverWait が設計方針なく混在している
  • 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未満では引き続き有効
💡 立て直し策:Selenium 4.6以降では Selenium Manager が標準搭載され、ChromeDriverの自動解決が可能です。ただし「完全に設定不要」ではなく、CI環境では以下の理由で追加設定が必要になるケースがあります。

  • Chromeがインストールされていない:最小構成のDockerイメージではChrome自体がない
  • PATH問題:コンテナ環境でChrome/ChromeDriverのパスが通っていない
  • 権限制限:バイナリ取得がファイルシステムの制限でブロックされる

既存プロジェクトやCI環境では webdriver-manager が引き続き広く使われているため、チームの運用に合わせて選択してください。CIではDockerイメージのChromeバージョンを固定する方法も組み合わせると安定します。

② implicitWaitとexplicitWaitを混在させてFlaky地獄になったとは?

Flakyテストとは「成功したり失敗したりが不安定で再現性のないテスト」のことです。CIを走らせるたびに結果が変わるため、「実際のバグなのか不安定なのか」の判断が困難になります。

何が起きたか

「待機処理をしっかりやろう」とドキュメントを読んだ結果、implicitly_waitWebDriverWait の両方をコード内に混在させてしまいました。動く日もあれば落ちる日もある——再現性のないFlakyテストが大量発生し、「テストが落ちても本当のバグかどうかわからない」状態になりました。

⚠️ implicitWaitとexplicitWaitを混在させることのリスク:

  • 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")
    # ...購入処理のみ...
⚠️ Page Object化の目安:「同じ操作が2箇所以上」はひとつのサインですが、実務では以下の軸も合わせて判断します。

  • UI変更頻度:変更が多い画面ほど集約価値が高い(変更箇所を1箇所にまとめられる)
  • テストの再利用性:複数のテストシナリオから呼ばれる操作は優先してPage Object化する
  • ドメインの境界:「ログイン」「商品検索」「カート操作」など、機能単位で分離すると保守しやすい

1000行になってから直すのは大工事ですが、300行の段階であれば比較的スムーズに整理できます。

💡 立て直し策:一気に全部をPage Object化しようとするとリファクタリングが失敗します。「最も使用頻度が高い操作(ログイン・ナビゲーション・フォーム入力)から優先してPage Object化する」のが現実的な順序です。

⑤ 担当者が退職して誰も触れないコードになったとは?

何が起きたか

「このSeleniumのテストはAさんしかわからない」——Aさんが退職した翌月、テストが落ちても誰も直せない状態になりました。コードにコメントはなく、ローカル環境の構築手順もドキュメント化されていませんでした。「テストが落ちても放置する」という文化が生まれ、自動化が形骸化していきました。

属人化の具体的な症状チェックリスト

※ Yes/Noだけでなく「チームの何割が対応できるか」という視点でも確認してみてください。1人しか対応できない状態が属人化の本質です。

チェック項目あなたのチームは?
READMEだけで環境構築が完了できる✅ / ❌
担当者以外の人がテストを追加・修正できる✅ / ❌
テストコードのコードレビューが行われている✅ / ❌
テストが落ちたとき、複数人が原因を調査できる✅ / ❌
「このテストはXXさんに聞かないとわからない」がない✅ / ❌
⚠️ 3つ以上❌なら危険信号:担当者が退職する前に対処が必要です。「テストが落ちても誰も直せない」状態になった瞬間、自動化への信頼は失われます。
💡 立て直し策:①環境構築手順をREADMEに書く(新人が手順通りに実行して動くレベルまで)、②テストコードをコードレビューの対象にする、③「テスト追加の最初の1件」を複数人でペアプログラミングする——この3つから始めると属人化の解消が現実的に進みます。

⑥ ヘッドレス化できずCIに組み込めなかったとは?

何が起きたか

「ローカルでは動く」ものの、CIサーバー(ヘッドレス環境)で動かすと軒並みエラーになりました。調査すると、ブラウザのウィンドウサイズや画面描画に依存した操作がテスト内に散在していることが判明。具体的には次のような問題が絡み合っていました。

一言でまとめると:ローカルと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 オブジェクト経由が推奨
DesiredCapabilitiesOptions クラスに統合
⚠️ アップグレードの鉄則:Seleniumに限らず、メジャーバージョンアップは必ず専用ブランチで全テスト通過を確認してからmainにマージしてください。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でも再発します。

Q. 「誰もテストコードを触れない」属人化を解消するには何から始めればいいですか?

最も即効性があるのは「READMEを整備して、別の人が手順通りに動かせるかを実際に試してもらう」ことです。環境構築ができれば、次は「1件だけ一緒にテストを追加する(ペアプログラミング)」で知識を共有します。コードレビューをテストコードにも適用することで、中長期的に属人化が解消されていきます。

Seleniumを続けた方がいいケースとは?

この記事では運用上の問題点を中心に解説しましたが、すべての現場でPlaywright移行が正解とは限りません。以下のケースでは、Selenium継続の方が現実的な選択です。

Seleniumが現実的な選択肢のケース

  • Selenium Grid資産が大規模:並列実行インフラが成熟していて、移行コストが膨大になる場合
  • Java・C#ベースの自動化基盤:PythonやTypeScriptへの移行が現実的でないチーム文化の場合
  • IE・レガシーブラウザ対応が必要:PlaywrightはIEをサポートしていない
  • 既存の数千件規模のSeleniumが安定稼働中:動いているものを壊すリスクが移行メリットを上回る場合

判断に使える2つの軸

  • チームスキル:TypeScript・JS文化がないチームでPlaywrightを導入すると、属人化がそのまま移植される
  • CI成熟度:CIが整備されていない段階でのツール移行は、移行コストを正しく見積もれず途中で止まりやすい
💡 大切なのは:「流行っているからPlaywrightに移行する」ではなく、「現状の課題に対して最適か」で判断することです。この記事で紹介した保守負債は、Seleniumのまま設計を整えることでも解消できます。

Seleniumは20年以上の歴史を持つ、実績あるツールです。この記事で紹介した7つのパターンは、すべて「Seleniumが悪い」のではなく「運用設計がないまま拡大した結果」でした。今日からでも予兆を確認し、1つずつ手を打てば、維持困難になった運用も必ず立て直せます。

🚀 今すぐ始める優先順位TOP3

読んだその日に実施できる対処から始めましょう。

1位requirements.txt でバージョンを固定する
今日できる・効果が大きい・リスクゼロ
2位implicitly_wait の使用箇所を grep で洗い出す
grep -r "implicitly_wait" ./tests で全件確認・1〜2日で対処可能
3位xpath フルパスの使用箇所を洗い出す
grep -r "By.XPATH" ./tests で確認し、影響度の高いものからdata-testid化を進める

📋 この記事のまとめ

  • ChromeDriverのバージョン不一致は、Selenium 4.6+ の Selenium Manager または webdriver-manager で自動管理できる
  • implicitWaitとexplicitWaitの混在はFlaky地獄の根本原因。explicitWaitに統一する
  • xpathフルパスは「使わない」ルールを今すぐ作る。安定属性付きCSS/XPath・data-testidへの移行を進める
  • Page Object化は「同じ操作が2箇所以上に登場したら」が実務でのサイン
  • 属人化の解消はREADME整備とコードレビューの適用から始める
  • ヘッドレス差異(viewport・lazy rendering・sticky UI)はローカルで確認できる。window-sizeの固定とscrollIntoViewを標準化する
  • メジャーバージョンアップは専用ブランチで計画的に。requirements.txtのバージョン固定は必須
  • Seleniumを続けるべきケースもある。「流行だから移行」ではなく「現状課題に対して最適か」で判断する
タイトルとURLをコピーしました