PO模式(Page Object Model)
PO模式(Page Object Model)
PO模式,即页面对象模型,是UI自动化测试中一种主流的、用于增强代码可维护性、可读性和复用性的设计模式。 其核心思想是将测试逻辑与页面细节(如元素定位、页面结构)分离。
concept

核心思想
- 页面抽象为对象:将每个被测试的Web页面或移动端页面(或页面的一个重要组件)抽象为一个独立的类(Class),这个类就是 PageObject。
- 操作为方法:将对页面元素的所有操作(如点击、输入、获取文本)封装为这个类的方法。
- 元素定位与业务逻辑分离:页面上所有的UI元素定位器(如XPath, CSS Selector, ID)都作为这个类的属性。测试用例脚本中不直接出现任何元素定位信息,只调用PageObject提供的方法。
标准分层结构
一个典型的PO模式项目结构通常包含以下三层:
- PageObject层 (基础层):
职责:封装单一页面的所有元素信息和基本操作。
内容:类的属性是元素定位器,类的方法是针对这些元素的基本操作(如 inputUsername, clickSubmit)。
示例:LoginPage, HomePage, ProductDetailPage。 - PageModule / Business Flow层 (业务流层,可选但推荐):
职责:将多个PageObject的方法组合起来,形成可复用的、完整的业务操作流程。
内容:一个类的方法中会调用多个不同PageObject的方法。
示例:LoginFlow.login(username, password)内部会调用 LoginPage.inputUsername()和 LoginPage.clickSubmit()。OrderFlow.createOrder()可能会串联浏览商品、加入购物车、结算等多个页面的操作。 - TestCase层 (测试层):
职责:编写具体的测试用例,包含测试数据、断言和测试步骤。
内容:测试用例脚本。这里只调用PageModule的业务方法或PageObject的页面方法,并进行结果断言。完全看不到任何元素定位信息。
示例:test_login_success, test_add_item_to_cart。
代码示例(以Web登录为例)
- PageObject - LoginPage.py
from selenium.webdriver.common.by import By from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC class LoginPage: def __init__(self, driver): self.driver = driver self.wait = WebDriverWait(driver, 10) # 元素定位器(页面细节集中在此) self.username_input = (By.ID, "username") self.password_input = (By.ID, "password") self.submit_button = (By.XPATH, "//button[@type='submit']") self.error_message = (By.CLASS_NAME, "alert-error") def enter_username(self, username): # 封装操作细节 element = self.wait.until(EC.presence_of_element_located(self.username_input)) element.clear() element.send_keys(username) return self # 支持链式调用 def enter_password(self, password): element = self.wait.until(EC.presence_of_element_located(self.password_input)) element.send_keys(password) return self def click_submit(self): self.wait.until(EC.element_to_be_clickable(self.submit_button)).click() # 通常返回下一个页面的PO对象,例如 return HomePage(self.driver) def get_error_text(self): return self.wait.until(EC.visibility_of_element_located(self.error_message)).text - PageModule - LoginFlow.py
class LoginFlow: @staticmethod def login_with_credentials(driver, username, password): login_page = LoginPage(driver) # 组合基本操作形成业务流 login_page.enter_username(username).enter_password(password).click_submit() # 返回登录后的页面对象,如首页 from pages.home_page import HomePage return HomePage(driver) - TestCase - test_login.py
import pytest from flows.login_flow import LoginFlow from pages.home_page import HomePage class TestLogin: def test_login_success(self, browser): # browser是pytest fixture,提供driver # 测试脚本清晰,只有业务调用和断言 home_page = LoginFlow.login_with_credentials(browser, "validUser", "validPass") assert home_page.is_user_logged_in("validUser") == True def test_login_failure(self, browser): login_page = LoginPage(browser) login_page.enter_username("invalidUser").enter_password("wrongPass").click_submit() error_text = login_page.get_error_text() assert "Invalid credentials" in error_text
PO模式的设计原则(六大原则)
- 单一职责原则:一个PageObject只代表一个页面(或一个功能完整的组件)的逻辑。
- 方法代表服务:PageObject的方法应体现用户在页面上的“目标”或“服务”,而不是实现细节(如“点击某个按钮”是细节,“提交登录”是目标)。
- 避免在PO内断言:断言是测试逻辑,应放在TestCase层。PO方法可以返回结果(如是否成功、获取文本)供TestCase断言。
- 返回其他PO:一个页面的操作导致页面跳转时,该方法应返回下一个页面的PageObject实例。这使测试用例的链式调用非常流畅。
- 不需要封装所有元素:只为测试涉及的元素创建定位器和方法。
- 相同动作,不同结果:同一个操作可能导致不同结果(如登录成功/失败),应在PO中通过不同方法或返回值来处理。
PO模式的优势
- 高可维护性:当页面UI发生变更时,通常只需修改对应的PageObject中的元素定位器,所有用到该元素的测试用例会自动生效,无需四处修改。
- 高可读性:测试用例由一系列业务方法组成(如 login(), addToCart(), checkout()),读起来像用户故事,易于理解和评审。
- 高复用性:封装的页面方法和业务流可以在多个测试用例中被重复使用,减少代码冗余。
- 低耦合:测试逻辑与UI细节、驱动(Selenium, Appium)解耦。更换测试框架或驱动时,影响范围小。
总结
PO模式通过抽象、封装和分层,将脆弱易变的UI元素定位与稳定的业务测试逻辑分离,是应对UI自动化“脆弱性”和“维护成本高”挑战的最有效实践,已成为现代UI自动化测试框架的事实标准。在实现时,通常结合PageFactory(用于懒加载元素)等工具,并遵循上述设计原则,以构建健壮、可持续的自动化测试工程。