PO模式
Page Object Model
测试页面和测试脚本分离,即页面封装成类,供测试脚本进行调用。
优缺点
- 优点
1.提高测试用例的可读性;
2.减少了代码的重复;
3.提高测试用例的可维护性,特别是针对UI频繁变动的项目;
- 缺点
结构复杂: 基于流程做了模块化的拆分。
1、项目准备
需求
- 更多-移动网络-首选网络类型-点击2G
- 更多-移动网络-首选网络类型-点击3G
- 显示-搜索按钮-输入hello-点击返回
文件目录
PO模式
- scripts
- - test_settting.py
- pytest.ini
代码
test_setting.py
import time
from appium import webdriver
class TestSetting:
def setup(self):
# server 启动参数
desired_caps = {}
# 设备信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
desired_caps['deviceName'] = '192.168.56.101:5555'
# app的信息
desired_caps['appPackage'] = 'com.android.settings'
desired_caps['appActivity'] = '.Settings'
# 解决输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 声明我们的driver对象
self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
def test_mobile_network_2g(self):
self.driver.find_element_by_xpath("//*[contains(@text,'更多')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'移动网络')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'首选网络类型')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'2G')]").click()
def test_mobile_network_3g(self):
self.driver.find_element_by_xpath("//*[contains(@text,'更多')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'移动网络')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'首选网络类型')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'3G')]").click()
def test_mobile_display_input(self):
self.driver.find_element_by_xpath("//*[contains(@text,'显示')]").click()
self.driver.find_element_by_id("com.android.settings:id/search").click()
self.driver.find_element_by_id("android:id/search_src_text").send_keys("hello")
self.driver.find_element_by_class_name("android.widget.ImageButton").click()
def teardown(self):
self.driver.quit()
pytest.ini
[pytest]
# 添加行参数
addopts = -s --html=./report/report.html
# 文件搜索路径
testpaths = ./scripts
# 文件名称
python_files = test_*.py
# 类名称
python_classes = Test*
# 方法名称
python_functions = test_*
2、多文件区分测试用例
需求
- 使用多个文件来区分不同的测试页面
好处
- 修改不同的功能找对应的文件即可
步骤
- 在scripts下新建test_network.py文件
- 在scripts下新建test_dispaly.py文件
- 移动不同的功能代码到对应的文件
- 移除原有的test_setting.py文件
文件目录
PO模式
- scripts
- - test_network.py
- - test_dispaly.py
- pytest.ini
代码
test_network.py
from appium import webdriver
class TestNetwork:
def setup(self):
# server 启动参数
desired_caps = {}
# 设备信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
desired_caps['deviceName'] = '192.168.56.101:5555'
# app的信息
desired_caps['appPackage'] = 'com.android.settings'
desired_caps['appActivity'] = '.Settings'
# 解决输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 声明我们的driver对象
self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
def test_mobile_network_2g(self):
self.driver.find_element_by_xpath("//*[contains(@text,'更多')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'移动网络')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'首选网络类型')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'2G')]").click()
def test_mobile_network_3g(self):
self.driver.find_element_by_xpath("//*[contains(@text,'更多')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'移动网络')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'首选网络类型')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'3G')]").click()
def teardown(self):
self.driver.quit()
test_dispaly.py
from appium import webdriver
class TestDisplay:
def setup(self):
# server 启动参数
desired_caps = {}
# 设备信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
desired_caps['deviceName'] = '192.168.56.101:5555'
# app的信息
desired_caps['appPackage'] = 'com.android.settings'
desired_caps['appActivity'] = '.Settings'
# 解决输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 声明我们的driver对象
self.driver = webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
def test_mobile_display_input(self):
self.driver.find_element_by_xpath("//*[contains(@text,'显示')]").click()
self.driver.find_element_by_id("com.android.settings:id/search").click()
self.driver.find_element_by_id("android:id/search_src_text").send_keys("hello")
self.driver.find_element_by_class_name("android.widget.ImageButton").click()
def teardown(self):
self.driver.quit()
3、封装前置代码
需求
- 将前置代码进行封装
好处
- 前置代码只需要写一份
步骤
- 新建base文件夹
- 新建base_driver.py文件
- 新建函数init_driver
- 写入前置代码并返回
- 修改测试文件中的代码
文件目录
PO模式
- base
- - base_driver.py
- scripts
- - test_network.py
- - test_dispaly.py
- pytest.ini
代码
base_driver.py
from appium import webdriver
def init_driver():
# server 启动参数
desired_caps = {}
# 设备信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
desired_caps['deviceName'] = '192.168.56.101:5555'
# app的信息
desired_caps['appPackage'] = 'com.android.settings'
desired_caps['appActivity'] = '.Settings'
# 解决输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 声明我们的driver对象
return webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
test_network.py
from base.base_driver import init_driver
class TestNetwork:
def setup(self):
self.driver = init_driver()
def test_mobile_network_2g(self):
self.driver.find_element_by_xpath("//*[contains(@text,'更多')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'移动网络')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'首选网络类型')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'2G')]").click()
def test_mobile_network_3g(self):
self.driver.find_element_by_xpath("//*[contains(@text,'更多')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'移动网络')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'首选网络类型')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'3G')]").click()
def teardown(self):
self.driver.quit()
test_dispaly.py
from base.base_driver import init_driver
class TestDisplay:
def setup(self):
self.driver = init_driver()
def test_mobile_display_input(self):
self.driver.find_element_by_xpath("//*[contains(@text,'显示')]").click()
self.driver.find_element_by_id("com.android.settings:id/search").click()
self.driver.find_element_by_id("android:id/search_src_text").send_keys("hello")
self.driver.find_element_by_class_name("android.widget.ImageButton").click()
def teardown(self):
self.driver.quit()
4、分离测试脚本
需求
- 测试脚本只剩流程
- 其他的步骤放倒page中
好处
- 测试脚本只专注过程
- 过程改变只需要修改脚本
步骤
- 新建page文件夹
- 新建network_page.py文件
- 新建display_page.py文件
- init函数传入driver
- init进入需要测试的页面
- page中新建“小动作”函数
- 移动代码
- 修改测试文件中的代码
文件目录
PO模式
- base
- - base_driver.py
- page
- - network_page.py
- - display_page.py
- scripts
- - test_network.py
- - test_dispaly.py
- pytest.ini
代码
base_driver.py
from appium import webdriver
def init_driver():
# server 启动参数
desired_caps = {}
# 设备信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
desired_caps['deviceName'] = '192.168.56.101:5555'
# app的信息
desired_caps['appPackage'] = 'com.android.settings'
desired_caps['appActivity'] = '.Settings'
# 解决输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 声明我们的driver对象
return webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
network_page.py
class NetWorkPage:
def __init__(self, driver):
self.driver = driver
self.driver.find_element_by_xpath("//*[contains(@text,'更多')]").click()
self.driver.find_element_by_xpath("//*[contains(@text,'移动网络')]").click()
def click_first_network(self):
self.driver.find_element_by_xpath("//*[contains(@text,'首选网络类型')]").click()
def click_2g(self):
self.driver.find_element_by_xpath("//*[contains(@text,'2G')]").click()
def click_3g(self):
self.driver.find_element_by_xpath("//*[contains(@text,'3G')]").click()
display_page.py
class DisplayPage:
def __init__(self, driver):
self.driver = driver
self.driver.find_element_by_xpath("//*[contains(@text,'显示')]").click()
def click_search(self):
self.driver.find_element_by_id("com.android.settings:id/search").click()
def input_text(self, text):
self.driver.find_element_by_id("android:id/search_src_text").send_keys(text)
def click_back(self):
self.driver.find_element_by_class_name("android.widget.ImageButton").click()
test_network.py
import sys, os
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.network_page import NetWorkPage
class TestNetwork:
def setup(self):
self.driver = init_driver()
self.network_page = NetWorkPage(self.driver)
def test_mobile_network_2g(self):
self.network_page.click_first_network()
self.network_page.click_2g()
def test_mobile_network_3g(self):
self.network_page.click_first_network()
self.network_page.click_3g()
def teardown(self):
self.driver.quit()
test_dispaly.py
import sys, os
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.display_page import DisplayPage
class TestDisplay:
def setup(self):
self.driver = init_driver()
self.display_page = DisplayPage(self.driver)
def test_mobile_display_input(self):
self.display_page.click_search()
self.display_page.input_text("hello")
self.display_page.click_back()
def teardown(self):
self.driver.quit()
5、抽取找元素的特征
需求
- 将元素的特城放在函数之上
好处
- 若特征改了,流程不变,可以直接在上面修改
步骤
- 将find_element_by_xxx改为find_element
- 将方式和具体特征向上移动
文件目录
PO模式
- base
- - base_driver.py
- page
- - network_page.py
- - display_page.py
- scripts
- - test_network.py
- - test_dispaly.py
- pytest.ini
代码
base_driver.py
from appium import webdriver
def init_driver():
# server 启动参数
desired_caps = {}
# 设备信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
desired_caps['deviceName'] = '192.168.56.101:5555'
# app的信息
desired_caps['appPackage'] = 'com.android.settings'
desired_caps['appActivity'] = '.Settings'
# 解决输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 声明我们的driver对象
return webdriver.Remote('http://127.0.0.1:4723/wd/hub', desired_caps)
network_page.py
from selenium.webdriver.common.by import By
class NetWorkPage:
more_button = By.XPATH, "//*[contains(@text,'更多')]"
mobile_network_button = By.XPATH, "//*[contains(@text,'移动网络')]"
click_first_network_button = By.XPATH, "//*[contains(@text,'首选网络类型')]"
network_2g_button = By.XPATH, "//*[contains(@text,'2G')]"
network_3g_button = By.XPATH, "//*[contains(@text,'3G')]"
def __init__(self, driver):
self.driver = driver
self.driver.find_element(self.more_button).click()
self.driver.find_element(self.mobile_network_button).click()
def click_first_network(self):
self.driver.find_element(self.click_first_network_button).click()
def click_2g(self):
self.driver.find_element(self.network_2g_button).click()
def click_3g(self):
self.driver.find_element(self.network_3g_button).click()
display_page.py
from selenium.webdriver.common.by import By
class DisplayPage:
display_button = By.XPATH, "//*[contains(@text,'显示')]"
search_button = By.ID, "com.android.settings:id/search"
search_edit_text = By.ID, "android:id/search_src_text"
back_button = By.CLASS_NAME, "android.widget.ImageButton"
def __init__(self, driver):
self.driver = driver
self.driver.find_element(self.display_button).click()
def click_search(self):
self.driver.find_element(self.search_button).click()
def input_text(self, text):
self.driver.find_element(self.search_edit_text).send_keys(text)
def click_back(self):
self.driver.find_element(self.back_button).click()
test_network.py
import sys, os
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.network_page import NetWorkPage
class TestNetwork:
def setup(self):
self.driver = init_driver()
self.network_page = NetWorkPage(self.driver)
def test_mobile_network_2g(self):
self.network_page.click_first_network()
self.network_page.click_2g()
def test_mobile_network_3g(self):
self.network_page.click_first_network()
self.network_page.click_3g()
def teardown(self):
self.driver.quit()
test_dispaly.py
import sys, os
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.display_page import DisplayPage
class TestDisplay:
def setup(self):
self.driver = init_driver()
self.display_page = DisplayPage(self.driver)
def test_mobile_display_input(self):
self.display_page.click_search()
self.display_page.input_text("hello")
self.display_page.click_back()
def teardown(self):
self.driver.quit()
6、抽取action
需求
- 将动作进行封装
步骤
- 新建base_action.py文件
- 将click、send_keys抽取到文件中
文件目录
PO模式
- base
- - base_driver.py
- - base_action.py
- page
- - network_page.py
- - display_page.py
- scripts
- - test_network.py
- - test_dispaly.py
- pytest.ini
代码
base_action.py
class BaseAction:
def __init__(self, driver):
self.driver = driver
def click(self, loc):
self.driver.find_element(loc[0], loc[1]).click()
def input(self, loc, text):
self.driver.find_element(loc[0], loc[1]).send_keys(text)
network_page.py
import sys, os
sys.path.append(os.getcwd())
from selenium.webdriver.common.by import By
from base.base_action import BaseAction
class NetWorkPage(BaseAction):
more_button = By.XPATH, "//*[contains(@text,'更多')]"
mobile_network_button = By.XPATH, "//*[contains(@text,'移动网络')]"
click_first_network_button = By.XPATH, "//*[contains(@text,'首选网络类型')]"
network_2g_button = By.XPATH, "//*[contains(@text,'2G')]"
network_3g_button = By.XPATH, "//*[contains(@text,'3G')]"
def __init__(self, driver):
BaseAction.__init__(self, driver)
self.click(self.more_button)
self.click(self.mobile_network_button)
def click_first_network(self):
self.click(self.click_first_network_button)
def click_2g(self):
self.click(self.network_2g_button)
def click_3g(self):
self.click(self.network_3g_button)
display_page.py
import sys, os
sys.path.append(os.getcwd())
from selenium.webdriver.common.by import By
from base.base_action import BaseAction
class DisplayPage(BaseAction):
display_button = By.XPATH, "//*[contains(@text,'显示')]"
search_button = By.ID, "com.android.settings:id/search"
search_edit_text = By.ID, "android:id/search_src_text"
back_button = By.CLASS_NAME, "android.widget.ImageButton"
def __init__(self, driver):
BaseAction.__init__(self, driver)
self.click(self.display_button)
def click_search(self):
self.click(self.search_button)
def input_text(self, text):
self.input(self.search_edit_text, text)
def click_back(self):
self.click(self.back_button)
test_network.py
import sys, os
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.network_page import NetWorkPage
class TestNetwork:
def setup(self):
self.driver = init_driver()
self.network_page = NetWorkPage(self.driver)
def test_mobile_network_2g(self):
self.network_page.click_first_network()
self.network_page.click_2g()
def test_mobile_network_3g(self):
self.network_page.click_first_network()
self.network_page.click_3g()
def teardown(self):
self.driver.quit()
test_dispaly.py
import sys, os
import time
sys.path.append(os.getcwd())
from base.base_driver import init_driver
from page.display_page import DisplayPage
class TestDisplay:
def setup(self):
self.driver = init_driver()
self.display_page = DisplayPage(self.driver)
def test_mobile_display_input(self):
self.display_page.click_search()
self.display_page.input_text("hello")
self.display_page.click_back()
pass
def teardown(self):
self.driver.quit()
7、增加WebDriverWait
需求
- 找控件使用WebDriverWait
好处
- 防止出现cpu卡的时候找不到
步骤
- 自己新建find方法
- 在系统的基础上增加WebDriverWait
文件目录
PO模式
- base
- - base_driver.py
- - base_action.py
- page
- - network_page.py
- - display_page.py
- scripts
- - test_network.py
- - test_dispaly.py
- pytest.ini
代码
base_action.py
from selenium.webdriver.support.wait import WebDriverWait
class BaseAction:
def __init__(self, driver):
self.driver = driver
def find_element(self, loc, time=10, poll=1):
return WebDriverWait(self.driver, time, poll).until(lambda x: x.find_element(loc[0], loc[1]))
# return self.driver.find_element(by, value)
def find_elements(self, loc, time=10, poll=1):
return WebDriverWait(self.driver, time, poll).until(lambda x: x.find_elements(loc[0], loc[1]))
# return self.driver.find_elements(by, value)
def click(self, loc):
self.find_element(loc).click()
def input(self, loc, text):
self.find_element(loc).send_keys(text)
8、xpath特殊处理
需求
- //*[contains(@,'')]是相同的可以进行抽取
好处
- 少写代码
步骤
- 获取loc后,进行拆分
- 字符串拼接
文件目录
PO模式
- base
- - base_driver.py
- - base_action.py
- page
- - network_page.py
- - display_page.py
- scripts
- - test_network.py
- - test_dispaly.py
- pytest.ini
代码
base_action.py
from selenium.webdriver.common.by import By
from selenium.webdriver.support.wait import WebDriverWait
class BaseAction:
def __init__(self, driver):
self.driver = driver
def find_element(self, loc, time=10, poll=0.5):
loc_by, loc_value = loc
if loc_by == By.XPATH:
xpath_key, xpath_value = loc_value
loc_value = "//*[contains(@%s, '%s')]" % (xpath_key, xpath_value)
return WebDriverWait(self.driver, time, poll).until(lambda x: x.find_element(loc_by, loc_value))
def find_elements(self, loc, time=10, poll=0.5):
loc_by, loc_value = loc
if loc_by == By.XPATH:
xpath_key, xpath_value = loc_value
loc_value = "//*[contains(@%s, '%s')]" % (xpath_key, xpath_value)
return WebDriverWait(self.driver, time, poll).until(lambda x: x.find_elements(loc_by, loc_value))
def click(self, loc):
self.find_element(loc).click()
def input(self, loc, text):
self.find_element(loc).send_keys(text)