PO模式
Page Object Model
测试页面和测试脚本分离,即页面封装成类,供测试脚本进行调用。
也就是说,脚本只需要负责关心流程,具体操作某个元素的步骤封装在页面对应的类的方法中
优缺点
优点
1.提高测试用例的可读性;
2.减少了代码的重复;
3.提高测试用例的可维护性,特别是针对UI频繁变动的项目;
缺点
结构复杂: 基于流程做了模块化的拆分。
项目需求
- 更多-移动网络-首选网络类型-点击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_*
多文件区分测试用例
需求
- 使用多个文件来区分不同的测试页面
好处
-修改不同的功能找对应的文件即可
步骤
1. 在scripts下新建test_network.py文件
2. 在scripts下新建test_dispaly.py文件
3. 移动不同的功能代码到对应的文件
4. 移除原有的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_display.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()
封装前置代码
需求
- 将前置代码进行封装
好处
- 前置代码只需要写一份
步骤
1. 新建base文件夹
2. 新建base_driver.py文件
3. 新建函数init_driver
4. 写入前置代码并返回
5. 修改测试文件中的代码
文件目录
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_display.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()
分离测试脚本
需求
好处
步骤
1. 新建page文件夹
2. 新建network_page.py文件
3. 新建display_page.py文件
4. 调用init函数传入driver
5. init进入需要测试的页面
6. page中新建“小动作”函数
7. 移动代码
8. 修改测试文件中的代码
文件目录
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_display.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()
抽取查找元素的特征
需求
- 将元素的特征放在函数之上
好处
- 若特征改了,流程不变,可以直接在上面修改
步骤
1. 将find_element_by_xxx改为find_element
2. 将方式和具体特征向上移动
一切目的都是为了代码简单易于维护。
文件目录
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_display.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()
抽取action
需求
- 将动作进行封装
步骤
1. 新建base_action.py文件
2. 将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_display.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()
def teardown(self):
self.driver.quit()
增加WebDriverWait
需求
- 找控件使用WebDriverWait
好处
- 防止出现cpu卡的时候找不到
步骤
1. 自己新建find方法
2. 在系统的基础上增加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)