Selenium Selenium 之 POM 设计模式

QE LAB for QE LAB · 2023年04月11日 · 3118 次阅读

作者:赵泽鑫|QE_LAB

Selenium 之 POM 设计模式

  • The public methods reqresent the services that the page offers
  • Try not to expose the internals of the page
  • Generally don't make assertions
  • Methods return other PageObjects
  • need not represent an entire page
  • Different results for the same action are modelled as different methods

POM 设计模式的定义

随着自动化测试的逐渐发展,页面对象模型变得更加流行用来帮助自动化测试减少重复的代码。页面对象模型 page object modle 是一种设计模式, 最早是在Martin Fowler的博客中被提出,要求我们编写自动化测试的时候将应用中的不同页面抽象成对应的 page 类,类中通常包含页面的 element 以及页面可以进行的操作方法,比如输入,搜索,滑动等。

POM 设计和传统设计的区别

不使用 POM 设计的自动化测试代码,会将元素和页面的操作方法以及测试方法写在同一个类中,这样的好处是编写的时候比较容易,所见即所得,当前用例需要什么数据就写什么方法获取。但这样的编写的缺点也比较明显,当不同的用例之间有相同的前置条件的时候,重复的代码会大量的增加,随着业务的复杂和自动化测试用例的增加,会导致测试变的更加的混乱,页面 UI 和数据和用例之间的耦合严重,一旦 UI 发生变化,维护的成本也会增加。

使用 POM 设计的自动化测试代码,将页面以及操作都写在对应的 page class 中,可以使代码得到很好的复用,拆分的单元越小也就越容易进行复用。使用 POM 的时候要尽量避免形式化,即使用了 POM 设计,但是 page class 中仍然是将多种操作混在一起编写成一个方法。比如页面上的输入和搜索功能应该写两个方法,一个是输入数据,一个是搜索操作。而不应该在把两个操作写在一个方法中。这样其他用例需要某一个单独操作的时候就要重新写方法。与此同时,POM 的设计也会带来需要维护的类数量翻倍的问题。使用 POM 后我们不得不维护更多个类,page class 和 testcase class,当页面数量增加的时候,类的数量也会成倍增加,这也会一定程度增加维护的难度。

自动化测试中为了更好的复用代码,除了 page class 外,我们一般会将多个页面共同使用的方法单独抽出来创建一个 base page,其他 page 继承 base page。举个🌰:

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

class BasePage(object):
   def __init__(self, driver):
       self.driver = driver

   def find_element(self, *loc):
       WebDriverWait(self.driver, 1, 5).until(EC.visibility_of(self.driver.find_element(*loc)))
       return self.driver.find_element(*loc)

   def type_text(self, text, *loc):
       self.find_element(*loc).send_keys(text)

   def click(self, *loc):
       self.find_element(*loc).click()

PO 类的设计

PO 类就是包含了页面元素和操作方法的类,实际编写的过程中值得注意的是:

  1. 除了验证页面元素是否被正确的加载在页面对象外。页面对象不应该存在其他的验证或断言。断言是测试的一部分,应该出现在测试的代码中,而不是页面对象。

  2. 一个页面对象不一定需要代表一个页面本身的所有部分。用于页面对象的相同原则可用于创建 "页面组件对象",它代表页面的离散块,并可包含在页面对象中。这些组件对象可以提供对这些离散块内的元素的引用,以及利用它们所提供的功能的方法。甚至可以将组件对象嵌套在其他组件对象中,以获得更复杂的页面。

  3. PageObjects 应该尽可能少的暴露底层的 WebDriver 实例,所以 PageObject 上的方法应该返回其他 PageObjects。举个🌰:

比如我们要进行一个搜索操作,在首页输入信息后点击搜索会进入搜索页面,最后断言搜索结果是不是和预期一致。

这样我们得到了两个页面和一个搜索方法,一个输入方法。首页是 MainPage,搜索页是 SearchPage,我们在首页初始化了 driver,在点击搜索跳转后会进入搜索页,这时候我们可以 return SearchPage(self.driver) 传递一个 driver 到搜索页面。这样我们就不需要在搜索页面重新引入 webdriver 了。

下面是 PO 类实现的🌰:

class UserRegisterPage(BasePage):
   username_input = (By.NAME, 'username')
   email_input = (By.NAME, 'email')
   pwd_input = (By.NAME, 'pwd')
   register_btn = (By.CLASS_NAME, 'btn')

def __init__(self, driver):
   BasePage.__init__(self, driver)

def goto_register_page(self):
   self.driver.get("http://localhost:8080/jpress/user/register")
   self.driver.maximize_window()

def input_username(self, username):
   self.type_text(username, *self.username_input)

def input_pwd(self, pwd):
   self.type_text(pwd, *self.pwd_input)

def click_register_btn(self):
   self.click(*self.register_btn)

使用 POM 设计的用例和不使用 POM 设计的用例对比

使用前:

def test_register_code_error(self):
   username = 'test001'
   email = 'test001@qq.com'
   pwd = '123456'
   captcha = '666'
   expected = '验证码不正确'

   self.driver.find_element_by_name('username').send_keys(username)
   self.driver.find_element_by_name('email').send_keys(email)
   self.driver.find_element_by_name('pwd').send_keys(pwd)
   self.driver.find_element_by_name('captcha').send_keys(captcha)
   self.driver.find_element_by_class_name('btn').click()
   WebDriverWait(self.driver, 5).until(EC.alert_is_present())
   alert = self.driver.switch_to.alert
   assert alert.text == expected
   alert.accept()

使用后:

def test_register(self, username, email, pwd, captcha, expected):
   self.registerPage.input_username(username)
   self.registerPage.input_email(email)

   self.registerPage.input_pwd(pwd)
   self.registerPage.input_confirmPwd(confirm_pwd)
   self.registerPage.input_captcha(captcha)
   self.registerPage.click_register_btn()

   WebDriverWait(self.driver, 5).until(EC.alert_is_present())
   alert = self.driver.switch_to.alert
   assert alert.text == expected
   alert.accept()

我们会发现使用 POM 设计模式的用例封装性更高,页面提供的功能和页面元素都被封装在对应的方法内。而未使用 POM 设计模式的用例,测试步骤中掺杂着页面元素,比较简单直观。具体项目中到底怎么取舍这两种不同的设计模式也是仁者见仁、智者见智的。我认为如果一个项目中页面数量不多且业务逻辑比较复杂,这时候需要使用 POM 来松耦合,减少重复代码的同时增加代码的可读性,更能胜任复杂的业务逻辑测试。而一个项目页面数量多且业务不复杂,那也没有非要使用 POM 模式的必要性。

最后是我在 github 使用一个开源的博客系统搭建的小项目,没有服务器也可以在本地 run 起来使用。大家感兴趣的话可以 clone 下来玩一下,博客中的部分用例在 repo 中也比较完整。

传送🚪:https://github.com/Zhao-Hansi/automation_of_jpress.git

推荐阅读:

https://martinfowler.com/bliki/PageObject.html

https://pypom.readthedocs.io/en/latest/user_guide.html#drivers

https://www.selenium.dev/documentation/test_practices/encouraged/page_object_models/

暂无回复。
需要 登录 后方可回复, 如果你还没有账号请点击这里 注册