页面对象模型
Whisper Lv4

简介

Page Object Model:页面对象模型,是Selenium中的一种测试设计模式,一个页面对象代表用户界面交互测试的一个区域。
使用page object来实现:测试、逻辑、数据、驱动相互分离。

使用页面对象模式的好处:

  1. 创建可重用的代码,可以跨多个测试用例共享
  2. 减少重复代码的数量
  3. 如果用户界面更改,只需要修改一个地方

基类

每个页面类继承。该类封装了下级page类的基本功能和公共的功能。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from selenium.webdriver.support.ui import WebDriverWait


class BasePageElement(object):
"""Base page class that is initialized on every page object class."""

def __set__(self, obj, value):
"""Sets the text to the value supplied"""
driver = obj.driver
WebDriverWait(driver, 100).until(
lambda driver: driver.find_element_by_name(self.locator))
driver.find_element_by_name(self.locator).send_keys(value)

def __get__(self, obj, owner):
"""Gets the text of the specified object"""
driver = obj.driver
WebDriverWait(driver, 100).until(
lambda driver: driver.find_element_by_name(self.locator))
element = driver.find_element_by_name(self.locator)
return element.get_attribute("value")

各页面对象

页面对象模式将为每个web页面创建一个对象。封装一些各页面的方法。

page.py如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
from element import BasePageElement
from locators import MainPageLocators

class SearchTextElement(BasePageElement):
"""这个类从指定的定位器中获得搜索文本"""

#The locator for search box where search string is entered
locator = 'q'


class BasePage(object):
"""Base class to initialize the base page that will be called from all pages"""

def __init__(self, driver):
"""该driver是测试用例使用时传过来的"""
self.driver = driver


class MainPage(BasePage):
"""Home page action methods come here. I.e. Python.org"""

#Declares a variable that will contain the retrieved text
search_text_element = SearchTextElement()

def is_title_matches(self):
"""验证"Python"字符串在title中"""
return "Python" in self.driver.title

def click_go_button(self):
"""触发搜索"""
element = self.driver.find_element(*MainPageLocators.GO_BUTTON)
element.click()


class SearchResultsPage(BasePage):
"""Search results page action methods come here"""

def is_results_found(self):
# Probably should search for this text in the specific page
# element, but as for now it works fine
return "No results found." not in self.driver.page_source

测试基础类

封装setup 和 teardown的类。这里用appium的例子。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

class BaseTest(unittest.TestCase):
def setUp(self):
path = lambda p: os.path.abspath(
os.path.join(os.path.dirname(__file__), p)
)

desired_caps = {
"platformName": "Android",
"platformVersion": "5.0.1",
"deviceName": "MX5",
"appPackage": "net.xxx.debug",
"appActivity": "net.xxx.MainActivity",
"unicodeKeyboard": "True",
"resetKeyboard": "True",
"app": path("./apps/debug.10.apk")
}


self.driver = webdriver.Remote('http://localhost:4723/wd/hub', desired_caps)


def tearDown(self):
self.driver.quit()

测试代码

具体的测试用例写在这里。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import unittest
from selenium import webdriver
import page

class PythonOrgSearch(BaseTest):
def setUp(self):
self.driver = webdriver.Firefox()
self.driver.get("http://www.python.org")

def test_search_in_python_org(self):
"""
验证查找的元素存在
"""

#加载main page.这里使用Python.org.
main_page = page.MainPage(self.driver)
#检验“python”在title中
assert main_page.is_title_matches(), "python.org title doesn't match."
#设置搜索模式 "pycon"
main_page.search_text_element = "pycon"
main_page.click_go_button()
search_results_page = page.SearchResultsPage(self.driver)
#验证搜索结果页不为空
assert search_results_page.is_results_found(), "No results found."

def tearDown(self):
self.driver.close()

if __name__ == "__main__":
unittest.main()

定位器

定位器将定位的字符串和使用它们的地方分隔开来,将来开发修改了定位的方式和字符串只用来改该文件。
locators.py如下:

1
2
3
4
5
6
7
8
9
from selenium.webdriver.common.by import By

class MainPageLocators(object):
"""主页的定位器们"""
GO_BUTTON = (By.ID, 'submit')

class SearchResultsPageLocators(object):
"""搜索结果页定位器们"""
pass

要将全部定位器放在同一个module中,应该定位器中也有很多公用的部分。

这里使用类,方便refactor,多个页面有相同的class或者是xpath的时候,是放在公用的定位器中还是单独的页面定位器中呢?

如果页面时公用的部分,如loading,title等则放在公用的定位器中,否则放在单独的页面定位器中。这就要求对前端的设计有一些大概的了解,如果没有把握的话最好放在单独的页面定位器中。

菜单的定位器集中放在common.py中,不放在每个页面中,菜单不属于各个页面。

添加注释

会显示在报告中

1
2
3
4
class AddAccount(Login):
"""添加账号"""
def setUp(self):
super(AddAccount, self).setUp()

写在class下面,不要写在setup下面。

添加类注释

1
2
3
4
5
6
7

## 添加测试用例注释
​```python
def test_search_in_python_org(self):
"""
验证查找的元素存在
"""