[Python]Page Object Modelパターンを用いたSelenium テストの記述方法メモ

* Page Object Modelパターンを利用したSeleniumテストの記述方法についてメモする。



## POM(Page Object Model)とは

* ページをクラスオブジェクトとして扱うブラウザ自動化テストのデザインパターンの一つ。

* **主な概念**
  * テストクラス
        * 対象ページのテストケース。
  * ページオブジェクト
        * 各Webページオブジェクトを作成することを目的としたクラス。
        * テスト用コードとWebページアクセス用コードを分離。
  * ロケータ
        * ページ要素を取得させる。
* **利点**
  * 可読性の高いテストケースを書くことができる。
  * 複数のテストケース間で共有できる再利用可能なコードを作ることができる※コード重複を防ぐことができる。

## 構成

```
root	-  TestBase.py
		|_ test_GoogleSearch.py
		|_ chromedriver.exe
		|_ Pages	-- google_page.py
					|_ Elements	-  BasePageElement.py
					|_ Locators	-  GoogleSearchPageLocators.py
								|_ GoogleSearchResultsPageLocators.py
								
```



## コード

* [こちら](https://kurozumi.github.io/selenium-python/page-objects.html)を参考に"Selenium"でGoogle検索するテストケースを記述する。

* `TestBase.py`

  * WebDriverの起動・停止などテストで共通する処理を集約する。

  ```python
  import unittest
  from selenium import webdriver
  
  
  # 共通処理を行うベーステストクラス
  # Webドライバーのオープン/クローズ
  class TestBase(unittest.TestCase):
      def setUp(self):
          self.driver = webdriver.Chrome()
          self.driver.implicitly_wait(10)
          self.driver.maximize_window()
  
      def tearDown(self):
          if (self.driver != None):
              print("Cleanup of test environment")
              self.driver.close()
              self.driver.quit()
  
  ```

  

* `test_GoogleSearch.py`

  * テストケースクラス

  ```python
  import unittest
  from Pages import google_page
  from TestBase import TestBase
  
  
  # テストクラス
  class GoogleSearch(TestBase):
      # テストケース
      def test_search_in_google(self):
  
          # www.google.comへアクセス
          self.driver.get("https://www.google.com/")
          main_page = google_page.MainPage(self.driver)
          # タイトルに'Google'が含まれているかを確認
          assert main_page.is_title_matches(), "Google Title Doesn't Match."
          # 検索ボックスに"selenium"を入力
          main_page.search_text_element = "selenium"
          # "Google検索"ボタンを押下
          main_page.click_search_button()
          search_results_page = google_page.SearchResultsPage(self.driver)
          # 検証:検索結果が0件でないこと
          assert search_results_page.is_results_found(), "No Search Results Found."
  
  
  if __name__ == "__main__":
      unittest.main()
  
  ```

  

* `Pages/google_page.py`

  * ページオブジェクトクラス。

  ```python
  from .Elements.BasePageElement import BasePageElement
  from .Locators.GoogleSearchPageLocators import GoogleSearchPageLocators
  from .Locators.GoogleSearchResultsPageLocators import GoogleSearchResultsPageLocators
  
  
  class SearchTextElement(BasePageElement):
      locator = 'q'
  
  
  class BasePage(object):
  
      def __init__(self, driver):
          self.driver = driver
  
  
  class MainPage(BasePage):
  
      search_text_element = SearchTextElement()
  
      def is_title_matches(self):
  
          return "Google" in self.driver.title
  
      def click_search_button(self):
          element = self.driver.find_element(
              *GoogleSearchPageLocators.SEARCH_BUTTON)
          element.click()
  
  
  class SearchResultsPage(BasePage):
  
      def is_results_found(self):
          element = self.driver.find_element(
              *GoogleSearchResultsPageLocators.SEARCH_RESULT)
          return element != None
  
  ```

  

* `Pages/Elements/BasePageElement.py`

  * ページ要素

  ```python
  from selenium.webdriver.support.ui import WebDriverWait
  
  
  # ページ要素クラス
  class BasePageElement(object):
  
      def __set__(self, obj, value):
          # 対象ページ要素が読み込まれるまで待機 → 初期化 → 値入力
          driver = obj.driver
          WebDriverWait(driver, 100).until(
              lambda driver: driver.find_element_by_name(self.locator))
          driver.find_element_by_name(self.locator).clear()
          driver.find_element_by_name(self.locator).send_keys(value)
  
      def __get__(self, obj, owner):
          # 対象ページ要素が読み込まれるまで待機 → 値取得
          driver = obj.driver
          WebDriverWait(driver, 100).until(
              lambda driver: driver.find_element_by_name(self.locator))
          element = driver.find_element_by_name(self.locator)
          return element.get_attribute("value")
  
  ```

  

* `Pages/Locators/GoogleSearchPageLocators.py`

  * Google検索ページ用ロケータ

  ```python
  from selenium.webdriver.common.by import By
  
  # Google検索ページ用ロケータクラス
  class GoogleSearchPageLocators(object):
      # "Google検索"ボタンのロケータ
      SEARCH_BUTTON = (By.NAME, 'btnK')
  
  ```

* `Pages/Locators/GoogleSearchResultsPageLocators.py`

  * Google検索結果ページ用ロケータ

  ```python
  from selenium.webdriver.common.by import By
  
  
  # Google検索結果ページ用ロケータクラス
  class GoogleSearchResultsPageLocators(object):
      # 検索結果件数のロケータ
      SEARCH_RESULT = (By.ID, 'result-stats')
  ```

  

## 動作確認

```shell
python test_GoogleSearch.py

DevTools listening on ws://127.0.0.1:60803/devtools/browser/e2f1274d-85ea-49c2-afdb-e118aea6e63e
Cleanup of test environment
.
----------------------------------------------------------------------
Ran 1 test in 11.892s

OK
```



## 参考情報

* [6. Page Objects](https://selenium-python.readthedocs.io/page-objects.html)
* [Page Object Model (POM) In Selenium Python](https://www.lambdatest.com/blog/page-object-model-in-selenium-python/)