PageObject 簡介
在為 UI 頁面寫測試用例時(比如 Web 頁面,移動端頁面),測試用例會存在大量元素和操作細節。如何面對當 UI 變化時,測試用例也要跟着變化這個問題?PageObject 設計模式閃亮登場(由 IT 大佬 Martin Flower 提出)。
使用 UI 自動化測試工具時(Selenium、Appium 等),如果無統一模式進行規範,随着用例的增多會變得難以維護,而 PageObject 讓自動化腳本井井有序,将 page 單獨維護并封裝細節,可以使 testcase 更穩健,不需要大改動。
PageObject 使用
具體做法:把元素資訊和操作細節封裝到 Page 類中,在測試用例上調用 Page 對象(PageObject),比如存在一個功能“選取相冊标題”,需要為之建立函數selectAblumWithTitle(),函數内部是操作細節findElementsWithClass('album')等:

以選“取相冊标題”舉例,僞代碼如下:
selectAblumWithTitle() {
#選取相冊
findElementsWithClass('album')
#選取相冊标題
findElementsWithClass('title-field')
#傳回标題内容
return getText()
}
PageObject 的主要原則是提供一個簡單接口 (或者函數,比如上述的 selectAblumWithTitle ),讓調用者在頁面上可以做任何操作,點選頁面元素,在輸入框輸入内容等。是以,如果要通路一個文本字段,Page Object 應該有擷取和傳回字元串的方法。Page Object 應該封裝對資料的操作細節,比如查找元素和點選元素。當頁面元素改動時,應該隻改變 Page 類中的内容,不需要改變調用它的地方。
不要為每個 UI 頁面都建立一個 page 類,應該隻為頁面中重要的元素建立 page 類。比如,一個頁面顯示多個相冊,應該建立一個相冊清單 page object,它包含許多相冊 page object。如果某些複雜 UI 的層次結構隻是用來組織 UI,那麼它就不應該出現在 page object 中。page object 的目的是通過給頁面模組化,進而對應用程式的使用者變得有意義:
如果你想導航到另一個頁面,初始 page 對象應當 return 另一個 page 對象,比如點選注冊,進入注冊頁面,在代碼中就應該 return Register()。如果想擷取頁面資訊,可以 return 基本類型(字元串、日期)。
建議不要在 page object 中放斷言。應該去測 page object,而不是讓 page object 自己測自己,page object 的責任是提供頁面的狀态資訊。這裡僅用 HTML 描述 Page Object,這種模式還可以用來隐藏 Java swing UI 細節,它可用于所有 UI 架構。
PageObject 六大原則
Selenium 針對 PageObject 的核心思想凝聚出了六大原則,掌握六大原則精髓,才可以進行 PageObject 最佳實踐演練:
- 公共方法代表頁面提供的服務
- 不要暴露頁面細節
- 不要把斷言和操作細節混用
- 方法可以 return 到新打開的頁面
- 不要把整頁内容都放到PO 中
- 相同的行為會産生不同的結果,可以封裝不同結果
下面,對上述六大原則進行更詳細的實操解釋:
- 原則一:要封裝頁面中的功能(或者服務),比如點選頁面中的元素,可以進入到新的頁面,于是,可以為這個服務封裝方法“進入新頁面”。
- 原則二:封裝細節,對外隻提供方法名(或者接口)。
- 原則三:封裝的操作細節中不要使用斷言,把斷言放到單獨的子產品中,比如 testcase。
- 原則四:點選一個按鈕會開啟新的頁面,可以用 return 方法表示跳轉,比如return MainPage()表示跳轉到新的PO:MainPage。
- 原則五:隻為頁面中重要的元素進行 PO 設計,舍棄不重要的内容。
- 原則六:一個動作可能産生不同結果,比如點選按鈕後,可能點選成功,也可能點選失敗,為兩種結果
封裝兩個方法,click_success和click_error。
基于企業微信的 PO 實戰案例
以企業微信首頁為例,企業微信首頁有二個主要功能:立即注冊和企業登入。
企業微信網址:
https://work.weixin.qq.com/- Index 頁面
自動化測試實戰 | 搞定 PageObject 設計模式PageObject 簡介PageObject 使用PageObject 六大原則基于企業微信的 PO 實戰案例實戰代碼
點選企業登入可以進入登入頁面,在頁面可以掃碼登入和企業注冊。
- Login 頁面
自動化測試實戰 | 搞定 PageObject 設計模式PageObject 簡介PageObject 使用PageObject 六大原則基于企業微信的 PO 實戰案例實戰代碼
點選企業注冊可以進入注冊頁面,在頁面可以輸入相關資訊進行注冊。
- Register 頁面
自動化測試實戰 | 搞定 PageObject 設計模式PageObject 簡介PageObject 使用PageObject 六大原則基于企業微信的 PO 實戰案例實戰代碼
用 Page Object 原則為頁面模組化,這裡涉及三個頁面:首頁,登入,注冊。在代碼中建立對應的三個類Inde,Login,Register:
- 登陸頁⾯提供 login findPassword 功能
- Login類 + login findPassword⽅法
- 登入頁⾯内的元素有多少并不關⼼,隐藏内部界⾯控件
- 登入成功和失敗會分别傳回不同的頁⾯
- findPassword
- loginSuccess
- loginFail
-
通過⽅法傳回值判斷登入是否符合預期
UML 圖
實戰代碼
目錄結構
BasePage 是所有 page object 的父類,它為子類提供公共的方法,比如下面的 BasePage 提供初始化 driver 和退出 driver,代碼中在 base_page 子產品的 BasePage 類中使用 init 初始方法進行初始化操作,包括 driver 的複用,driver 的指派,全局等待的設定(隐式等待)等等:
from time import sleep
from selenium import webdriver
from selenium.webdriver.remote.webdriver import WebDriver
class BasePage:
def __init__(self, driver: WebDriver = None):
#此處對driver進行複用,如果不存在driver,就構造一個新的
if driver is None:
# Index頁面需要用,首次使用時構造新driver
self._driver = webdriver.Chrome()
# 設定隐式等待時間
self._driver.implicitly_wait(3)
# 通路網頁
self._driver.get(self._base_url)
else:
# Login與Register等頁面需要用這個方法,避免重複構造driver
self._driver = driver
def close(self):
sleep(20)
self._driver.quit()
Index 是企業微信首頁的 page object,它存在兩個方法,進入注冊 page object 和進入登陸 page object,這裡 return 方法傳回 page object 實作了頁面跳轉,比如:goto_register方法return Register,實作從首頁跳轉到注冊頁:
from selenium.webdriver.common.by import By
from test_selenium.page.base_page import BasePage
from test_selenium.page.login import Login
from test_selenium.page.register import Register
class Index(BasePage):
_base_url = "https://work.weixin.qq.com/"
# 進入注冊頁面
def goto_register(self):
self._driver.find_element(By.LINK_TEXT, "立即注冊").click()
# 建立Register執行個體後,可調用Register中的方法
return Register(self._driver)
# 進入登入頁面
def goto_login(self):
self._driver.find_element(By.LINK_TEXT, "企業登入").click()
# 建立Login執行個體後,可調用Login中的方法
return Login(self._driver)
Login 是登入頁面的 page object,主要功能有:進入注冊頁面,掃描二維碼,是以建立兩個方法代表兩個功能:scan_qrcode和goto_registry。代碼跟上面相似,不過多介紹:
from selenium.webdriver.common.by import By
from test_selenium.page.base_page import BasePage
from test_selenium.page.register import Register
class Login(BasePage):
# 掃描二維碼
def scan_qrcode(self):
pass
# 進入注冊頁面
def goto_registry(self):
self._driver.find_element(By.LINK_TEXT, "企業注冊").click()
return Register(self._driver)
Register 是注冊頁面的 page object,主要功能是填寫正确注冊資訊,當填寫錯誤時,傳回錯誤資訊。register 方法實作了正确的表格填寫,當填寫完畢時傳回自身(頁面還停留在注冊頁)。get_error_message 方法實作了錯誤填寫的情況,如果填寫錯誤,就收集錯誤内容并傳回:
from selenium.webdriver.common.by import By
from test_selenium.page.base_page import BasePage
class Register(BasePage):
# 填寫注冊資訊,此處隻填寫了部分資訊,并沒有填寫完全
def register(self, corpname):
# 進行表格填寫
self._driver.find_element(By.ID, "corp_name").send_keys(corpname)
self._driver.find_element(By.ID, "submit_btn").click()
# 填寫完畢,停留在注冊頁,可繼續調用Register内的方法
return self
#填寫錯誤時,傳回錯誤資訊
def get_error_message(self):
# 收集錯誤資訊并傳回
result=[]
for element in self._driver.find_elements(By.CSS_SELECTOR, ".js_error_msg"):
result.append(element.text)
return result
test_index 子產品是對上述功能的測試,它獨立于 page 類,在 TestIndex 類中隻需要調用 page 類提供的方法即可,比如下面對注冊頁及登陸頁的測試使用了 test_register 和 test_login 方法:
from test_selenium.page.index import Index
class TestIndex:
# 所有步驟前的初始化
def setup(self):
self.index = Index()
# 對注冊功能的測試
def test_register(self):
# 進入index,然後進入注冊頁填寫資訊
self.index.goto_register().register("霍格沃茲測試學院")
# 對login功能的測試
def test_login(self):
# 從首頁進入到注冊頁
register_page = self.index.goto_login().goto_registry()\
.register("測吧(北京)科技有限公司")
# 對填寫結果進行斷言,是否填寫成功或者填寫失敗
assert "請選擇" in "|".join(register_page.get_error_message())
# 關閉driver
def teardown(self):
self.index.close()