Python+Selenium Web 自動化テストにおけるよくある実装トラップと解決指南

1. 要素検出後のアクション未実行による例外発生

Selenium スクリプトで要素を特定しても、その後 CLICK や SEND_KEYS などの操作ハンドラを呼び出さない場合、テストランナーはエラーではなく完了ステータスを返すものの、実際の UI 操作は記録されません。これは設計段階で「識別→実行」のステップを分離せずに記述している場合に頻繁に発生します。必ずローケータ取得直後に適切なインタラクションメソッドをチェーンするか、明示的な呼び出し配置を行ってください。

2. ファイルアップロード機能における不可視 input 要素の扱い

ファイルをブラウザに読み込ませる際、画面にが表示されていないと勘違いして諦めるケースが多く見られます。CSS の非表示設定(display: none や visibility: hidden)や z-index の制御に関わらず、DOM ツリー上に物理的に存在すれば WebDriver は要素への参照を取得可能です。見つかった input 要素に対して絶対パスや相対パスを send_keys() 経由で渡すだけで、OS 標準のファイルダイアログを経由することなくアップロードフローは正常に終了します。

3. 基底クラスにおけるドライバ変数のデフォルト値設定不備

ページオブジェクトの親クラスを定義する際、コンストラクタ引数に初期値を指定しないと、サブクラス側で引数省略時の生成で例外が発生します。また、原本のコードでは渡されたドライバー変数を即座に None で上書きしてしまうため、依存性注入の恩恵を受けられません。正しい実装では、渡されたコントローラーがある場合はそれを保持し、空白の場合は新規 Chrome リソースを生成する条件分岐を実装する必要があります。

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.remote.webdriver import WebDriver

class BasePage:
    DEFAULT_URL: str = ""

    def __init__(self, controller: WebDriver = None):
        self._browser = controller
        
        if self._browser is None:
            chrome_cfg = Options()
            chrome_cfg.debugger_address = "127.0.0.1:9222"
            self._browser = webdriver.Chrome(options=chrome_cfg)
            
        if self.DEFAULT_URL:
            self._browser.get(self.DEFAULT_URL)
            
        self._browser.implicitly_wait(5)

4. レンダリング遅延によるタイミングエラー

スクリプトの実行スピードがページの DOM パースや JavaScript バインディングより速い場合、意図したノードがまだブラウザ内に取り込まれておらず、「ElementNotFound」や「StaleElementReferenceException」を引き起こします。このような非同期レンダリング環境では、fixed delay の導入よりも、WebDriverWait を用いて条件関数が True を返すまで自動的にポーリングする明示的待機パターンを採用するのがベストプラクティスです。

5. 検索ロジックのメソッド封装と柔軟性

要素探索関数を独自実装する際は、検索文字列だけでなく検索アルゴリズム自体を引数として外部から注入できる設計にすると、XPath から CSS セレクタへの変更が必要になった際の修正コストを劇的に削減できます。

def locate_target(self, rule_type: By, selector_value: str):
    return self._browser.find_element(rule_type, selector_value)

6. Page Object モデルにおける階層構造の一貫性

PO アーキテクチャを採用する際は、全ビューモデルが同一の共通基底クラスを継承することを強制してください。これにより WebDriver インスタンスのライフサイクル管理、グローバル待機時間の統合設定、および再利用可能なユーティリティ関数の一元展開が可能になり、テストスイートの保守性を確保できます。

7. ブラウザセッション状態と DOM 構造の不一致

ログイン済みユーザー向けの画面とゲスト公開画面では、HTML ツリーやデータ属性が完全に一致しません。Driver が操作しているブラウザのセッション情報(Cookie や Token)と、スクリプトが想定している要素が存在する前提環境がズレていると、同じセレクト戦略を使用しても発見できなくなります。要素を検索する前に、必ずアクティブなウィンドウの URL 正規化や認証ステータスの分岐チェックを実施してください。

8. 浮動レイヤー(フローティングUI)の検証手法

開発者ツールのカーソルツールだけで要素を強調表示しようとすると、position: fixed や high z-index を持つオーバーレイが干渉し、正確なバウンディングボックスが取得できないことがあります。そのような動的ポップアップやドラッグ可能ウィジェットについては、ブラウザウィンドウ上で直接該当領域をクリックまたはホバーさせた状態で、右クリックメニューから検証(Inspect)エントリを選択する方法が最も安定しています。

9. 要素不在時の DOM 差分調査

特定のセレクタでマッチしない場合、まず現在アクティブなタブが保持している生 HTML を抽出し、目的の ID やクラス名が実際にパースされているか文字列マッチで確認します。これにより、AJAX フェッチの失敗や条件付き render ループの欠落を迅速に切り分けられます。

def _verify_dom_marker(self, target_id: str) -> bool:
    rendered_markup = self._browser.page_source
    return target_id in rendered_markup

# 検証実行
if not _verify_dom_marker("submit_form_container"):
    raise AssertionError("Required UI node absent in current view.")

タグ: Selenium Python automated-web-testing page-object-pattern webdriverwait

6月3日 21:53 投稿