ページオブジェクトモデルへの書き換え

1. ページオブジェクトモデル(POM)とは何か

概念 説明
定義 画面(ページ・ダイアログ・コンポーネント)をクラスとして抽象化し、その**状態(要素)振る舞い(操作)**をテストコードから分離してカプセル化する設計パターン。
目的 ✔ 変更箇所の局所化
✔ 冗長なコードの削減
✔ 可読性・再利用性の向上
✔ ドメイン知識とテストロジックの明確な分割

2. 既存スクリプトから POM へ書き換える全体フロー

  1. ページ(または機能)単位に現行テストを棚卸し

    • 画面遷移図や UI マップを作成し、要素と操作の対応関係を整理

  2. ページクラスのスケルトンを作成

    • URL/主要要素/代表操作をメソッドとして列挙

  3. ロケーターと操作メソッドの移植

    • 既存テストから driver.find_element... のロジックを移し替え

  4. テストケース側でページクラスを呼び出すようリファクタ

    • 旧ロケーター呼出しを削除し、LoginPage.login(user, pass) などへ置換

  5. 共通処理の抽出とユーティリティ統合

    • 待機・例外処理を BasePage / ヘルパーにまとめる

  6. CI パイプラインで段階的に回帰

    • テストグループを分割し、POM 化ブランチを順次統合


3. スケルトン例

BasePage(Python)

python
from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class BasePage: TIMEOUT = 10 def __init__(self, driver): self.driver = driver def wait_visible(self, locator): return WebDriverWait(self.driver, self.TIMEOUT).until( EC.visibility_of_element_located(locator) )

LoginPage(Python)

python
from selenium.webdriver.common.by import By from .base_page import BasePage class LoginPage(BasePage): URL = "https://example.com/login" _USERNAME = (By.ID, "username") _PASSWORD = (By.ID, "password") _SUBMIT = (By.CSS_SELECTOR, "button[type='submit']") # アクション def open(self): self.driver.get(self.URL) def login(self, user, pwd): self.wait_visible(self._USERNAME).send_keys(user) self.driver.find_element(*self._PASSWORD).send_keys(pwd) self.driver.find_element(*self._SUBMIT).click() # 戻り値で遷移後ページを返すとチェーンが容易 from .dashboard_page import DashboardPage return DashboardPage(self.driver)

LoginTest(pytest)

python
def test_login_success(driver, credentials): login = LoginPage(driver) dashboard = login.open().login(credentials.user, credentials.password) assert dashboard.is_loaded()

4. 言語別ベストプラクティス

言語 推奨ライブラリ/機構 ポイント
Java JUnit/TestNG + Selenium 4 + org.openqa.selenium.support.PageFactory @FindBy アノテーションで要素宣言。コンストラクタで PageFactory.initElements(driver, this) を呼ぶだけで DI 可能
Python pytest + selenium-page-factory または独自 BasePage 動的言語の特性を活かし、辞書にロケーターを保持してループ処理で共通化する手法も有効
C# NUnit/xUnit + SeleniumExtras.PageObjects LoadableComponent<T> パターンを用いるとページ読み込み検証を標準化できる

5. インクリメンタル・マイグレーション戦略

  1. 高変更頻度ページから着手

    • 既存テストで最も“壊れやすい”部分を優先

  2. Facade クラスで旧 API をラップ

    • 一度に大量書き換えせず、旧テストも動かし続ける

  3. CI 上で A/B 実行

    • 同一シナリオを旧・新双方で実行し結果差分をチェック

  4. リント&コードカバレッジ閾値を設定

    • 粗いコードの混入防止、リファクタ効果を定量把握


6. よくある落とし穴と回避策

落とし穴 症状 回避策
Page クラス巨大化 1,000 行超・操作だらけ 画面内コンポーネントを Component Object としてさらに分割
過度な継承階層 多重継承でメソッド探索が困難 BasePage + 1 階層までに抑え、共通ロジックは Mix-in で
同期処理の散在 各メソッドが個別に Thread.sleep wait_* ヘルパーを一元化し、明示的待機へ統合
テストデータ依存強度 固定 ID やメールアドレスをハードコード フィクスチャ/Factory パターンで動的生成
PageFactory 乱用 遅延読み込みや動的要素に不向き Ajax 更新が多い場合は手動 By + 明示的待機の方が堅牢

7. 付随メリット

  • IDE 補完の質向上
    要素名がメソッド化されるため、テストケース上で誤入力が減少

  • ドメイン用語の共有辞書化
    CheckoutPage.placeOrder() のようにビジネスプロセスがコード名へ直結し、非開発者レビューが容易

  • 再利用率の向上
    同一操作を複数シナリオで呼び回せるため、新規シナリオ追加の工数が削減


8. まとめ

既存 Selenium スクリプトをページオブジェクトモデルへ書き換えることで、保守コストの最小化テストコードのドメイン化を両立できます。
ポイントは「画面 ≒ クラス、操作 ≒ メソッド」という単純明快な対応付けを保ちながら、待機・エラーハンドリングを共通化することです。段階的な移行と CI の並走を徹底し、アンチパターンに陥らないよう小さな単位でリファクタを進めていきましょう。

ChatGPT4o 生成日:2025/06/21