因为要迁移 git,整理部门的项目时,看到了快有 4 年的 web 自动化项目,是个 webdriver+pytest 的。就顺便看看,还是发现不少有点意思的地方,算是个 webdriver+pytest 整合基础分享吧
大致结构如下
├── /Pictures # 用于上传图片
├── /baseTools # 公共工具
| ├── /browserTool # 浏览器启动及管理工具
| | ├── /browserdrivers # 浏览器的driver
| | ├── browserManager.py # 核心模块,浏览器启动、操作工具
| | └── winGuiAuto.py # windowsUI的自动化操作模块
| ├── /dbTool # 数据库操作工具
| ├── /hostTool # hosts操作工具
| └── /reportTool # 报表生成工具
├── /businessPrj # 业务项目(主模块)
| └── /XXXX # XXXX业务
| ├── /support # 页面元素模型及公共方法
| | ├── /common # 公共组件类的操作
| | └── /... # 各个业务模块页面元素类
| ├── /testCases # 页面元素模型及公共方法
| | └── /case01_XX... # 各个业务模块测试模块
| | | └── test_01_XX.py...# 各个测试脚本
| ├── confdata.py # 该业务项目下的测试数据
| └── conftest.py # 该业务项目下pytest的配置文件(公共账号登录、redis、数据库客户端、全局参数)
├── confrun.py # 运行脚本配置文件
├── conftest.py # pytest的全局配置文件(driver的启动关闭及其他公共工具相关处理)
├── main.py # 项目的运行入口
└── README.md
比较简单而常见的结构,里面整合了浏览器管理、报告生成等等一些列工具及一系列处理
可以指定浏览器、代理、测试版本的环境及开发版本、是否冒烟、运行的业务模块
启动前还做了些残余进程的处理和临时文件的清理操作
@pytest.fixture(scope='session',autouse=True)
def xBM(request):
"""
session级别:整个程序运行中,只运行一次
:param request:
:return:
"""
global bm
if bm is None or bm.driver is None:
print("\n----打开webdriver------")
browserType=request.config.getoption("--browserType")
IpAndPort = request.config.getoption("--proxyIpPort")
ipPortSpl = IpAndPort.split(":")
if len(ipPortSpl) != 2 or not ipPortSpl[1].isdigit():
ipPortSpl = ["",0]
chromeOptions=['--demo-type','--ignore-certificate-errors']
firefoxProfiles=[
("network.http.phishy-userpass-length", 255),
("webdriver_assume_untrusted_issuer",False),
]
bm=BrowserManager(browserType,ipPortSpl[0],int(ipPortSpl[1]),chromeOptions=chromeOptions,firefoxProfiles=firefoxProfiles)
bm.maxSizeWin()
def end():
bm.quit()
print("\n----关闭webdriver------")
request.addfinalizer(end) # 最终关闭dirver
return bm
利用了 pytest.fixture 的 scope='session',这样整个自动化只会自动的启动 1 次 webdriver,并返回了封装 webdriver 的对象 xBM 这个 fixture,并在运行完成最后关闭 webdriver,形成一个闭环。
另外还用同样方式的定义了一个 redis 的客户端 fixture(不细说了)
@pytest.fixture(scope='session',autouse=True)
def xGlobalArgs():
return {}
这个绝对是 fixture 的一个小技巧,自动化运行中的测试方法脚本之间传递参数就靠它了,比如一个测试方法获取了页面的 id,可以存在里面 xGlobalArgs["id"]="1231", 另 1 个测试方法就可以直接使用 xGlobalArgs["id"]
先是定义了一个元素选择器类,封装 webdriver 的 browserManager 中的元素操作方法都是基于这个元素选择器类来做的(可能为了统一元素定位方式,为 UI 做准备?)
class ElementSelector():
def __init__(self,by:str,value:str):
self.by=by
self.value=value
页面元素库类,里面含元素及通用操作
from baseTools.browserTool.browerManager import BrowserManager,ElementSelector,By
class LoginPage(object):
BaseUrl='https://login.xxxx.com/'
#----元素---
EmailAddress_input=ElementSelector(By.NAME,"loginName")
Password_input=ElementSelector(By.NAME,"password")
LoginIn_button=ElementSelector(By.ID,"submit")
ErrorTips_info=ElementSelector(By.XPATH,"//ul[contains(@class,'error-tips')]")
#----方法---
def login(self,bm:BrowserManager,email:str,password:str)->bool: # 登录方法
pass
def isLogon(self,bm:BrowserManager)->bool: # 检查是否已经登录
pass
def logOut(self,bm:BrowserManager,redirectUrl=""): # 退出登录
pass
测试类直接继承了 PO 类
这样有个好处是测试方法中可以直接使用元素
@pytest.mark.parametrize 使用了数据驱动方式
@allure.feature('xxx'),@allure.description('xxx'),@allure.story('xxx'),@allure.title('xxx') 可以描述测试方法集、方法名,可以在 allure 报告中友好呈现
with allure.step("xxx") 可以定义步骤,也可以在 allure 报告中友好呈现
@pytest.fixture(scope='function')
def xLoginAsBuyer(xBM:BrowserManager):
"""
:param xBM:
:return: 0 失败,1 成功,2 密码错误
"""
lp=LoginPage()
if lp.isLogon(xBM):
lp.logOut(xBM)
xBM.driver.get(lp.BaseUrl)
rst,err=lp.baselogin(xBM,confdata.buyer[0],confdata.buyer[1])
errMsg="[%s,%s]:%s"%(confdata.buyer[0],confdata.buyer[1],str(err)) if err else None
ignoreGuide(xBM) # 利用修改cookie及redis来清除掉各种向导、弹框
# 前置处理
yield rst==1,errMsg
# 后置处理
lp.logOut(xBM) # 退出登录
这种 fixture 需要在 test 方法的参数中,并在方法中使用才会调用(非自动的),利用 yield 分开前置处理后后置处理:
这里前置处理就是测试前登录一个公共账号返回结果,后置处理是当这个 test 结束后,自动调用退出登录方法
@pytest.fixture(scope='function')
def xOraCli():
"""
oracle客户端
:return:
"""
dbClient=OracleClient('192.168.1.10',3306,'xxxx','xxxx','xxxx')
yield dbClient
dbClient.close()
这个也是同样的,这个 fixture 返回了一个 db 客户端,放在 test 方法的参数中,可以在测试方法使用这个 db 客户端进行相关操作,test 结束后自动关闭这个客户端。
[就这些]