1.PO基本介绍
测试PO模式(Page Object Model)
测试页面和测试脚本分离,即页面封装成类,供测试脚本进行调用。
优点
1.提高测试用例的可读性;
2.减少了代码的重复;
3.提高测试用例的可维护性,特别是针对UI频繁变动的项目;
缺点
结构复杂: 基于流程做了模块化的拆分。
PO:page objece,分层机制,让不同层去做不同类型的事情,让代码结构清晰,增加复用性。
主要有以下几种分层方式:
1)两层: 对象逻辑层+业务数据层
2)三层:对象库层+逻辑层+业务数据层
3)四层:对象库层+逻辑层+业务层+数据层
优势:
1)效率高 :同理,PO模式的逻辑层方法有具体定义,情况和元素发生变化一样 修改逻辑层,业务层不变。这样看来结构简单清晰,舒服更符合人类习惯, 普通方式就是继续堆case。
2) 复用多收益大: 同样这里如果逻辑复用越多,PO模式收益越大,因为对于PO模式来说都只需要修改一个地方多处受益。
2.Appium方法二次封装
为什么二次封装方法?
减少过多使用类似方法,更方便管理定位元素,尽量将函数方法简洁化、统一化。
系统已提供定位方法
driver.find_element_by_id()
driver.find_element_by_class_name()
driver.find_element_by_xpath()
driver.find_elements_by_id()
driver.find_elements_by_class_name()
driver.find_elements_by_xpath()
实际以上定位方法封装以下方法:
find_element(by=By.XX, value=None)
find_elements(by=By.XX, value=None)
# by:定位类型(By.ID,By.CLASS_NAME,By.XPATH)
# value:定位元素的属性值
封装思路
为什么做?
使用统一的方法来完成元素的定位.
怎么去做?
借用:
find_element(by=By.XX, value=None)
find_elements(by=By.XX, value=None)
封装实现(一)
# 点击元素
def click_element(self, location,location_vlaue):
# location: 定位类型
# location_vlaue: 定位元素属性值
self.find_element(location,location_vlaue).click()
# 输入内容
def input_element(self, location,location_vlaue,text):
# text: 需要输入的值
self.find_element(location,location_vlaue).send_keys(text)
发现问题:
没有简化参数的传递,仅仅将点击和输入做了封装.
封装实现(二)
# 点击元素
def click_element(self, location):
# location: 定位类型&定位属性值,
⚠️ location类型为元祖 格式(By.ID,value),(By.CLASS_NAME,value),(By.XPATH,value)
self.find_element(*location).click()
# *location将元祖类型的值进行了一次解包操作,来满足find_element的参数传递要求
# 输入内容
def input_element(self, location,text):
# text: 需要输入的值
self.fm = self.find_element(*location)
self.fm.clear() # 需要先清空输入框,防止有默认内容
self.fm.send_keys(text)
Demo示例
业务场景:
1.进入设置
2.点击搜索按钮
3.输入123
4.点击搜索框返回按钮
封装代码:Base.py
from selenium.webdriver.support.wait import WebDriverWait
class Base(object):
def __init__(self,driver):
self.driver = driver
def find_element(self,loc,timeout=10):
# 封装称为智能等待方法
# loc:类型为元祖,格式(By.ID,value),(By.CLASS_NAME,value),(By.XPATH,value)
# timeout:搜索超时时间
return WebDriverWait(self.driver, timeout).until(lambda x: x.find_element(*loc))
def click_element(self,loc):
# 封装点击操作
self.find_element(loc).click()
def input_text(self,loc,text):
# 封装输入操作
self.fm = self.find_element(loc)
self.fm.clear() # 需要先清空输入框,防止有默认内容
self.fm.send_keys(text)
测试代码: test.py
from appium import webdriver
from selenium.webdriver.common.by import By
# 导入已封装的类 Base.py
from Base import Base
class Test_Base:
def __init__(self):
desired_caps = {}
# 手机 系统信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
# 设备号
desired_caps['deviceName'] = '192.168.56.101:5555'
# 包名
desired_caps['appPackage'] = 'com.android.settings'
# 启动名
desired_caps['appActivity'] = '.Settings'
# 允许输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 手机驱动对象
self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
def test(self):
# 搜索按钮
search_button = (By.ID,"com.android.settings:id/search")
# 搜索输入框
search_text = (By.ID,"android:id/search_src_text")
# 搜索框返回按钮
search_return_button = (By.CLASS_NAME,"android.widget.ImageButton")
# 实例化二次封装的Base类
base_object = Base(self.driver)
# 点击搜索输入框
base_object.click_element(search_button)
# 搜索框内输入123
base_object.input_text(search_text,123)
# 点击搜索框返回按钮
base_object.click_element(search_return_button)
# 退出driver对象
self.driver.quit()
if __name__ == "__main__":
Test_Base().test()
3.页面元素操作封装(一)
将页面元素定位和元素操作封装在一个类文件中.
封装步骤
确定好要封装的页面操作
业务场景:
1.进入设置
2.点击搜索按钮
3.输入123
4.点击搜索框返回按钮
需要用到哪些元素和定位方法
# 搜索按钮
search_button = (By.ID, "com.android.settings:id/search")
# 搜索输入框
search_text = (By.ID, "android:id/search_src_text")
# 搜索框返回按钮
search_return_button = (By.CLASS_NAME,"android.widget.ImageButton")
依赖的定位方法
使用二次封装的方法,Base.py文件
页面封装
编写搜索操作类:search_page.py
from selenium.webdriver.common.by import By
# 导入基础定位封装包
from Base import Base
class Search_Page:
def __init__(self,driver):
# 传如driver对象
self.driver = driver
# 实例化二次封装的类,用到封装好的操作函数
self.base_object = Base(self.driver)
# 搜索按钮
self.search_button = (By.ID, "com.android.settings:id/search")
# 搜索输入框
self.search_text = (By.ID, "android:id/search_src_text")
# 搜索框返回按钮
self.search_return_button = (By.CLASS_NAME,"android.widget.ImageButton")
def input_search_text(self,text):
# text: 需要输入的内容
# 封装搜索按钮的输入操作
# 点击设置中搜索按钮
self.base_object.click_element(self.search_button)
# 在搜索输入框内输入123
self.base_object.input_text(self.search_text,text)
# 点击搜索框返回按钮
self.base_object.click_element(self.search_return_button)
编写测试文件: test.py
from appium import webdriver
# 导入封装好的页面类
from search_page import Search_Page
class Test_Base:
def __init__(self):
desired_caps = {}
# 手机 系统信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
# 设备号
desired_caps['deviceName'] = '192.168.56.101:5555'
# 包名
desired_caps['appPackage'] = 'com.android.settings'
# 启动名
desired_caps['appActivity'] = '.Settings'
# 允许输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 手机驱动对象
self.driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
def test(self):
# 实例化页面封装类
sp = Search_Page(self.driver)
# 调用操作类
sp.input_search_text(123)
# 退出driver对象
self.driver.quit()
if __name__ == "__main__":
Test_Base().test()
问题
1.若我要完成Base基础类的导入,需要在每个页面的封装类内实例化Base类,
并需要实例化对象引用Base类内部方法。
即:
self.driver = driver
self.base_object = Base(self.driver)
2.若我们有多个测试函数文件,需要在每个文件中都声明一次driver,
重复代码太多,并且测试类内部冗余代码过多。
4.页面元素操作封装(二)
通过类继承的方式完成页面的封装
页面封装
业务场景:
1.进入设置
2.点击搜索按钮
3.输入123
4.点击搜索框返回按钮
页面封装类:search_page.py
from selenium.webdriver.common.by import By
# 导入基础定位封装包
from Base import Base
class Search_Page(Base):
def __init__(self,driver):
Base.__init__(self,driver) # 父类初始化方法
# 搜索按钮
self.search_button = (By.ID, "com.android.settings:id/search")
# 搜索输入框
self.search_text = (By.ID, "android:id/search_src_text")
# 搜索框返回按钮
self.search_return_button = (By.CLASS_NAME,"android.widget.ImageButton")
def input_search_text(self,text):
# text: 需要输入的内容
# 封装搜索按钮的输入操作
# 点击设置中搜索按钮
self.click_element(self.search_button) # 子类继承父类的所有方法
# 在搜索输入框内输入
self.input_text(self.search_text,text)
# 点击搜索框返回按钮
self.click_element(self.search_return_button)
独立手机驱动对象方法
统一管理手机驱动对象的初始化
新建手机驱动对象文件:Init_Driver.py
代码:
from appium import webdriver
def init_driver():
# 服务端启动参数
desired_caps = {}
# 手机 系统信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
# 设备号
desired_caps['deviceName'] = '192.168.56.101:5555'
# 包名
desired_caps['appPackage'] = 'com.android.settings'
# 启动名
desired_caps['appActivity'] = '.Settings'
# 允许输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 手机驱动对象
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
return driver # 返回driver对象
测试脚本编写
代码实现: test.py
# 导入封装好的页面类
from cup.search_page import Search_Page
# 导入独立的手机驱动对象
from cup.Init_Driver import init_driver
class Test_Base:
def __init__(self):
self.driver = init_driver()
def test(self):
# 示例化页面封装类
sp = Search_Page(self.driver)
# 调用操作类
sp.input_search_text(123)
# 退出driver对象
self.driver.quit()
if __name__ == "__main__":
Test_Base().test()
5.PO模式项目管理
目录结构
清晰的目录结构,方便代码模块化管理和他人解读。
常用目录结构:
App_Project # 项目名称
- Basic # 存储基础设施类
- __init__.py # 空文件
- Init_Driver.py # 手机驱动对象初始化
- Base.py # 方法的二次封装
- read_data.py #数据解析读取方法
- Page # 存储封装页面文件
- __init__.py # 存储页面元素
- search_page.py # 封装页面元素的操作方法
- Data # 存储数据文件
- search_data.yaml(也可以是其他文件比如txt,excel,json,数据库等)
- Test # 存储测试脚本目录
- test_search.py # 测试搜索文件
Basic目录文件代码
1.Basic/Init_Driver.py
from appium import webdriver
def init_driver():
# 服务端启动参数
desired_caps = {}
# 手机 系统信息
desired_caps['platformName'] = 'Android'
desired_caps['platformVersion'] = '5.1'
# 设备号
desired_caps['deviceName'] = '192.168.56.101:5555'
# 包名
desired_caps['appPackage'] = 'com.android.settings'
# 启动名
desired_caps['appActivity'] = '.Settings'
# 允许输入中文
desired_caps['unicodeKeyboard'] = True
desired_caps['resetKeyboard'] = True
# 手机驱动对象
driver = webdriver.Remote("http://127.0.0.1:4723/wd/hub", desired_caps)
return driver
2.Basic/Base.py
from selenium.webdriver.support.wait import WebDriverWait
class Base(object):
def __init__(self,driver):
self.driver = driver
def find_element(self,loc,timeout=10):
'''
二次封装find_element方法,增加了显示等待和简化参数传递
'''
# 原生方法:find_element(by=By.ID, value=None),需要传递两个值
# *loc 将传入(By.XX, "value")解包为两个单独的值,满足find_element方法的参数传递
return WebDriverWait(self.driver, timeout).until(lambda x: x.find_element(*loc))
def click_element(self,loc):
'''
封装点击操作函数
'''
self.find_element(loc).click()
def input_text(self,loc,text):
'''
封装输入操作函数
'''
self.fm = self.find_element(loc)
self.fm.clear() # 需要先清空输入框,防止有默认内容
self.fm.send_keys(text)
Page目录文件代码
1.Page/__init__.py
from selenium.webdriver.common.by import By # selenium原生定位策略集
'''
设置页面
'''
# 搜索按钮
search_button = (By.ID, "com.android.settings:id/search")
# 搜索输入框
search_text = (By.ID, "android:id/search_src_text")
# 搜索框返回按钮
search_return_button = (By.CLASS_NAME,"android.widget.ImageButton")
2.Page/search_page.py
# 导入基础定位封装包
from Basic.Base import Base
import Page
class Search_Page(Base):
def __init__(self,driver):
Base.__init__(self,driver) # Base类的初始化方法
def input_search_text(self,text):
'''
封装搜索按钮的输入操作
'''
# 点击设置中搜索按钮
self.click_element(Page.search_button) # 传入的__init__.py文件声明的search_button变量
# 在搜索输入框内输入
self.input_text(Page.search_text,text) # 传入的__init__.py文件声明的search_text变量
# 点击搜索框返回按钮
self.click_element(Page.search_return_button) # 传入的__init__.py文件声明的search_return_button变量
Test目录文件代码
1.test_search.py
# 导入封装好的页面类
from Page.search_page import Search_Page
# 导入独立的手机驱动对象
from Basic.Init_Driver import init_driver
import time
class Test_Base:
def __init__(self):
self.driver = init_driver()
def test(self):
# 示例化页面封装类
sp = Search_Page(self.driver)
# 调用操作类
sp.input_search_text(123)
# 退出driver对象
self.driver.quit()
if __name__ == "__main__":
Test_Base().test()