Selenium 部门的老 web 自动化框架的一些解读

JoyMao · 2022年04月19日 · 最后由 Jay_ 回复于 2022年07月19日 · 3504 次阅读

前言

因为要迁移 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

比较简单而常见的结构,里面整合了浏览器管理、报告生成等等一些列工具及一系列处理

一些特点罗列

main.py

【1】启动时读取了一些外部参数,这个应该是和持续集成工具整合时用的


可以指定浏览器、代理、测试版本的环境及开发版本、是否冒烟、运行的业务模块

启动前还做了些残余进程的处理和临时文件的清理操作

【2】全局 contest.py 中用了不少 pytest 的特性,值得说说

全局的浏览器(webdriver)启动及关闭
@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"]

【3】用了一个简单的 PO 模式

先是定义了一个元素选择器类,封装 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 报告中友好呈现

【4】模块下的 contest.py 也有一些 pytest 的特性操作

公共业务方法定义
@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 结束后自动关闭这个客户端。

[就这些]

共收到 1 条回复 时间 点赞

说实话 这种代码 我都没看下去的兴趣. 你还看了这么久 也真的是佩服你

需要 登录 后方可回复, 如果你还没有账号请点击这里 注册