Page Object 模式

一、Page Object 模式
在Web应用程序的UI测试中,测试开发人员编写测试脚本并与web应用的某些区域进行交互。而Page Object只是将这些交互的区域建模为测试代码中的对象。 这减少了重复代码的数量,并且意味着如果web应用程序的UI更改了,测试人员则仅需要在一个地方修改对应的程序即。

归根结底,PO模式就是以页面为单位进行建模,隐藏实现的细节,本质就是面向接口编程。它可以减少重复的find click等代码,而且易读性比较高,页面布局变化后,容易进行修改,测试用例维护成本变低。
二、Page Object 六大设计原则
1、六大设计原则
- The public methods represent 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
2、六大设计原则解读
- 用公共方法代替UI所提供的服务
比如163邮箱登录页,主要功能是登录,然后还有选择登录方式、输入用户名、密码等功能,这些功能都可以封装成这个UI界面所提供的方法;当然,部分数据较多或者较为复杂,复用性也比较高的话,例如添加成员,也可以单独抽离出来做一个page。
- 不要暴露页面内部的元素给外部
测试开发人员将页面内部元素封装成为方法,后续测试脚本编写时,只需要利用这些方法对页面进行业务操作,而不用再关心页面内的元素;其本质,就是面向接口编程,测试人员只关心请求操作后接口的返回值是什么,而不需要关心接口内部的实现机制。
- 不要在方法内加断言
不同模块之间的功能不同,对一个测试用例而言,断言应该在测试用例中实现,而不应该在页面操作方法中,方法只是提供业务操作。
- 方法应该返回其他的Page Object 或者返回用于断言的数据
返回页面对象,这个页面可以是当前页(因为可能还要在这个页面进行其他操作),可以是其他页面(我们操作某个方法后很可能会跳转到另一个页面进行下一步操作);
返回断言的值,测试用例总归有预期结果的对吧,那么最后肯定要有方法返回一个值,用来给我们做断言,来判断用例执行是否符合预期结果。
返回self,不要返回null或者写一个void没有返回值的方法,这样的方法没有意义。可以返回self,即链式调用,后续可以再继续执行其他操作。
- 不需要建模UI内的所有元素
一个UI页面可能会包含很多的元素,但是我们只要根据实际业务需求,将我们用的上的元素进行建模即可
- 同样的行为不同的结果可以建模为不同的方法
同样的行为:无论输入的账号密码正确与否,都是按照输入账号密码,点击登录这样的行为去操作不同的结果:账号密码错误和正确得到的登录响应一定是不同的。
建模为不同的方法:对于登录页来说,就可以根据登录信息正确与否建模出
正确登录、账号错误登录、密码错误登录等方法了
三、Page Object 示例
1、框架组织结构
- page :主要保存对页面的封装方法
- driver :主要保存Web、Android、Ios、接口的驱动
- testcase :主要保存测试用例
- data :主要保存配置文件和数据
- utils :主要保存对其他扩展功能
- log:主要保存日志文件
- report:主要保存测试报告

2、示例
以163邮箱登录为例,可以把登录页封装成一个页面类,页面内部的方法再在此页面类中实现。其中页面中可以再分为三个层,元素层、操作层、业务层。
- 元素层:次层存放定位好的元素及定位方法
- 操作层:根据业务需要,结合定位的元素实现相应的操作,比如点击、输入等
- 业务层:根据业务需要,将封装的方法组成业务流操作,也就是一系列操作的集合,并具有现实意义,比如实现登录整个操作。
LoginPage:封装的登录页(非完成代码)
from selenium.webdriver.common.by import By
from page.basepage import BasePage
class LoginPage(BasePage):
# 对象层
_lbNormal_locator = (By.XPATH, '//*[@id="lbNormal"]') # 点击密码登录
_iframe_locator = (By.XPATH, '//div[@id="loginDiv"]/iframe') # 跳转iframe
_email_locator = (By.NAME, 'email') # 输入用户名
_password_locator = (By.NAME, 'password') # 输入密码
# 操作层
# 获取登录提示信息
def _get_error_text(self):
self.logger.info('获取登录提示信息')
return self.find(self._error_locator).text
# 获取用户ID信息
def _get_userid(self):
self.logger.info('获取用户ID信息')
return self.find(self._userid_locator).text
# 点击登录按钮
def _click_login_button(self):
self.logger.info('点击登录按钮')
self.find_and_click(self._dologin_locator)
return self
# 业务层
def login_by_password_success(self, username, password):
self.get_web_url()
self._input_username(username)
self._input_password(password)
self._click_login_button()
return self._get_userid()
BasePage:对于公共的方法,比如频繁调用的查找元素操作find可以将其封装至其中。
from selenium.webdriver.remote.webdriver import WebDriver
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.wait import WebDriverWait
class BasePage:
def __init__(self, driver: WebDriver, logger):
self.logger = logger
self.driver = driver
# 查找元素
def find(self, locator):
self.show_wait_find_element(locator)
self.logger.info(f'查找元素:{locator}')
return self.driver.find_element(*locator)
# 查找并点击元素
def find_and_click(self, locator):
self.show_wait_find_element(locator)
self.logger.info(f'查找并点击元素:{locator}')
return self.driver.find_element(*locator).click()
# 显示等待元素
def show_wait_find_element(self, locator):
try:
self.logger.info(f'显示等待元素:{locator}')
return WebDriverWait(self.driver, 10).until(EC.visibility_of_element_located(locator))
except Exception as msg:
return self.logger.debug(f'显示等待元素出错,出错信息为:{msg}')
TestCase:利用LoginPage中实现的业务方法,进行测试并断言。
import pytest
from page.mainpage import MainPage
class TestLogin:
@classmethod
def setup_class(cls):
cls.login_page.click_login()
@classmethod
def teardown_class(cls):
cls.login_page.quit()
@pytest.mark.parametrize("username,password,expected", [('XXXX', 'XXXX', 'XXXX'),])
def test_login_mail_success(self, username, password, expected):
actual = self.login_page.login_by_password_success(username, password)
assert actual == expected, '登录成功,断言失败'
四、参考
1、https://martinfowler.com/bliki/PageObject.html
2、https://github.com/SeleniumHQ/selenium/wiki/PageObjects
3、https://selenium-python.readthedocs.io/page-objects.html
4、https://pypom.readthedocs.io/en/latest/

浙公网安备 33010602011771号