一:主要内容

框架功能、框架架构及测试报告效果

airtest安装、环境搭建

框架搭建、框架运行说明

框架源码

二:框架功能及测试报告效果

1. 框架功能:

该框架笔者用来作为公司的项目的前端自动化,支持pc和app,本文的air脚本是针对app的,关于pc的脚本会专门在写一篇文章说明,该框架功能如下:

支持在安卓多台设备中批量运行所有后缀为air的测试脚本(因为ios的连接需要macOS,我是windows机所以暂时只连了安卓端的ios未做测试)

支持指定某个用例或某几个用例在某台设备或某几台设备中进行运行

支持控制测试用例执行顺序,默认会将登录用例排在第一,退出用例排在最后执行,如果想要自定义其他顺序,可以在run.py文件中修改sort_cases函数方法即可

支持多脚本多设备运行完成后,生成一份汇总的测试报告,且点击汇总测试报告中具体的某一个用例,还能查看该用例详细的airtest报告

2. 框架架构说明

3. 测试报告效果:

给大家看一下多设备、多脚本的测试报告效果:

点击详情效果:

三:airtest安装、环境搭建

1.python环境安装

这里不再赘述,安装并配置好环境变量后,执行python -V查看是否安装成功

2.airtestIDE安装

airtest安装很简单,安装airtestIDE,从官网下载:http://airtest.netease.com/

下载后解压缩到本地,我的本地位置为:G:\AirtestIDE_2020-01-21_py3_win64\AirtestIDE_2020-01-21_py3_win64\AirtestIDE.exe,双击exe文件即为启动airtestIDE工具即可

3.包安装

需要安装如下包:

pip install airtestpip install pocoui

如果执行不能安装成功,则可以使用如下命令:

pip install -i http://pypi.douban.com/simple --trusted-host pypi.douban.com airtestpip install -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com pocoui

如果想用airtest编写selenium即pc自动化脚本,则还需要安装如下包:

pip install seleniumpip install pynputpip install airtest_selenium

关于这一步的安装也就是 pip install airtest_selenium,也可以从airtest安装目录下拷贝该文件夹到python目录下

我的python目录为:G:\python3.6.5;

我的airtest安装目录为:G:\AirtestIDE_2020-01-21_py3_win64\AirtestIDE_2020-01-21_py3_win64,该路径下有个airtest_selenium文件夹;

可以拷贝airtest目录下的airtest_selenium文件夹到python目录下。

如果想用airtest编写selenium即pc自动化脚本,除了安装上面的包,因为airtest-selenium自动化因为需要打开浏览器,所以我们还需要配置谷歌浏览器路径和下载匹配的谷歌驱动文件

airtest设置谷歌启动路径:airtestIDE界面-点击选项-点击设置-点击chrome path-选择谷歌安装路径一直到chrome.exe文件

下载匹配的谷歌驱动文件:

可以使用该网站下载:https://npm.taobao.org/mirrors/chromedriver

下载后替换掉airtest根目录我的路径是G:\AirtestIDE_2020-01-21_py3_win64\AirtestIDE_2020-01-21_py3_win64下的chromedriver.exe文件即可

4.框架版本说明

该框架使用版本如下:

python 3.6.5airtest 1.1.3pocoui 1.0.79pynput 1.6.8airtestIDE 1.2.3

四:框架搭建、框架运行说明

1.框架搭建

该框架搭建很简单,就是一个python工程:

该工程根目录下开始时有一个result空文件夹、一个report_tpl.html模板文件、run.py启动脚本、docs文件夹是我自己放的一些项目描述文档可有可无,.air文件是自己通过airtestIDE编写的项目的自动化脚本

2.框架脚本文件说明

run.py #启动文件,python run.py即可report_tpl.html#测试报告模板文件report.html #自动生成的测试报告文件,会将汇总的执行结果的json数据即下面的summary数据格式与report_tpl.html结合,生成测试报告result #文件夹,用于存放每个测试用例的执行json结果数据格式为下面的results数据格式xxx.air#测试用例,所有以.air文件名称结尾的文件夹都是测试用例xxx.air/log#每个测试用例的日志文件,以设备号区分,每个设备号下存放一份测试结果日志文件log.html#每个测试用例在每个设备中运行的具体效果,即测试报告中点击具体测试用例右侧弹出的页面详情效果log.txt #每个测试用例在每个设备中运行的json结果数据

3.框架运行编写建议

执行命令时可以用python run.py运行整个框架

但是写脚本或者调试脚本时,用airtestIDE来操作,即从airtestIDE中新建编辑.air脚本保存到该框架的根目录下,调试通过后再用run.py进行批量脚本、批量设备去执行。

这样就比较清晰

五:框架源码

1.run.py

1 # -*- encoding=utf-8 -*-2 # Run Airtest in parallel on multi-device3 import os4 import traceback5 import subprocess6 import webbrowser7 import time8 import json9 import shutil 10 from airtest.core.android.adb import ADB 11 from jinja2 import Environment, FileSystemLoader 121314 def run(devices, airs): 15 """" 16 run_all 171819 """ 20 try: 21 data_r=[] 22 global time_s 23 time_s = time.time() 24 for air in airs: 25 results = load_jdon_data(air) 26 tasks = run_on_multi_device(devices, air, results) 27 for task in tasks: 28 status = task['process'].wait() 29 results['tests'][task['dev']] = run_one_report(task['air'], task['dev']) 30 results['tests'][task['dev']]['status'] = status 31 name = air.split(".")[0] 32 json.dump(results, open(get_path("result")+os.sep+name+'_data.json', "w"), indent=4) 33 data_r.append(results) 34 run_summary(data_r) 35 except Exception as e: 36 traceback.print_exc() 373839 def run_on_multi_device(devices, air, results): 40 """ 41 在多台设备上运行airtest脚本 42 Run airtest on multi-device 43 """ 44 tasks = [] 45 for dev in devices: 46 log_dir = get_path("log",dev,air) 47 #命令行执行:airtest run openOrder.air --device Android://127.0.0.1:5037/b7f0c036 --log F:\airtest_code\good_store_project\log\openOrder 48 cmd = [ 49 "airtest", 50 "run", 51 air, 52 "--device", 53 "Android:///" + dev, 54 "--log", 55 log_dir 56 ] 57 try: 58 tasks.append({ 59 'process': subprocess.Popen(cmd, cwd=os.getcwd()), 60 'dev': dev, 61 'air': air 62 }) 63 except Exception as e: 64 traceback.print_exc() 65 return tasks 6667 #点击每个用例的详情页面 68 def run_one_report(air, dev): 69 """" 70 生成一个脚本的测试报告 71 Build one test report for one air script 72 """ 73 try: 74 log_dir = get_path("log",dev, air) 75 log = os.path.join(log_dir, 'log.txt') 76 if os.path.isfile(log): 77 #命令行执行:airtest report F:\airtest_code\good_store_project\openOrder.air --log_root F:\airtest_code\good_store_project\log\openOrder --outfile F:\airtest_code\good_store_project\log\openOrder\openOrder.html --lang zh 78 #如果是selenium,则最后要加上selenium插件 79 #airtest report F:\airtest_code\good_store_project\openOrder.air --log_root F:\airtest_code\good_store_project\log\openOrder --outfile F:\airtest_code\good_store_project\log\openOrder\openOrder.html --lang zh --plugins airtest_selenium.report 80 cmd = [ 81 "airtest", 82 "report", 83 air, 84 "--log_root", 85 log_dir, 86 "--outfile", 87 os.path.join(log_dir, 'log.html'), 88 "--lang", 89 "zh" 90 ] 91 ret = subprocess.call(cmd, shell=True, cwd=os.getcwd()) 92 return { 93 'status': ret, 94 'path': os.path.join(log_dir, 'log.html') 95 } 96 else: 97 print("Report build Failed. File not found in dir %s" % log) 98 except Exception as e: 99 traceback.print_exc()100 return {'status': -1, 'device': dev, 'path': ''}101 102 103 def run_summary(data):104 """"105 生成汇总的测试报告106 Build sumary test report107 """108 try:109 for i in data:110 c = get_json_value_by_key(i,"status")111 112 summary = {113 'time': "%.3f" % (time.time() - time_s),114 'success': c.count(0),115 'count': len(c)116 }117 summary['start_all'] = time.strftime("%Y-%m-%d %H:%M:%S",118time.localtime(time_s))119 summary["result"] = data120 print("summary++++++++++",summary)121 122 env = Environment(loader=FileSystemLoader(os.getcwd()),123 trim_blocks=True)124 html = env.get_template('report_tpl.html').render(data=summary)125 with open("report.html", "w", encoding="utf-8") as f:126 f.write(html)127 webbrowser.open("report.html")128 except Exception as e:129 traceback.print_exc()130 131 132 def load_jdon_data(air):133 """"134 加载进度135 返回一个空的进度数据136 """137 clear_log_dir(air)138 return {139 'start': time.time(),140 'script': air,141 'tests': {}142 143 }144 145 def clear_log_dir(air):146 """"147 清理log文件夹 openCard.air/log148 Remove folder openCard.air/log149 """150 log = os.path.join(os.getcwd(), air, 'log')151 if os.path.exists(log):152 shutil.rmtree(log)153 154 #获取key为status的值155 def get_json_value_by_key(in_json, target_key, results=[]):156 for key,value in in_json.items(): # 循环获取key,value157 if key == target_key:158 results.append(value)159 if isinstance(value, dict):160 get_json_value_by_key(value,target_key)161 return results162 163 #获取路径164 def get_path(content,device=None,air="openCard.air"):165 root_path = os.getcwd()166 path = os.getcwd()167 if content=="result":168 #返回测试报告路径169 path = os.path.join(root_path,"result")170 elif content == "log":171 log_dir = os.path.join(root_path,air, 'log', device.replace(".", "_").replace(':', '_'))172 #如果没有日志路径则创建一个173 if not os.path.exists(log_dir):174 os.makedirs(log_dir)175 #返回日志路径176 path = log_dir177 elif content == "cases":178 #返回测试用例路径179 path = os.path.join(root_path,air)180 else:181 #返回根目录182 path = root_path183 return path184 185 #获取路径下所有air的测试用例文件186 def get_cases(path):187 cases=[]188 for name in os.listdir(get_path(path)):# 遍历当前路径下的文件夹和文件名称189 if name.endswith(".air"):190 cases.append(name)191 return cases192 193 def sort_cases(cases,loginAir,outAir):194 #清除列表中的登录、退出登录,然后将其分别添加到列表的第一位和最后一位195 cases.remove(loginAir)196 cases.remove(outAir)197 cases.insert(0, loginAir)198 cases.insert(len(airs), outAir)199 return cases200 201 202 if __name__ == '__main__':203 204 """205 初始化数据206 Init variables here207 """208 #获取所有已连接的设备列表209 devices = [tmp[0] for tmp in ADB().devices()]210 #设置指定设备执行测试用例211 # devices = ["BTY4C16705003852","b7f0c036"]212 #获取所有测试用例213 airs = get_cases("root")214 #将登录用例排在最前面执行,退出用例排在最后面执行215 sort_airs = sort_cases(airs,"loginPro.air","loginOutPro.air")216 #获取指定用例,按顺序执行217 # sort_airs = ["openCardPro.air","openOrderPro.air","quickMoneyPro.air"]218 """219 执行脚本220 excute scripts221 """222 # 运行所有脚本223 run(devices, sort_airs)

2.report_tpl.html

1 2 3 4 5 6 7 8  9 Airtest 多设备并行测试结果汇总 10  11  12 *{ 13 margin: 0; 14 padding: 0; 15 } 16 body{ 17 background: #eeeeee 18 } 19 .container { 20 width: 75%; 21 min-width: 800px; 22 margin: auto 23 } 24 body.zh .en{ 25 display: none; 26 } 27 body.en .zh{ 28 display: none; 29 } 30 h1{ 31 margin-top: 50px; 32 text-align: center; 33 } 34 .center{ 35 text-align: center; 36 margin-top: 15px; 37 margin-bottom: 30px; 38 font-size: 14px; 39 position: relative; 40 } 41 .btn{ 42 border: solid 1px #c0c0c0; 43 padding: 5px 20px; 44 border-radius: 3px; 45 background: white; 46 cursor: context-menu; 47 } 48 .btn.lang:hover { 49 background: #5cb85c26; 50 border-color: #0a790a; 51 } 52 .btn.lang { 53 position: absolute; 54 top: 0; 55 } 56 .head { 57 margin: 20px 0 30px 0; 58 } 59 .head, .table{ 60 background: white; 61 border-radius: 5px; 62 box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12); 63 padding: 30px 20px; 6465 } 66 .head .progress{ 67 background: #dddddd; 68 color: white; 69 border-radius: 5px; 70 text-align: center; 71 margin-top: 12px; 72 } 73 .head .progress-bar-success{ 74 width: 0; 75 transition: all 0.5s ease; 76 background: #5cb85c; 77 border-radius: 5px; 78 } 79 .table-title { 80 text-align: center; 81 margin-bottom: 20px; 82 font-size: 18px; 83 font-weight: bold; 84 position: relative; 85 } 86 .table-row{ 87 border: solid 1px #e5e5e5; 88 margin-top: -1px; 89 cursor: context-menu; 90 } 91 .table-row:hover, .table-row.active{ 92 background: beige; 93 } 94 .table-head{ 95 background: aliceblue; 96 } 97 .table-head:hover{ 98 background: aliceblue; 99 }100 .table-head .table-col{101 padding-top: 10px;102 padding-bottom: 10px;103 font-weight: bold;104 text-align: center;105 }106 .table-col{107 display: inline-block;108 width: 200px;109 line-height: 30px;110 padding: 5px 10px;111 border-left: solid 1px #e5e5e5;112 margin-top: -1px;113 margin-right: -5px;114 }115 .table-col.short{116 width: 100px;117 text-align: center;118 }119 .table-col.mid{120 width: 200px;121 text-align: center;122 }123 .table-col:first-child{124 border: none;125 }126 .table-col.long{127 width: calc(100% - 700px);128 }129 .table-col.success{130 color: green;131 }132 .table-col.failed{133 color: red;134 }135 .detail{136 text-align: center;137 font-size: 14px;138 color: gray;139 }140 .iframe{141 position: fixed;142 top: 0;143 right: -100%;144 width: 70%;145 min-width: 800px;146 height: 100%;147 box-shadow: 0 5px 10px grey;148 transition: right 0.5s ease;149 background: white;150 max-width: 1100px;151 }152 .iframe-tools{153 position: absolute;154 top: 23px;155 left: -34px;156 background: white;157 box-shadow: -2px 2px 5px grey;158 border-radius: 7px;159 }160 .iframe-tools .close, .iframe-tools .open{161 width: 32px;162 height: 50px;163 color: gray;164 cursor: context-menu;165 display: block;166 }167 .iframe.show{168 right: 0;169 }170 iframe{171 width: 100%;172 height: calc(100% - 70px);173 border: none;174 }175 .iframe-head {176 height: 60px;177 line-height: 70px;178 text-align: center;179 border-bottom: solid 1px #ddd;180 box-shadow: 2px 0 6px #999;181 margin-bottom: 10px;182 }183 ::-webkit-scrollbar {184 width: 10px;185 height: 10px;186 background-color: rgba(0,0,0,.34);187 }188 ::-webkit-scrollbar-thumb {189 background-color: #8b8b8b;190 border-radius: 10px;191 }192 ::-webkit-scrollbar-track {193 background-color: #f5f5f5;194 -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.22);195 }196 .iframe .close {197 background: url('data:img/jpg;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgEAYAAAAj6qa3AAAABGdBTUEAALGPC/xhBQAAAAFzUkdC AK9OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dE AAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAAA6JJREFUaN7tmc9LG0EUx99sE+xB/wBJ vQhCPLTojAr+Af6iKJSSVhD0EhNC1HgWxIt/gBA0MaQXqQoV2gb/Ag+Krs4KHlSMN38EFRGkIupm Xw/reEgIu9lsNi34vQTcie99P/NmZt8E4FWvKkpt6bZ0W/rdu0rnYVdektmBNEETNPHlS3Ytu5Zd Oz6mC3SBLgwMVNo4o4wy2tmp1qv1av3REVthK2xldNS2ANRN3dT99SvVqEa1pyfGGGMMkcpUprKq VgqEME436SbdvL8XebEAC7CAppkFQYyMwwM8wMP376SVtJJWlyt3HMYwhrFsFg7hEA6HhpRBZVAZ XFwst3GcwzmcS6VImIRJ+O3b/IHAgCFCB3RARyTCfdzHfdGoIQCxlkSpQwxiEKuqMkoMt3Ebt1UV kpCE5MCAElACSuDHD7uMN280bzRvdHcTiUhE+vWroPHcvJ4nCBuxERvfv9+t2a3ZrTk4EM/z9gC5 QW6QG05PyR7ZI3uitJ+ejAK9VEgTNEHT0pJdS0PMeLHGRQWQEAmR0OhorvGXvI3+T8tYy1jL2OfP uI7ruL68rP/V7TZL3urSMF3qBYwDBw48HOacc85jsULDDQE4DcIp40UDKDcIp41bBmA3CDJDZsjM 1ZXTxksG8JIHZZRRnw93cAd3lpYKHZd5IMSpIYMMsqqaNh6EIAQ1DeqgDuoCAd7De3jPt29W8y8Z gJDVijAtm2Y8V2/syu9863zrfOvgwOPz+Dy+/X04gRM4+fTpOYz1OGUybjsA20GU2biQ6WaoWGnV WrVW/ecPzuIszmazxX4f/ehHv6ZhBCMYub0tV5627QFCdIJO0ImuLuiDPuj7/dv05lYIRJl7DdsA 2G3cKRAlA7DapIjjTJR60cenTU2X5T1AzLjVJgXmYR7mR0akcWlcGu/v1x8633QVXQGWS91gV69U 02UaQLmM58ppEIYAnDJeKRCFr8QqZNxpEAWvxNRr9Vq9TqeLblLiEId4MMgVrnAlmbRqPI9riU0X 3uEd3n34kHszlPdqehY9i55Fb29re2t7a3svL4mXeIn340d9Rkl+xYgZX4VVWB0Z0Y0nEnYZF8pk MplMZn/fc+O58dyYf8UmLuIirqkppV1pV9p//sx7bhSYpmiKpoaHyQW5IBfxuH58SZJT7+qFZLg0 QhCC0OQk93M/909PlxxQgBC/B+j38KGQU4aNQOj5PD7qn5OTZQuoB/B6K238f8nrVf+6/gLOvYPg ZwC/JwAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOS0wMy0wNlQyMDozMTo1NCswODowMMqAOUgAAAAl dEVYdGRhdGU6bW9kaWZ5ADIwMTktMDMtMDZUMjA6MzE6NTQrMDg6MDC73YH0AAAASHRFWHRzdmc6 YmFzZS11cmkAZmlsZTovLy9ob21lL2FkbWluL2ljb24tZm9udC90bXAvaWNvbl9jOHk0dXZzNXd0 Zy9DbG9zZS5zdmfc199nAAAAAElFTkSuQmCC') no-repeat;198 background-size: 20px;199 background-position: center;200 border-bottom: solid 2px #e5e5e5;201 }202 .iframe .open {203 background: url("data:img/jpg;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgEAYAAAAj6qa3AAAABGdBTUEAALGPC/xhBQAAAAFzUkdC AK7OHOkAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAAZiS0dE AAAAAAAA+UO7fwAAAAlwSFlzAAAASAAAAEgARslrPgAABS5JREFUaN7tmGtIU28cx3+/s0tFGyEW SLfJ0F4Yk3YttNUY5prBoMsha1EUQoUZESW9KIXKYWiZQhR7UfYiRgmZkBaGLCuTaucsR6RiVxLp It1cLJjn/P4vxurf3785by3Rz5uxZ8/vec73s+fsec4Appjir0Qn08l0so0bDdcN1w3X1erxmoeJ d9DBwGqsxup586iYiqnY69WWa8u15SrVpBHwKwsXMipGxahu3TKyRtbIJiVNMgEAUAqlUJqaKr4Q X4gvGhuN3cZuY3di4uQR8AsajaAUlIKyoSEzMTMxM1GpnGQCANCKVrSaTCF3yB1y37yZXpZell42 c+akEfBDhAtd6MrIkG6Xbpdur61NqUqpSqmaNm3CCdDe197X3k9J0ev0Or2OZUkggYSsrJhF2NCG tlWrZvXO6p3V6/GspJW0kqTSoeqG7DDWGBIMCYaEzExSk5rUTicchINw0G6HAiiAguRkQEBAAKzE SqwcwQT1UA/1a9cGpUFpUFpdHWncujXyKooDxI1XUJZlWZaVSF6GXoZehjZvph20g3YcOgQlUAIl aWl/QjYAANRADdS43ZyaU3PqXbsijUTjJkC3RbdFt8VsBic4wXn2LB7Gw3h48eI/FngQyEY2slVU 8C7exbv27x8zAZF7Viaj1bSaVp84gb3Yi7379gEHHHA4bitsdBw7xnEcx3FFRSO+QI1Go9FoEhLk drldbq+rgyZogiazOd7RYoVUpCJVYeGwd4Eld5bcWXJnzhy5XC6Xy5ubJ1rwKLgMl+Gy5OSYBaRd SbuSdkWhYD4xn5hP9fWRVo0m3kFGxsWLXCFXyBUWFMQsYPrS6UunLz1zBo/iUTxqNMY7wrBZA2tg TW2twqfwKXx5eZFGURzyHGDYa9hr2Lt+Pa2jdbQuup9ONBobv8z+MvvL7E2bOOSQw/7+6CeSwUpM XaYuU9f8+aIgCqJw4wY8gAfwYMaMeEcZHl6v7JrsmuyawxHIDmQHsr9//2+PQVdAv7xf3i8vKcEq rMKqhIRRX4se9KAnimyPb95AGZRBWVcX8cQT//o1etCDnmCQLtAFuhAKQRIkQZJGg3a0oz0nJ+Z5 jsNxON7aGuoL9YX6HA5uAbeAWxAKDdZ9gIDIc/aiRUKP0CP0OJ3DzUnN1EzNT55AMRRDcUMDetGL 3qamcGo4NZza0hJwB9wB97dvYAUrWP9ngHRIh3QA/SX9Jf2l6IFlaAF0m27Tbb9fUAgKQZGT8xSf 4lMMBoeqGyBAPC+eF88XFWEd1mGdRPL78nCYrtJVuurxkItc5Dp3zq/wK/yK1tYBXXnggR+uzhgw gxnM7e2iX/SLfputbUXbirYVnz/HWv5DgDZDm6HNmDs3cqLLzY0IGKwsug0eOMCreBWv6ugYh2i/ h4CAnj8XH4oPxYdZWY9PPz79+PSHD8Md5ocAJp/JZ/Jzc2E37Ibd//rm8yEf8oNB2kk7aWdeHh/m w3z48uU/Hjiau5zKqby7W7AIFsGSldWGbdiGPT0jHe/nOeAUnIJTP+95ukt36W5np8iKrMiaTHEP XkEVVPHuHXOPucfciwZ/9Wq04zLRH73IW50usrQ6OiTbJNsk2ywWv9Kv9Cvb2+MWvJIqqfLjR9gA G2BDdrbviO+I70hn51iNLxUcgkNwWCy4HJfj8mfPoAVaoMVqfVTzqOZRzdu38QoOJ+EknPz6lelj +pg+u9333vfe9z4QGPN59KX6Un2p2x15rP3bzvbM+P9lp9uj26PbY7WOfqQpppiI/AOjmiKrfUvK NAAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAxOS0wMy0wNlQyMDozMTo1NCswODowMMqAOUgAAAAldEVY dGRhdGU6bW9kaWZ5ADIwMTktMDMtMDZUMjA6MzE6NTQrMDg6MDC73YH0AAAASHRFWHRzdmc6YmFz ZS11cmkAZmlsZTovLy9ob21lL2FkbWluL2ljb24tZm9udC90bXAvaWNvbl9jOHk0dXZzNXd0Zy9z aGFyZS5zdmftz7m3AAAAAElFTkSuQmCC") no-repeat;204 background-size: 20px;205 background-position: center;206 }207 select{height: 28px; line-height: auto; vertical-align: middle; height: 22px\9; padding: 3px 0\9; box-sizing:content-box; font-size: 13px;}208 :root select{padding: 0; height: 28px;}209 210 211 212 213 214 215 

汇总报告

216 217 Switch to English version218 开始时间:{{data['start_all']}},耗时 {{data['time']}} 秒219 Started at:{{data['start_all']}},cost {{data['time']}} s220 221 222
成功率: {{data["success"]}}/{{data["count"]}}
223
Success rate: {{data["success"]}}/{{data["count"]}}
224 225 226 {data['success'] *100 / data['count']}}%">227 {{'%0.2f' % (data["success"] *100 / data["count"])}}%228 229 230 231 232 233 全部234 all235 success236 成功237 failed238 失败239 240 241 242 用例列表243 Detail244 245 246 247 序号248 状态249 用例250 设备251 id252 result253 case254 device255 --256 257 {% set ns = namespace(found=0) %}258 {% for dat in data['result'] %}259 {% for dev, item in dat['tests'].items() %}260 {item['path']}}" >261 {% set ns.found = ns.found + 1 %}262 {{ns.found}}263 {'success' if item['status']==0 else 'failed'}}">{{"成功" if item['status']==0 else "失败"}}264 {'success' if item['status']==0 else 'failed'}}">{{"success" if item['status']==0 else "failed"}}265 {{dat['script']}}266 {{dev}}267 268 点击可查看详情269 click to see detail270 271 {% endfor %}272 {% endfor %}273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 var Lang = 'zh' // or en290 var rows = document.querySelectorAll('.table-row')291 var iframe = document.querySelector('.iframe')292 var iframeHead = document.querySelector('.iframe-head')293 var open = document.querySelector('.open')294 var close = document.querySelector('.iframe .close')295 var langBtn = document.querySelector('.lang')296 var body = document.body297 var prevActiveRow = null298 function init() {299 for(i=0; i=0) {321 showIframe(prevActiveRow)322 }323 })324 document.body.className = Lang325 }326 function showIframe(obj){327 var num = obj.querySelector('.table-col.short').innerText328 var device = obj.querySelector('.table-col.long').innerText329 if(Lang =='en') {330 num = ordinal_suffix_of(num)331 iframeHead.innerHTML = "Test report running in the " + num + ' device "' + device + '"'332 open.setAttribute('title', 'open in a new tab')333 close.setAttribute('title', 'close')334 }335 else {336 iframeHead.innerHTML = "第 " + num + " 台设备 【" + device + "】 的测试报告"337 open.setAttribute('title', '在新标签页打开')338 close.setAttribute('title', '关闭')339 }340 iframe.querySelector('iframe').setAttribute('src', path)341 open.setAttribute('href', path)342 iframe.className='iframe show'343 if(prevActiveRow){344 prevActiveRow.className = "table-row"345 }346 obj.className = 'table-row active'347 prevActiveRow = obj348 }349 function ordinal_suffix_of(i) {350 i = Number(i)351 var j = i % 10,352 k = i % 100;353 if (j == 1 && k != 11) {354 return i + "st";355 }356 if (j == 2 && k != 12) {357 return i + "nd";358 }359 if (j == 3 && k != 13) {360 return i + "rd";361 }362 return i + "th";363 }364 function addEvent(obj,type,handle) {365 try{// Chrome、FireFox、Opera、Safari、IE9.0 and above366 obj.addEventListener(type,handle);367 }catch(e){368 try{// IE8.0 and below369 obj.attachEvent('on'+ type,handle);370 }catch(e){// Browser in earlier vesion371 obj['on'+ type]= handle;372 }373 }374 }375 init()376 $(document).ready(function(){377 $('#exit').change(function(){// 下拉框绑定change事件378 var exit_code = $(this).children('option:selected').val(); // 获取下拉框选中值379 $('#tab .table-row').each(function() {380 var self = $(this).children().eq(1).text(); // 获取每行第二列的值381 if(exit_code=='all'){// 选中all时,数据全部显示382 $(this).show();383 }else{ // 选中其他的值时,进一步判断384 if(self!=exit_code){ // 列中的值和选中值不一致385 $(this).hide();// 该行不显示386 $('#tab .table-head').show()387 }else{388 $(this).show();389 }390 }391 });392 })393 })394 395

软件测试工程师自学教程:

这才是2022最精细的自动化测试自学教程,我把它刷了无数遍才上岸字节跳动,做到涨薪20K【值得自学软件测试的人刷】

接口性能测试 — 软件测试人必会618实战场景分析

软件测试工程师月薪2W以上薪资必学技能 — Python接口自动化框架封装.

美团面试真题_高级测试25K岗位面试 — 软件测试人都应该看看

测试开发之全面剖析自动化测试平台 — 软件测试人的必经之路

软件测试必会_Jmeter大厂实战 — 仅6步可实现接口自动化测试

Jmeter实战讲解案例 — 软件测试人必会

最后: 可以在公众号:伤心的辣条 ! 自行领取一份216页软件测试工程师面试宝典文档资料【免费的】。以及相对应的视频学习教程免费分享!,其中包括了有基础知识、Linux必备、Shell、互联网程序原理、Mysql数据库、抓包工具专题、接口测试工具、测试进阶-Python编程、Web自动化测试、APP自动化测试、接口自动化测试、测试高级持续集成、测试架构开发测试框架、性能测试、安全测试等。

现在我邀请你进入我们的软件测试学习交流群:746506216】,备注“入群”, 大家可以一起探讨交流软件测试,共同学习软件测试技术、面试等软件测试方方面面,还会有免费直播课,收获更多测试技巧,我们一起进阶Python自动化测试/测试开发,走向高薪之路。

喜欢软件测试的小伙伴们,如果我的博客对你有帮助、如果你喜欢我的博客内容,请 “点赞” “评论” “收藏” 一 键三连哦!