一:主要内容

  • 框架功能、框架架构及测试报告效果
  • 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 airtest

pip install pocoui

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

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

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

pip install selenium

pip install pynput

pip 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.5

airtest 1.1.3

pocoui 1.0.79

pynput 1.6.8

airtestIDE 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

 # -*- encoding=utf-8 -*-
# Run Airtest in parallel on multi-device
import os
import traceback
import subprocess
import webbrowser
import time
import json
import shutil
from airtest.core.android.adb import ADB
from jinja2 import Environment, FileSystemLoader def run(devices, airs):
""""
run_all
"""
try:
data_r=[]
global time_s
time_s = time.time()
for air in airs:
results = load_jdon_data(air)
tasks = run_on_multi_device(devices, air, results)
for task in tasks:
status = task['process'].wait()
results['tests'][task['dev']] = run_one_report(task['air'], task['dev'])
results['tests'][task['dev']]['status'] = status
name = air.split(".")[0]
json.dump(results, open(get_path("result")+os.sep+name+'_data.json', "w"), indent=4)
data_r.append(results)
run_summary(data_r)
except Exception as e:
traceback.print_exc() def run_on_multi_device(devices, air, results):
"""
在多台设备上运行airtest脚本
Run airtest on multi-device
"""
tasks = []
for dev in devices:
log_dir = get_path("log",dev,air)
#命令行执行:airtest run openOrder.air --device Android://127.0.0.1:5037/b7f0c036 --log F:\airtest_code\good_store_project\log\openOrder
cmd = [
"airtest",
"run",
air,
"--device",
"Android:///" + dev,
"--log",
log_dir
]
try:
tasks.append({
'process': subprocess.Popen(cmd, cwd=os.getcwd()),
'dev': dev,
'air': air
})
except Exception as e:
traceback.print_exc()
return tasks #点击每个用例的详情页面
def run_one_report(air, dev):
""""
生成一个脚本的测试报告
Build one test report for one air script
"""
try:
log_dir = get_path("log",dev, air)
log = os.path.join(log_dir, 'log.txt')
if os.path.isfile(log):
#命令行执行: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
#如果是selenium,则最后要加上selenium插件
#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
cmd = [
"airtest",
"report",
air,
"--log_root",
log_dir,
"--outfile",
os.path.join(log_dir, 'log.html'),
"--lang",
"zh"
]
ret = subprocess.call(cmd, shell=True, cwd=os.getcwd())
return {
'status': ret,
'path': os.path.join(log_dir, 'log.html')
}
else:
print("Report build Failed. File not found in dir %s" % log)
except Exception as e:
traceback.print_exc()
return {'status': -1, 'device': dev, 'path': ''} def run_summary(data):
""""
生成汇总的测试报告
Build sumary test report
"""
try:
for i in data:
c = get_json_value_by_key(i,"status") summary = {
'time': "%.3f" % (time.time() - time_s),
'success': c.count(0),
'count': len(c)
}
summary['start_all'] = time.strftime("%Y-%m-%d %H:%M:%S",
time.localtime(time_s))
summary["result"] = data
print("summary++++++++++",summary) env = Environment(loader=FileSystemLoader(os.getcwd()),
trim_blocks=True)
html = env.get_template('report_tpl.html').render(data=summary)
with open("report.html", "w", encoding="utf-8") as f:
f.write(html)
webbrowser.open("report.html")
except Exception as e:
traceback.print_exc() def load_jdon_data(air):
""""
加载进度
返回一个空的进度数据
"""
clear_log_dir(air)
return {
'start': time.time(),
'script': air,
'tests': {} } def clear_log_dir(air):
""""
清理log文件夹 openCard.air/log
Remove folder openCard.air/log
"""
log = os.path.join(os.getcwd(), air, 'log')
if os.path.exists(log):
shutil.rmtree(log) #获取key为status的值
def get_json_value_by_key(in_json, target_key, results=[]):
for key,value in in_json.items(): # 循环获取key,value
if key == target_key:
results.append(value)
if isinstance(value, dict):
get_json_value_by_key(value,target_key)
return results #获取路径
def get_path(content,device=None,air="openCard.air"):
root_path = os.getcwd()
path = os.getcwd()
if content=="result":
#返回测试报告路径
path = os.path.join(root_path,"result")
elif content == "log":
log_dir = os.path.join(root_path,air, 'log', device.replace(".", "_").replace(':', '_'))
#如果没有日志路径则创建一个
if not os.path.exists(log_dir):
os.makedirs(log_dir)
#返回日志路径
path = log_dir
elif content == "cases":
#返回测试用例路径
path = os.path.join(root_path,air)
else:
#返回根目录
path = root_path
return path #获取路径下所有air的测试用例文件
def get_cases(path):
cases=[]
for name in os.listdir(get_path(path)): # 遍历当前路径下的文件夹和文件名称
if name.endswith(".air"):
cases.append(name)
return cases def sort_cases(cases,loginAir,outAir):
#清除列表中的登录、退出登录,然后将其分别添加到列表的第一位和最后一位
cases.remove(loginAir)
cases.remove(outAir)
cases.insert(0, loginAir)
cases.insert(len(airs), outAir)
return cases if __name__ == '__main__': """
初始化数据
Init variables here
"""
#获取所有已连接的设备列表
devices = [tmp[0] for tmp in ADB().devices()]
#设置指定设备执行测试用例
# devices = ["BTY4C16705003852","b7f0c036"]
#获取所有测试用例
airs = get_cases("root")
#将登录用例排在最前面执行,退出用例排在最后面执行
sort_airs = sort_cases(airs,"loginPro.air","loginOutPro.air")
#获取指定用例,按顺序执行
# sort_airs = ["openCardPro.air","openOrderPro.air","quickMoneyPro.air"]
"""
执行脚本
excute scripts
"""
# 运行所有脚本
run(devices, sort_airs)

2.report_tpl.html

 <!DOCTYPE html>
<html>
<head>
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<link rel="shortcut icon" type="image/png" href="http://airtest.netease.com/static/img/icon/favicon.ico">
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>Airtest 多设备并行测试结果汇总</title>
</head>
<style type="text/css">
*{
margin: 0;
padding: 0;
}
body{
background: #eeeeee
}
.container {
width: 75%;
min-width: 800px;
margin: auto
}
body.zh .en{
display: none;
}
body.en .zh{
display: none;
}
h1{
margin-top: 50px;
text-align: center;
}
.center{
text-align: center;
margin-top: 15px;
margin-bottom: 30px;
font-size: 14px;
position: relative;
}
.btn{
border: solid 1px #c0c0c0;
padding: 5px 20px;
border-radius: 3px;
background: white;
cursor: context-menu;
}
.btn.lang:hover {
background: #5cb85c26;
border-color: #0a790a;
}
.btn.lang {
position: absolute;
top: 0;
}
.head {
margin: 20px 0 30px 0;
}
.head, .table{
background: white;
border-radius: 5px;
box-shadow: 0 2px 5px 0 rgba(0, 0, 0, 0.16), 0 2px 10px 0 rgba(0, 0, 0, 0.12);
padding: 30px 20px; }
.head .progress{
background: #dddddd;
color: white;
border-radius: 5px;
text-align: center;
margin-top: 12px;
}
.head .progress-bar-success{
width: 0;
transition: all 0.5s ease;
background: #5cb85c;
border-radius: 5px;
}
.table-title {
text-align: center;
margin-bottom: 20px;
font-size: 18px;
font-weight: bold;
position: relative;
}
.table-row{
border: solid 1px #e5e5e5;
margin-top: -1px;
cursor: context-menu;
}
.table-row:hover, .table-row.active{
background: beige;
}
.table-head{
background: aliceblue;
}
.table-head:hover{
background: aliceblue;
}
.table-head .table-col{
padding-top: 10px;
padding-bottom: 10px;
font-weight: bold;
text-align: center;
}
.table-col{
display: inline-block;
width: 200px;
line-height: 30px;
padding: 5px 10px;
border-left: solid 1px #e5e5e5;
margin-top: -1px;
margin-right: -5px;
}
.table-col.short{
width: 100px;
text-align: center;
}
.table-col.mid{
width: 200px;
text-align: center;
}
.table-col:first-child{
border: none;
}
.table-col.long{
width: calc(100% - 700px);
}
.table-col.success{
color: green;
}
.table-col.failed{
color: red;
}
.detail{
text-align: center;
font-size: 14px;
color: gray;
}
.iframe{
position: fixed;
top: 0;
right: -100%;
width: 70%;
min-width: 800px;
height: 100%;
box-shadow: 0 5px 10px grey;
transition: right 0.5s ease;
background: white;
max-width: 1100px;
}
.iframe-tools{
position: absolute;
top: 23px;
left: -34px;
background: white;
box-shadow: -2px 2px 5px grey;
border-radius: 7px;
}
.iframe-tools .close, .iframe-tools .open{
width: 32px;
height: 50px;
color: gray;
cursor: context-menu;
display: block;
}
.iframe.show{
right: 0;
}
iframe{
width: 100%;
height: calc(100% - 70px);
border: none;
}
.iframe-head {
height: 60px;
line-height: 70px;
text-align: center;
border-bottom: solid 1px #ddd;
box-shadow: 2px 0 6px #999;
margin-bottom: 10px;
}
::-webkit-scrollbar {
width: 10px;
height: 10px;
background-color: rgba(0,0,0,.34);
}
::-webkit-scrollbar-thumb {
background-color: #8b8b8b;
border-radius: 10px;
}
::-webkit-scrollbar-track {
background-color: #f5f5f5;
-webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.22);
}
.iframe .close {
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;
background-size: 20px;
background-position: center;
border-bottom: solid 2px #e5e5e5;
}
.iframe .open {
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;
background-size: 20px;
background-position: center;
}
select{height: 28px; line-height: auto; vertical-align: middle; height: 22px\9; padding: 3px 0\9; box-sizing:content-box; font-size: 13px;}
:root select{padding: 0; height: 28px;}
</style>
<body class="zh">
<div class="container-fluid" >
<div class="container">
<div class="main">
<div class="material">
<h1>汇总报告</h1>
<div class="center">
<div class="btn lang">Switch to English version</div>
<div class="time zh">开始时间:{{data['start_all']}},耗时 <b>{{data['time']}}</b> 秒</div>
<div class="time en">Started at:{{data['start_all']}},cost <b>{{data['time']}}</b> s</div>
</div>
<div class="head">
<header class="zh"><span class="rate"></span>成功率:</span> {{data["success"]}}/{{data["count"]}}</header>
<header class="en"><span class="rate">Success rate:</span> {{data["success"]}}/{{data["count"]}}</header>
<div>
<div class="progress">
<div class="progress-bar progress-bar-success" role="progressbar" aria-valuemin="0" aria-valuemax="100" style="width: {{data['success'] *100 / data['count']}}%">
<span class="">{{'%0.2f' % (data["success"] *100 / data["count"])}}%</span>
</div>
</div>
</div>
</div>
<select name="" id="exit" style="width: 100px;">
<option class="zh" value="all">全部</option>
<option class="en" value="all">all</option>
<option class="en" value="成功">success</option>
<option class="zh" value="成功">成功</option>
<option class="en" value="失败">failed</option>
<option class="zh" value="失败">失败</option>
</select>
<div class="table" >
<div class="table-title">
<span class="running_detail zh">用例列表</span>
<span class="running_detail en">Detail</span>
</div>
<div class="table-content" id="tab">
<div class="table-row table-head">
<div class="table-col short zh">序号</div>
<div class="table-col short zh">状态</div>
<div class="table-col mid zh">用例</div>
<div class="table-col long zh">设备</div>
<div class="table-col short en">id</div>
<div class="table-col short en">result</div>
<div class="table-col mid en">case</div>
<div class="table-col long en">device</div>
<div class="table-col ">--</div>
</div>
{% set ns = namespace(found=0) %}
{% for dat in data['result'] %}
{% for dev, item in dat['tests'].items() %}
<div class="table-row" path="{{item['path']}}" >
{% set ns.found = ns.found + 1 %}
<div class="table-col short">{{ns.found}}</div>
<div class="table-col short zh {{'success' if item['status']==0 else 'failed'}}">{{"成功" if item['status']==0 else "失败"}}</div>
<div class="table-col short en {{'success' if item['status']==0 else 'failed'}}">{{"success" if item['status']==0 else "failed"}}</div>
<div class="table-col mid">{{dat['script']}}</div>
<div class="table-col long">{{dev}}</div> <div class="table-col detail zh">点击可查看详情</div>
<div class="table-col detail en">click to see detail</div>
</div>
{% endfor %}
{% endfor %}
</div>
</div>
</div>
</div> <div class="iframe">
<div class="iframe-head"></div>
<iframe src='.'></iframe>
<div class="iframe-tools">
<div class="close"></div>
<a class="open" href='.' target='_blank'></a>
</div>
</div>
</div>
</body>
<script type="text/javascript">
var Lang = 'zh' // or en
var rows = document.querySelectorAll('.table-row')
var iframe = document.querySelector('.iframe')
var iframeHead = document.querySelector('.iframe-head')
var open = document.querySelector('.open')
var close = document.querySelector('.iframe .close')
var langBtn = document.querySelector('.lang')
var body = document.body
var prevActiveRow = null
function init() {
for(i=0; i<rows.length; i++){
addEvent(rows[i], 'click', function(e){
path = this.getAttribute('path')
console.log(this)
if(path) {
showIframe(this)
}
})
}
addEvent(close, 'click', function(e){
iframe.className='iframe'
})
addEvent(langBtn, 'click', function(e){
if(Lang == 'zh'){
Lang = 'en';
this.innerText = '切换到中文版'
} else {
Lang = 'zh'
this.innerText = "Switch to English version"
}
document.body.className = Lang
if (iframe.className.indexOf('show')>=0) {
showIframe(prevActiveRow)
}
})
document.body.className = Lang
}
function showIframe(obj){
var num = obj.querySelector('.table-col.short').innerText
var device = obj.querySelector('.table-col.long').innerText
if(Lang =='en') {
num = ordinal_suffix_of(num)
iframeHead.innerHTML = "Test report running in the " + num + ' device "' + device + '"'
open.setAttribute('title', 'open in a new tab')
close.setAttribute('title', 'close')
}
else {
iframeHead.innerHTML = "第 " + num + " 台设备 【" + device + "】 的测试报告"
open.setAttribute('title', '在新标签页打开')
close.setAttribute('title', '关闭')
}
iframe.querySelector('iframe').setAttribute('src', path)
open.setAttribute('href', path)
iframe.className='iframe show'
if(prevActiveRow){
prevActiveRow.className = "table-row"
}
obj.className = 'table-row active'
prevActiveRow = obj
}
function ordinal_suffix_of(i) {
i = Number(i)
var j = i % 10,
k = i % 100;
if (j == 1 && k != 11) {
return i + "st";
}
if (j == 2 && k != 12) {
return i + "nd";
}
if (j == 3 && k != 13) {
return i + "rd";
}
return i + "th";
}
function addEvent(obj,type,handle) {
try{// Chrome、FireFox、Opera、Safari、IE9.0 and above
obj.addEventListener(type,handle);
}catch(e){
try{// IE8.0 and below
obj.attachEvent('on'+ type,handle);
}catch(e){// Browser in earlier vesion
obj['on'+ type]= handle;
}
}
}
init()
$(document).ready(function(){
$('#exit').change(function(){ // 下拉框绑定change事件
var exit_code = $(this).children('option:selected').val(); // 获取下拉框选中值
$('#tab .table-row').each(function() {
var self = $(this).children().eq(1).text(); // 获取每行第二列的值
if(exit_code=='all'){ // 选中all时,数据全部显示
$(this).show();
}else{ // 选中其他的值时,进一步判断
if(self!=exit_code){ // 列中的值和选中值不一致
$(this).hide(); // 该行不显示
$('#tab .table-head').show()
}else{
$(this).show();
}
}
});
})
})
</script>
</html>

airtest+poco多脚本、多设备批处理运行测试用例自动生成测试报告的更多相关文章

  1. Pycharm上python3运行unittest无法生成测试报告

    原文地址https://www.cnblogs.com/yoyoketang/p/7523409.html 前言 经常有人在群里反馈,明明代码一样的啊,为什么别人的能出报告,我的出不了报告:为什么别人 ...

  2. loadrunner笔记(三):设置、运行场景和生成测试报告

    //上一篇的代码有点问题,问题出在 web_reg_find()函数中,这个函数简单的说是搜索下一步操作的请求对象(html)页面中是否存在相应的文本字符串.所以用在登录操作中,它搜索的是主页.htm ...

  3. docker运行haproxy 自动生成配置

    #根据参数,shell自动生成haproxy配置 #为方便部署,特意做了个haproxy镜像 #Haproxy run as docker #运行实例 run #!/bin/bash #docker ...

  4. MySQL运行时自动生成的性能相关的数据参考

      某大师曾说过,一个DBA要像熟悉自己的老婆一样熟悉自己的数据库,个人认为包含了两个方面的熟悉: 1,在稳定性层面来说,更多的是关注高可用.读写分离.负载均衡,灾备管理等等high level层面的 ...

  5. jmeter+ANT+Jekins性能自动生成测试报告脚本(模板),加入:Median TIme、90%、95%、99%、QPS、以及流量显示

    <?xml version="1.0"?><xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/T ...

  6. (appium+python)UI自动化_09_unittest批量运行测试用例&生成测试报告

    前言 上篇文章[(appium+python)UI自动化_08_unittest编写测试用例]讲到如何使用unittets编写测试用例,并执行测试文件.接下来讲解下unittest如何批量执行测试文件 ...

  7. 5分钟上手自动化测试——Airtest+Poco快速上手

    版权声明:该文章为AirtestProject原创文章:允许转载,但转载必须注明“转载”并保留原链接 前言 本文档将演示使用`AirtestProject`专用的编辑器AirtestIDE,编写`Ai ...

  8. fiddler4自动生成jmeter脚本

    接口.性能测试任务当遇到从浏览器或移动app自己抓包的情况出现时就变得巨苦逼了,苦在哪里?苦在需要通过抓包工具抓报文,需要通过抓包报文梳理业务逻辑.需要将梳理的逻辑编写成脚本.最最苦的情况是,自己抓包 ...

  9. 如何在linux搭建airtest+chromeweb测试环境--(用命令行运行.air脚本)

    大前堤: 如果你需要airtest提供的可视化测试报告,那你的操作系统,一定要有图形化界面. 否则运行你的airtest脚本 会遇到这样的问题 Xlib.error.DisplayNameError: ...

随机推荐

  1. String 对象-->toUpperCase() 方法

    1.定义和用法 将字符串中所有的小写字符转换成大写字符,大写字符保持不变 返回转换后的结果字符串 语法: string.toUpperCase() 注意:不会改变字符串本身,仅以返回值的形式返回结果 ...

  2. String 对象-->toLowerCase() 方法

    1.定义和用法 将字符串中所有的大写字符转换成小写字符,小写字符不变 返回转换后的结果字符串 语法: string.toLowerCase() 注意:不会改变字符串本身,仅以返回值的形式返回结果 举例 ...

  3. AJ学IOS(02)UI之按钮操作 点击变换 移动 放大缩小 旋转

    不多说,先上图片看效果,AJ分享,必须精品 这个小程序主要实现点击方向键可以让图标上下左右动还有放大缩小以及旋转的功能,点击图片会显示另一张图片. 点击变换 其实用到了按钮的两个状态,再State C ...

  4. 如何正确管理HBase的连接,从原理到实战

    本文将介绍HBase的客户端连接实现,并说明如何正确管理HBase的连接. 最近在搭建一个HBase的可视化管理平台,搭建完成后发现不管什么查询都很慢,甚至于使用api去listTable都要好几秒. ...

  5. Serval and Parenthesis Sequence CodeForces - 1153C

    题目大意:一个字符串只含有? ( ),?可以变成 ) 或者 ( ,将字符串中所有的?变成) 或者 ( 使得字符串合法. 合法就是让括号配对,并且不可以提前结束比如:()()这样是不合法的. 题解:既然 ...

  6. 2.react-插件

    PC: antd(蚂蚁金服)https://ant.design/index-cn 移动: mobile-antd(蚂蚁金服)https://mobile.ant.design =========== ...

  7. [PHP] excel 的导入导出

    其实excel导入导出挺简单的,导出最简单! 其原理都是把数据读出来,导出是从数据库中读出数据,导入是从文件读出数据! 导出写入文件,导入写入数据库! 但是在导入表的时候,用的是PHPExcel, 不 ...

  8. pysparnn 模块使用,相似句子召回

    import pysparnn.cluster_index as ci from sklearn.feature_extraction.text import TfidfVectorizer data ...

  9. 9个小技巧让你的 if else看起来更优雅

    if else 是我们写代码时,使用频率最高的关键词之一,然而有时过多的 if else 会让我们感到脑壳疼,例如下面这个伪代码: 是不是很奔溃?虽然他是伪代码,并且看起来也很夸张,但在现实中,当我们 ...

  10. mysql查询添加

    当表结构一样的情况下,insert into 想要插入的表  SELECT * from  查询的表; 此sql语句,适应于 1000万数据插入1000万数据中去,2000万数据的合并 .------ ...