Airtest自定义启动器支持批量运行脚本,并兼容在AirtestIDE中使用

news2024/11/16 13:33:59

小编注:上期详细讲了Airtest启动器的原理,以及在最后给出了2个实现方案。本次是第2个方案的另一个实现案例,供大家学习参考。

Python v3.7.0 / Airtest: 1.1.1 / PocoUI: 1.0.78

自定义的启动器主要实现了以下功能:

将一些公共参数和方法添加到全局变量中,在各业务脚本中无需声明,可直接使用,如语句超时时间 TIMEOUT,就在此进行统一设置;

设置Airtest全局属性值,对所有脚本生效,如下所示:

# 图像识别精确度阈值 [0,1]
 ST.THRESHOLD = 0.80
# assert语句里图像识别时使用的高要求阈值 [0,1]
 ST.THRESHOLD_STRICT = 0.85
# 每一步操作后等待多长时间再进行下一步操作
 ST.OPDELAY = 1
# 图像查找超时时间,默认为20s
 ST.FIND_TIMEOUT = 10
# 修改图像识别算法顺序,只要成功匹配任意一个符合设定阙值的结果,程序就会认为识别成功
 ST.CVSTRATEGY = ["tpl", "sift", "brisk"]

在正式脚本运行前后,添加子脚本的运行,使得运行各个业务脚本时,初始页面都将为小程序首页,不需要再添加额外的环境判断代码;

setup.air:在每个脚本运行前,都会首先运行此脚本。此脚本会判断当前页面是否位于小程序首页,若不是,则会重启微信,进入小程序首页;

teardown.air:在每个脚本运行结束后,都会运行此脚本。此脚本会点击页面顶部的HOME图标,返回到小程序首页。若成功返回到小程序首页,则会尝试关闭首页遮罩广告(注意:遮罩广告样式不能改变),若未能成功返回首页,则会重启微信,再进入小程序首页。

通过配置config.csv中的内容,实现批量运行脚本;

在运行脚本时,会先迭代查找suite/目录下所有以.air结尾的目录,自动忽略setup.air和teardown.air,然后读取config.csv中所配置的 Label 为 "Y"的脚本名称,二者做交集运算,其结果作为本次实际要运行的脚本集合。

批量运行脚本结束后,支持生成聚合报告,与Jenkins进行持续集成时,此报告会作为附件进行发送。点击聚合报告中模块名称,会跳转到Airtest Project自带的测试报告页面。

图片

图片

Airtest Project自带的测试报告中,引用了很多静态资源,如js/css文件、页面截图等,这里通过使用Nginx来搭建静态资源服务器,使测试报告可以正常加载。

兼容在AirtestIDE中使用自定义启动器

持续集成时,必然要通过命令行来运行业务脚本,需要手动传入一些参数如设备号,脚本名等,而使用AirtesIDE运行脚本时,这些参数会自动传入。源码中使用argparse的方法来解析命令行传入的参数,为了兼容在AirtestIDE中使用自定义的启动器,此处不能使用和源码相同的方法来解析命令行参数,否则AirtestIDE中会无法识别参数,而导致报错;

import sys

if len(sys.argv) == 1:  # 如果没有传入任何命令行参数,则批量执行脚本
    main()
    quit()
else:  # 在AirtestIDE中单个运行脚本时,会传入script、log、device等参数,则调用以下原始方法;
    ap = runner_parser()
    args = ap.parse_args()
    run_script(args, McCustomAirtestCase)

直接贴项目原始代码,注释已经写得很详细了。

#!/usr/bin/env python
# -*- coding:utf8 -*-
"""
@File  : mc_launcher.py
@Author: Rethink
@Date  : 2019/12/20 10:49
@Desc  :
"""
import csv
import datetime
import os
import shutil
import sys
import time
from argparse import *
from pathlib import Path
from typing import List

import airtest.report.report as report
import jinja2
from airtest.cli.parser import runner_parser
from airtest.cli.runner import run_script
from airtest.core.settings import Settings as ST

# 若脚本在IDE中运行,IDE会自动帮忙加载AirtestCase;若用命令行运行脚本,则需要导入 AirtestCase
if not globals().get("AirtestCase"):
    from airtest.cli.runner import AirtestCase

AIRTEST_EXPORT_DIR = os.getenv('AIRTEST_EXPORT_DIR')  # 测试报告相关资源打包后的导出路径,目录不存在会自动创建
AUTOLINE_HOST = os.getenv('AUTOLINE_HOST')  # 静态资源文件服务器 格式:Scheme://IP:Port

if Path(AIRTEST_EXPORT_DIR).is_dir():
    pass
else:
    os.makedirs(AIRTEST_EXPORT_DIR)

class McCustomAirtestCase(AirtestCase):
    """
    Aietest Project自定义启动器,参考文档:http://airtest.netease.com/docs/cn/7_settings/3_script_record_features.html
    """

    PROJECT_ROOT = os.getenv("AIRTEST_PROJECT_ROOT", r"E:\treasure\Airtest\suite")  # 设置子脚本存放的根目录

    def setUp(self):
        print("----------Custom Setup [Hook method]----------")
        # 将自定义的公共变量加入到`self.scope`中,在脚本代码中就可以直接使用
        self.scope["SLEEPTIME"] = 1  # 睡眠时间
        self.scope["TIMEOUT"] = 5  # 超时时间

        # 设置`Airtest`全局属性值
        ST.THRESHOLD = 0.80  # 图像识别精确度阈值 [0,1]
        ST.THRESHOLD_STRICT = 0.85  # assert语句里图像识别时使用的高要求阈值 [0,1]
        ST.OPDELAY = 2  # 每一步操作后等待多长时间进行下一步操作, 只针对Airtest语句有效, 默认0.1s
        ST.FIND_TIMEOUT = 10  # 图像查找超时时间,默认为20s
        ST.CVSTRATEGY = ["tpl", "sift", "brisk"]  # 修改图像识别算法顺序,只要成功匹配任意一个符合设定阙值的结果,程序就会认为识别成功

        # 可以将一些通用的操作进行封装,然后在其他脚本中 import;
        # Airtest 提供了 using 接口,能够将需要引用的脚本加入 sys.path 里,其中包含的图片文件也会被加入 Template 的搜索路径中
        # using("common.air")    # 相对于PROJECT_ROOT的路径
        self.exec_other_script("setup.air")
        super(McCustomAirtestCase, self).setUp()

    def tearDown(self):
        print("----------Custom Teardown [Hook method]----------")
        self.exec_other_script("teardown.air")
        super(McCustomAirtestCase, self).tearDown()


def find_all_scripts(suite_dir: str = "") -> list:
    """
    遍历suite目录,取出所有的测试脚本
    """
    suite = []

    if not suite_dir:
        suite_dir = McCustomAirtestCase.PROJECT_ROOT

    for fpath in Path(suite_dir).iterdir():
        tmp = Path(suite_dir, fpath)
        if not tmp.is_dir():
            pass
        else:
            if fpath.suffix == '.air' and fpath.stem not in ["setup", "teardown"]:  # 这里会排除掉初始化脚本
                suite.append(fpath.name)
            else:
                deep_scripts = find_all_scripts(tmp)  # 递归遍历
                suite += deep_scripts

    return suite


def allow_run_scripts() -> List[str]:
    """
    读取配置文件,返回允许运行的脚本名称
    """
    config_allow_run = []

    with open("config.csv", "r") as f:
        f_csv = csv.DictReader(f)
        for row in f_csv:
            if row["Label"].upper() == "Y":
                config_allow_run.append(row["Script"].strip())

    return config_allow_run


def run_airtest(script, log_root, device=""):
    """
    运行单个脚本,并生成测试报告,返回运行结果
    :param script:  *.air, 要运行的脚本
    :param device:  设备字符串
    :param log_root:  脚本日志存放目录
    """
    if log_root.is_dir():
        shutil.rmtree(log_root)
    else:
        os.makedirs(log_root)
        print(str(log_root) + '>>> is created')

    # 组装运行参数
    args = Namespace(device=device,  # 设备字符串
                     log=log_root,  # log目录
                     recording=None,  # 禁止录屏
                     script=script  # *.air
                     )
    run_script(args, McCustomAirtestCase)


def generate_report(script, *, log_root, export_root):
    """
    生成测试报告
    :param script:  运行名称
    :param log_root:  脚本log目录
    :return: export_root  测试报告输出目录
    """
    # 测试报告导出目录
    if not export_root.is_dir():
        os.makedirs(export_root)
        print(str(export_root) + '>>> is created')

    output_file = Path(export_root, script.replace('.air', '.log'), 'log.html')  # 测试报告`log.html`存放路径

    # 生成测试报告
    rpt = report.LogToHtml(script_root=script,  # *.air
                           log_root=log_root,  # log目录
                           export_dir=export_root,  # 设置此参数后,生成的报告内资源引用均使用相对路径
                           lang='zh',  # 设置语言, 默认"en"
                           script_name=script.replace(".air", ".py"),  # *.air/*.py
                           static_root=AUTOLINE_HOST + '/static',  # 设置此参数后,打包后的资源目录中不会包含样式资源
                           plugins=['poco.utils.airtest.report']  # 使报告支持poco语句
                           )
    rpt.report(template_name="log_template.html", output_file=output_file)

    # 提取脚本运行结果
    result = rpt.test_result  # True or False

    return result


def summary_html(results: list, output_dir: str, template_dir: str, elapsed_time):
    """
    生成自定义的聚合报告
    :param results:  用例执行结果
    :param output_dir:  html输出目录
    :param template_parent:  jinja2模板所在目录
    """
    env = jinja2.Environment(
        loader=jinja2.FileSystemLoader(template_dir),
        extensions=(),
        autoescape=True,
    )
    template = env.get_template("summary_template.html", template_dir)
    html = template.render({"results": results,  # 运行结果
                            "now": datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S'),  # 当前时间
                            "elapsed_time": elapsed_time,  # 脚本运行时长
                            })
    output_file = Path(output_dir, "summary.html")  # 聚合报告路径
    with open(output_file, 'w', encoding="utf-8") as f:
        f.write(html)


def copy_lastest_report(source):
    """
    复制最新生成的测试报告到 lastest/
    """
    latest = Path(AIRTEST_EXPORT_DIR, 'latest')
    if latest.is_dir():
        shutil.rmtree(latest)  # dst 目录必须不能存在,否则copytree报错

    shutil.copytree(os.path.abspath(source),
                    os.path.abspath(latest))  # 目录内文件不能正在使用 否则无法复制成功PermissionError: [WinError 5] 拒绝访问


def print_message(*message):
    print("*" * 50)
    mes = " ".join(map(str, message))
    print(mes)
    print("*" * 50)


def clear_history_report(report_dir, critical_day):
    """
    清除生成的历史测试报告
    :param report_dir:  测试报告目录
    :param critical_day:
    """
    root = Path(report_dir)
    reports = [report for report in root.iterdir() if report.is_dir() and report.name != 'latest']
    for report in reports:
        dt = datetime.datetime.strptime(report.name, '%Y%m%d%H%M%S')
        delta = datetime.datetime.now() - dt
        if delta.days > int(critical_day):
            shutil.rmtree(os.path.abspath(report))


def main():
    start_time = int(time.time())
    all_scripts = find_all_scripts()
    config_allow_run = allow_run_scripts()
    suite: set = set(all_scripts).intersection(config_allow_run)
    print_message("本次要运行的用例集合为:", suite)

    results = []  # 脚本运行结果汇总
    dt = datetime.datetime.now().strftime('%Y%m%d%H%M%S')
    export_root = Path(AIRTEST_EXPORT_DIR, dt)  # 测试报告导出目录

    for script in suite:
        print_message("正在运行的用例名称:" + script)

        log_root = Path(McCustomAirtestCase.PROJECT_ROOT, 'log', script.replace('.air', '.log'))  # 脚本日志目录
        run_airtest(script, log_root)  # 运行脚本
        result = generate_report(script, log_root=log_root, export_root=export_root)  # 生成测试报告
        results.append((script, result))

        if not result:
            print_message("用例执行失败: " + script)
        else:
            print_message("用例执行成功:" + script)

    print_message("用例运行结果汇总:", results)
    end_time = int(time.time())
    elapsed_time = end_time - start_time
    summary_html(results, output_dir=export_root, template_dir=McCustomAirtestCase.PROJECT_ROOT,
                 elapsed_time=elapsed_time)
    copy_lastest_report(source=export_root)
    clear_history_report(AIRTEST_EXPORT_DIR, 3)


if __name__ == '__main__':
    """
    通过命令行来启动air脚本时,需要传入一些参数如设备号,脚本名等,而使用AirtesIDE运行脚本时,这些参数会自动传入;
    源码中使用argparse的方法来解析命令行参数,此处不能使用和源码相同的方法,否则AirtestIDE中会无法识别参数,而导致报错;
    具体源码,详见runner_parser、parse_args和run_script三个方法
    """
    # parser = argparse.ArgumentParser()
    # parser.add_argument("-b", "--Batch", type=str, help="是否启用批量运行脚本功能,为了支持在AirtestIDE中使用自定义的启动器,此项默认不启用")
    # args = parser.parse_args()
    #
    # # 如果传入Batch参数且其值为true, 则表示开启批量运行脚本功能
    # if args.Batch and args.Batch.lower() == "true":
    #     main()
    # else:
    #     # AirtestIDE中运行脚本时, 会自动带入script, --device, --log, --recording参数;
    #     ap = runner_parser()
    #     args = ap.parse_args()
    #     run_script(args, McCustomAirtestCase)

    if len(sys.argv) == 1:  # 如果没有传入任何命令行参数,则批量执行脚本
        main()
        quit()
    else:  # 在AirtestIDE中运行脚本时,会传入script、log、device等参数,则调用以下方法;
        ap = runner_parser()
        args = ap.parse_args()
        run_script(args, McCustomAirtestCase)

如果你不想一个人野蛮生长,找不到系统的资料,问题得不到帮助,坚持几天便放弃的感受的话,可以加入我们的QQ群:746506216,大家可以一起讨论交流,里面会有各种软件测试资料和技术交流。


资源分享

下方这份完整的软件测试视频学习教程已经上传CSDN官方认证的二维码,朋友们如果需要可以自行免费领取 【保证100%免费】

在这里插入图片描述

在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/22950.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

浙大医疗健康产业管理MBA提面经验分享

各位潜在的学弟学妹们好,很高兴和各位分享下我参加2022年浙大医疗产业管理MBA的提前批面试经验。在经过材料的撰写提交、面试备考各环节后顺利拿到优秀资格,为后面的笔试备考减轻了很大压力,回忆起去年的面试过程,我的面试以及备考…

沉睡者IT - 为你解密那些卖虚拟资源和知识付费课程的平台到底有多简单和多赚钱。

潜力博主推荐,点击上面关注博主 ↑ ↑ 上图为平台首页面截图,官方总站演示:vip.zzzz.la 备用演示:VIP.网站 1.虚拟资源平台介绍! (1)虚拟资源项目站是一个在线知识付费平台,全自动…

Nacos 中的配置文件如何实现加密传输

小伙伴们知道,Spring Cloud Config 很早就提供了配置文件的加解密功能,并且支持对称加密和非对称加密两种不同的模式。Nacos 作为分布式配置中心服务注册中心的合体,在配置文件加密这块一直差点意思,不过好在,如果你使…

公众号免费搜题系统调用方法

公众号免费搜题系统调用方法 本平台优点: 多题库查题、独立后台、响应速度快、全网平台可查、功能最全! 1.想要给自己的公众号获得查题接口,只需要两步! 2.题库: 查题校园题库:查题校园题库后台&#xf…

SQL Server 服务的启动

目录 前言: 一、进入控制面板 二、开启 SQL Server 服务 1. 找到管理工具并点击 2. 双击服务 3. 找到SQL Server 数据库服务 4. 右键点击 SQL Server 服务,选择启动 三、修改 SQL Server 服务的启动方式 1. 右键点击服务,点击属性 …

激发客户潜在需求

企业不光要看到客户的显现需求,更要挖掘客户的潜在需求,因为客户的潜在需求是可以转化为显现需求的,满足客户的潜在需求可以为企业带来更多经济效益。 前言 潜在需求是指消费者虽然有明确意识的欲望,但由于种种原因还没有明确的显…

Redis的缓存更新策略和缓存问题

1.缓存更新 1.1缓存更新策略 内存淘汰: 不需要自己维护,利用Redis的内存淘汰机制,当内存不足时自动淘汰部分数据,下次查询时更新缓存一致性 : 差维护成本:无 超时删除: 给缓存数据添加TTL时间…

零基础自学javase黑马课程第十五天

零基础自学javase黑马课程第十五天 ✨欢迎关注🖱点赞🎀收藏⭐留言✒ 🔮本文由京与旧铺原创,csdn首发! 😘系列专栏:java学习 💻首发时间:🎞2022年11月21日&…

【案例设计】配置与批量化处理外部 Texture 导入格式转换

开发平台:Unity 2020 版本以上 编程平台:Visual Studio 2020 版本 编程语言:CSharp   前言 Unity 开发者不仅是要求在面对开发需求上有着预见性的目光与能力去应对各种功能实现。更加注重的是通过各个项目的开发类型与过程,总结…

计算机体系结构:不同改进方案的性价比计算

题目内容 某一计算机用于商业外贸的事务处理,有大量的字符串操作。由于这种事务处理很普遍,有较大的市场,故而设计人员决定在下一代此类计算机的CPU中加入字符串操作的功能。经测试应用软件调查发现,字符串操作的使用占整个程序运…

进程切换及一些常见概念(面试必问)

目录前言一、竞争性1、什么是进程的竞争性?2、为什么进程间存在竞争性?二、独立性#这里先简单了解三、并行四、并发五、优先级队列六、进程切换寄存器1. 函数返回值2. 进程上下文数据总结前言 在不同的进程在处理机上切换的过程中,我们需要学…

碳酸钙/GPC3单克隆抗体介导阿霉素二氧化硅纳米粒/DOX-GNRs@mSiO2-HA-RGD纳米制备方法

小编在这里整理了碳酸钙/GPC3单克隆抗体介导阿霉素二氧化硅纳米粒/DOX-GNRsmSiO2-HA-RGD纳米制备方法,来看! 碳酸钙阿霉素二氧化硅纳米颗粒制备方法: 包括以下步骤: 将含有钙离子的乙醇溶液与含有氨水与盐酸阿霉素的水溶液混合,…

【Jupyter】远程连接Jupyter服务器

远程连接Jupyter 步骤一 配置Jupyter https://blog.csdn.net/MYRLibra/article/details/109599531 https://blog.csdn.net/weixin_40641725/article/details/114636779 安装 conda activate abc #激活虚拟环境 pip install jupyter #安装 jupyter notebook --generate-conf…

Webservice接口-WSDL文档【Webservice】

WSDL是一个用于精确描述Web服务的文档,WSDL文档是一个遵循WSDL-XML模式的XML文档。WSDL 文档将Web服务定义为服务访问点或端口的集合。在 WSDL 中,由于服务访问点和消息的抽象定义已从具体的服务部署或数据格式绑定中分离出来,因此可以对抽象…

python使用flask实现前后端分离通过前端修改数据库数据【全栈开发基础】

文章目录🚎前言:🛺工具🚓截图🚕数据库截图🚙前端截图🚘代码🚲增加🍕前端 HTML🍟后端 python🛴 删除🍕前端 HTML🍟后端 pyt…

AMM 套利者

AMM 套利者 理由 以太坊和其他支持 EVM 的区块链上有很多 AMM。其中许多 AMM 是 UniswapV2 的分叉项目或与 UniswapV2 具有相同的接口。这些 AMM 的列表: Uniswap V2(以太坊)寿司交换(以太坊)煎饼掉期(BSC)MDEX(BSC/heco) ... 一旦相同代币…

SpiderPool - 云原生容器网络 IPAM 插件

SpiderPool 来源于容器网络落地实践的经验积累,是「Daocloud 道客」开源的原生容器网络 IPAM 插件(github:https://github.com/spidernet-io/spiderpool),主要用于搭配 Underlay CNI 实现容器云平台精细化的管理和分配…

策略验证_指标买点分析技法_运用boll布林线指标选择买点

写在前面: 1. 本文中提到的“股票策略校验工具”的具体使用操作请查看该博文; 2. 文中知识内容来自书籍《同花顺炒股软件从入门到精通》 3. 本系列文章是用来学习技法,文中所得内容都仅仅只是作为演示功能使用 目录 解说 策略代码 结果 解…

基于云原生网关的可观测性最佳实践

作者: 井轶 为什么要进行可观测性建设 可观测性并不是一个新词,该词来源于控制理论,是指系统可以由其外部输出推断其其内部状态的程度,随着 IT 行业几十年的发展,IT 系统的监控,告警,问题排查…

【附源码】计算机毕业设计JAVA学生信息管理系统

【附源码】计算机毕业设计JAVA学生信息管理系统 目运行 环境项配置: Jdk1.8 Tomcat8.5 Mysql HBuilderX(Webstorm也行) Eclispe(IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持)。 项目技术: JAVA my…