Page Object 是 UI 自动化测试项目开发实践的最佳设计模式之一,它的主要特点体现在对界面交互细节的封装上,使测试用例更专注于业务的操作,从而提高测试用例的可维护性。
1. 认识 Page Object
当为 Web 页面编写测试时,需要操作该 Web 页面上的元素。直接操作这些元素,这样代码十分脆弱,因为 UI 会经常变动。
page 对象的一个基本经验法则是:凡是人能做的事,page 对象通过软件客户端都能做到。它提供一个易于编程的接口,并隐藏窗口中底层的部件。page 对象把在 GUI 控件上所有查询和操作数据的行为封装为方法。
一个好的经验法则是,即使改变具体的元素,page 对象的接口也不应当发生变化。
页面上有重要意义的元素可以独立为一个 page 对象。经验法则的目的是通过给页面建模,使其对应用程序的使用者变得有意义。
Page Object 是一种设计模式,在自动化测试开发中应遵循这种设计模式来编写代码。 Page Object 应该遵循以下原则进行开发:
- Page Object 应该易于使用。
- 有清晰的结构,如 PageObjects 对应页面对象,PageModules 对应页面内容。
- 只写测试内容,不写基础内容。
- 在可能的情况下防止样板代码。
- 不需要自己管理浏览器。
- 在运行时选择浏览器,而不是类级别。
- 不需要直接接触 Selenium。
2. 实现 Page Object
(1)Page Object 简单实例
当有以下测试代码 test_baidu.py ,两条测试用例中重复使用了元素的定位和操作,当元素定位变化后,那么这两条测试用例都需要修改。如果测试用例有几百条,UI 频繁变化,那么自动化测试用例维护成本较高。
...
def test_baidu_search_case1(self):
""" 搜索关键字:selenium """
self.driver.get(self.base_url)
self.driver.find_element_by_id('kw').send_keys('selenium')
self.driver.find_element_by_id('su').click()
sleep(2)
def test_baidu_search_case2(self):
""" 搜索关键字:page object """
self.driver.get(self.base_url)
self.driver.find_element_by_id('kw').send_keys('page object')
self.driver.find_element_by_id('su').click()
sleep(2)
...
Page Object 的设计思想上是把元素定位与元素操作进行分层,这样带的来最直接的好处就是当元素发生变化时,只需维护 page 层的元素定位,而不需要关心在哪些测试用例当中使用了这些元素。
创建 baidu_page.py 文件,代码如下:
class BaiduPage():
def __init__(self, driver):
self.driver = driver
def search_input(self, search_key):
self.driver.find_element_by_id('kw').send_keys(search_key)
def search_button(self):
self.driver.find_element_by_id('su').click()
修改 test_baidu.py,修改部分如下:
from baidu_page import BaiduPage
...
def test_baidu_search_case1(self):
""" 搜索关键字:selenium """
self.driver.get(self.base_url)
bd = BaiduPage(self.driver)
bd.search_input('selenium')
bd.search_button()
sleep(2)
def test_baidu_search_case2(self):
""" 搜索关键字:page object """
self.driver.get(self.base_url)
bd = BaiduPage(self.driver)
bd.search_input('page object')
bd.search_button()
sleep(2)
...
导入 BaiduPage 类,每个测试用例中为 BaiduPage 类传入驱动,用封装的方法设计测试用例,就能在测试用例中消除元素定位。
(2)改进Page Object 封装
上面例子演示了 Page Object 设计模式的基本原理,也带来了点问题,例如,需要写更多的代码了。为了使 Page 层的封装更加方便,我们做一些改进。
创建 base.py 文件,代码如下:
class BasePage:
"""
基础 Page 层,封装一些常用方法
"""
def __init__(self, driver):
self.driver = driver
def open(self, url=None):
if url is None:
self.driver.get(self.url)
else:
self.driver.get(url)
def by_id(self, id_):
return self.driver.find_element_by_id(id_)
def by_name(self, name):
return self.driver.find_element_by_name(name)
def by_class(self, class_name):
return self.driver.find_element_by_class(class_name)
def by_xpath(self, xpath):
return self.driver.find_element_by_xpath(xpath)
def by_css(self, css):
return self.driver.find_element_by_css_selector(css)
def get_title(self):
return self.driver.title
def get_text(self, xpath):
return self.by_xpath(xpath).text
def js(self, script):
self.driver.execute_script(script)
创建 BasePage 类作为所有 Page 类的基类,在 BasePage 类中封装一些自动化开发常用的方法。 open() 方法用于打开网页,url 参数默认为 None,则默认打开子类中定义的 url。(子类是后面的 BasePage 类) get_text()方法需要接收元素定位,这里默认为 XPath 定位。
修改 baidu_page.py 文件,代码如下:
from base import BasePage
class BaiduPage(BasePage):
"""百度 Page 层, 百度页面封装操作到的元素"""
url = "https://www.baidu.com"
def search_input(self, search_key):
self.by_id('kw').send_keys(search_key)
def search_button(self):
self.by_id('su').click()
修改 test_baidu.py 文件,代码如下:
import unittest
from time import sleep
from selenium import webdriver
from baidu_page import BaiduPage
class TestBaidu(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome()
def test_baidu_search_case1(self):
page = BaiduPage(self.driver)
page.open()
page.search_input('selenium')
page.search_button()
sleep(2)
self.assertEqual(page.get_title(), "selenium_百度搜索")
def test_baidu_search_case2(self):
page = BaiduPage(self.driver)
page.open()
page.search_input('page object')
page.search_button()
sleep(2)
self.assertEqual(page.get_title(), "page object_百度搜索")
@classmethod
def tearDownClass(cls):
cls.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity=2)
3. poium 测试库
poium 是一个基于 Selenium/appium 的 Page Object 测试库,最大的特点是简化了 Page 层元素的定义。 支持 pip 安装,pip install poium
(1)基本使用
使用 poium 重写 baidu_page.py,修改如下:
from poium import Page, Element
class BaiduPage(Page):
"""百度 Page 层,百度页面封装操作到的元素"""
search_input = Element(id_='kw')
search_button = Element(id_='su')
调用 Element 类定义元素定位,并赋值给变量,这里仅封装元素的定位,并返回元素对象,元素的具体操作仍然在测试用例中完成。符合 Page Object 的思想,将元素定位与元素操作分层。
修改 test_baidu.py ,代码如下:
import unittest
from time import sleep
from selenium import webdriver
from baidu_page import BaiduPage
class TestBaidu(unittest.TestCase):
@classmethod
def setUpClass(cls):
cls.driver = webdriver.Chrome()
def test_baidu_search_case1(self):
page = BaiduPage(self.driver)
page.get("https://www.baidu.com")
page.search_input = 'selenium'
page.search_button.click()
sleep(2)
self.assertEqual(page.get_title, "selenium_百度搜索")
def test_baidu_search_case2(self):
page = BaiduPage(self.driver)
page.get("https://www.baidu.com")
page.search_input = 'page object'
page.search_button.click()
sleep(2)
self.assertEqual(page.get_title, "page object_百度搜索")
@classmethod
def tearDownClass(cls):
cls.driver.quit()
if __name__ == '__main__':
unittest.main(verbosity=2)
(2)更多用法
a. 支持的定位方式 poium 支持 8 种定位方式。
from poium import Page, Element
class SomePage(Page):
elem_id = Element(id_='id')
elem_name = Element(name='name')
elem_class = Element(class_name='class')
elem_tag = Element(tag='input')
elem_link_text = Element(link_text='this_is_link')
elem_partial_link_text = Element(partial_link_text='is_link')
elem_xpath = Element(xpath='//*[@id="kk"]')
elem_css = Element(css='#id')
b. 设置元素超时时间 通过 timeout 参数可设置元素超时时间,默认为 10s。
from poium import Page, Element
class SomePage(Page):
search_input = Element(id_='kw', timeout=5)
search_button = Element(id_='su', timeout=30)
**c.**设置元素描述 元素非常多时,必须通过注释来增加可读性,可以使用 describe 参数。
from poium import Page, Element
class SomePage(Page):
"""
登录 Page 类
"""
username = Element(css='#loginAccount', describe='用户名')
password = Element(css='#loginPwd', describe='密码')
login_button = Element(css='#login_btn', describe='登录按钮')
user_info = Element(css='a.nav_user_name > span', describe='用户信息')
d. 定位一组元素 可使用 Elements 类。
from poium import Page, Elements
class SomePage(Page):
search_result = Elements(xpath='//div/h3/a')
poium 极大地简化了 Page 层的定义,它还提供了很多的 API,如 PageSelect类简化了下拉框的处理等。目前,poium 已经在Web 自动化项目中得到了很好的应用。
|