iT邦幫忙

2023 iThome 鐵人賽

DAY 18
0

學習原因:

Page Object Model(POM)是一種在自動化測試中使用的軟體測試設計模式,主要用於將 Test Case 的邏輯和網頁介面的元素分離,提高測試程式的可讀性維護性

學習目標:

  • 學習 Page Object Model 的設計
  • 建立 Test Case 和 Page Objects 之間的架構

Page Object Model (POM)

POM 的核心概念是將每個網頁視為一個 頁面 (Page),並為每個頁面創建一個對應的 Page Object。每個 Page Object 類封裝了該頁面的 元素定位操作方法,這樣測試代碼只需調用 Page Object 的方法,而不需要直接處理元素定位等細節。

https://ithelp.ithome.com.tw/upload/images/20230919/201620383Kq7i1bFKv.png

以上一篇文章已簡單優化過的程式,作為例子:

import time

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

def find_visible_element(locator, timeout=10):
    return WebDriverWait(driver, timeout).until(
        EC.visibility_of_element_located(locator)
    )

def find_clickable_element(locator, timeout=10):
    return WebDriverWait(driver, timeout).until(
        EC.element_to_be_clickable(locator)
    )

driver = webdriver.Chrome()
driver.maximize_window()
driver.get("https://demo.applitools.com/")

header = find_visible_element((By.CLASS_NAME, "auth-header"))
print(header.text)

username_elem = find_clickable_element((By.ID, "username"))
username_elem.send_keys("username")

pw_elem = find_clickable_element((By.ID, "password"))
pw_elem.send_keys("password")

checkbox_elem = find_clickable_element((By.CLASS_NAME, "form-check-input"))
checkbox_elem.click()

login_btn_elem = find_clickable_element((By.ID, "log-in"))
login_btn_elem.click()

time.sleep(10)
driver.quit()

改寫成 Page Object Model,把 Login 頁面寫成 Page Object。

from selenium.webdriver.common.by import By
from action_utils import ActionUtils

# 這裡會繼承 ActionUtils class,裡面會有針對 WebDriver 的操作方法,如找 Element 的方法等
# 下面會再詳述
class LoginPage(ActionUtils):

    # 把 Locator 以 Tuple 型態存儲成 Login Page 的 attribute
    # 可被 Page Object 內的 method 運用
    header = (By.CLASS_NAME, "auth-header")
    username_text_field = (By.ID, "username")
    pw_text_field = (By.ID, "password")
    remember_me_checkbox = (By.CLASS_NAME, "form-check-input")
    login_btn = (By.ID, "log-in")

    # 頁面操作相關的 methods
    def print_header_text(self):
        print("印出 Header 的文字")
        # 應用了父層 ActionUtils 的 Method
        print(self.find_visible_elem(self.header).text)

    def input_username(self, username):
        print(f"輸入 Username: {username}")
        username_elem = self.find_clickable_elem(self.username_text_field)
        username_elem.send_keys(username)

    def input_password(self, password):
        print(f"輸入 Password: {password}")
        pw_elem = self.find_clickable_elem(self.pw_text_field)
        pw_elem.send_keys(password)

    def check_remember_me(self):
        print("勾選 Remember me")
        self.find_clickable_elem(self.remember_me_checkbox).click()

    def click_login_btn(self):
        print("點擊登入按鈕")
        self.find_clickable_elem(self.login_btn).click()

而之前抽出的 find_visible_elem()find_clickable_elem(),因為可以被多個不同的 Page Object 應用,所以會建一個 Page Object 的 Parent,我這裡命名 ActionUtils,也有蠻多人會命名為 PageBase,都只供參考。

每個 Page Object 都會繼承 ActionUtils 以運用共用的 method。

from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

class ActionUtils():
    # 頁面的操作需要 Webdriver,所以需要主程式從參數傳入
    def __init__(self, driver):
        self.driver = driver

    def find_visible_elem(self, locator, timeout=10):
        return WebDriverWait(self.driver, timeout).until(
            EC.visibility_of_element_located(locator)
        )

    def find_clickable_elem(self, locator, timeout=10):
        return WebDriverWait(self.driver, timeout).until(
            EC.element_to_be_clickable(locator)
        )

    # 後續可擴充更多 Page Object 共用的 Method,都是應用 WebDriver 的相關操作。

Page Object 都寫好了,再來就是修改主程式。

import time

from selenium import webdriver
from login_page import LoginPage

driver = webdriver.Chrome()
driver.maximize_window()
driver.get("https://demo.applitools.com/")

# init login page object
login_page = LoginPage(driver)

# 運用 page object 的 method 完成了整個操作流程
login_page.print_header_text()
login_page.input_username("username")
login_page.input_password("password")
login_page.check_remember_me()
login_page.click_login_btn()

time.sleep(10)
driver.quit()

可見主程式少了很多雜訊,從這段程式更簡單的看出操作的步驟。

後續可能會覺得登入流程幾乎是每個 Test Case 都必須的組合流程,甚至會可以在 Page Object 裡面再多包一個 Method,然後就可被主程式直接運用。

class LoginPage(ActionUtils):
    ...

    def login(self, username, password):
        self.input_username(username)
        self.input_password(password)
        self.check_remember_me()
        self.click_login_btn()

簡單用一張圖來表達整體的架構

https://ithelp.ithome.com.tw/upload/images/20230919/201620381I9SomNNzj.png

使用 Page Object Model 優點:

  • 提高可讀性和維護性
    測試程式更清晰、易讀,且易於維護,因為元素定位和操作都在 Page Object 中集中處理。

  • 重用性
    如果頁面元素或功能發生變化,只需更新相應的 Page Object,不需修改多個測試案例。

  • 降低耦合度
    可以更專注於測試邏輯,不需了解頁面結構和元素定位的細節,只需調用 Page Object 方法。

學會 Page Object Model 的寫法,就繼續用昨天建議的網站,寫成 Page Object Model 練習吧。
Web 的 UI 測試先到這裡了,接下來會進入 API 測試的部分。


上一篇
Day 17: Selenium - Implicit & Explicit Wait
下一篇
Day 19: HTTP and REST API
系列文
從 0 開始培育成為自動化測試工程師的學習指南30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言