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/

posted @ 2020-03-17 13:49  xyztank  阅读(232)  评论(0)    收藏  举报