PO模式(Page Object Model)

1 minute read

PO模式(Page Object Model)

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

concept

POMexample

核心思想

  1. 页面抽象为对象:将每个被测试的Web页面或移动端页面(或页面的一个重要组件)抽象为一个独立的类(Class),这个类就是 PageObject。
  2. 操作为方法:将对页面元素的所有操作(如点击、输入、获取文本)封装为这个类的方法。
  3. 元素定位与业务逻辑分离:页面上所有的UI元素定位器(如XPath, CSS Selector, ID)都作为这个类的属性。测试用例脚本中不直接出现任何元素定位信息,只调用PageObject提供的方法。

标准分层结构

一个典型的PO模式项目结构通常包含以下三层:

  1. PageObject层 (基础层): 职责:封装单一页面的所有元素信息和基本操作。
    内容:类的属性是元素定位器,类的方法是针对这些元素的基本操作(如 inputUsername, clickSubmit)。
    示例:LoginPage, HomePage, ProductDetailPage。
  2. PageModule / Business Flow层 (业务流层,可选但推荐):
    职责:将多个PageObject的方法组合起来,形成可复用的、完整的业务操作流程。
    内容:一个类的方法中会调用多个不同PageObject的方法。
    示例:LoginFlow.login(username, password)内部会调用 LoginPage.inputUsername()和 LoginPage.clickSubmit()。OrderFlow.createOrder()可能会串联浏览商品、加入购物车、结算等多个页面的操作。
  3. TestCase层 (测试层):
    职责:编写具体的测试用例,包含测试数据、断言和测试步骤。
    内容:测试用例脚本。这里只调用PageModule的业务方法或PageObject的页面方法,并进行结果断言。完全看不到任何元素定位信息。
    示例:test_login_success, test_add_item_to_cart。

代码示例(以Web登录为例)

  1. 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
    
  2. 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)
    
  3. 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模式的设计原则(六大原则)

  1. 单一职责原则:一个PageObject只代表一个页面(或一个功能完整的组件)的逻辑。
  2. 方法代表服务:PageObject的方法应体现用户在页面上的“目标”或“服务”,而不是实现细节(如“点击某个按钮”是细节,“提交登录”是目标)。
  3. 避免在PO内断言:断言是测试逻辑,应放在TestCase层。PO方法可以返回结果(如是否成功、获取文本)供TestCase断言。
  4. 返回其他PO:一个页面的操作导致页面跳转时,该方法应返回下一个页面的PageObject实例。这使测试用例的链式调用非常流畅。
  5. 不需要封装所有元素:只为测试涉及的元素创建定位器和方法。
  6. 相同动作,不同结果:同一个操作可能导致不同结果(如登录成功/失败),应在PO中通过不同方法或返回值来处理。

PO模式的优势

  • 高可维护性:当页面UI发生变更时,通常只需修改对应的PageObject中的元素定位器,所有用到该元素的测试用例会自动生效,无需四处修改。
  • 高可读性:测试用例由一系列业务方法组成(如 login(), addToCart(), checkout()),读起来像用户故事,易于理解和评审。
  • 高复用性:封装的页面方法和业务流可以在多个测试用例中被重复使用,减少代码冗余。
  • 低耦合:测试逻辑与UI细节、驱动(Selenium, Appium)解耦。更换测试框架或驱动时,影响范围小。

总结

PO模式通过抽象、封装和分层,将脆弱易变的UI元素定位与稳定的业务测试逻辑分离,是应对UI自动化“脆弱性”和“维护成本高”挑战的最有效实践,已成为现代UI自动化测试框架的事实标准。在实现时,通常结合PageFactory(用于懒加载元素)等工具,并遵循上述设计原则,以构建健壮、可持续的自动化测试工程。