airtest+poco多腳本、多設備批處理運行測試用例自動生成測試報告


一:主要內容

  • 框架功能、框架架構及測試報告效果
  • 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

  1 # -*- encoding=utf-8 -*-
  2 # Run Airtest in parallel on multi-device
  3 import os
  4 import traceback
  5 import subprocess
  6 import webbrowser
  7 import time
  8 import json
  9 import shutil
 10 from airtest.core.android.adb import ADB
 11 from jinja2 import Environment, FileSystemLoader
 12 
 13 
 14 def run(devices, airs):
 15     """"
 16         run_all
 17  18  19     """
 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()
 37 
 38 
 39 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
 66 
 67 #點擊每個用例的詳情頁面
 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 report
107     """
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",
118                                              time.localtime(time_s))
119         summary["result"] = data
120         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/log
148         Remove folder openCard.air/log
149     """
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,value
157         if key == target_key:
158             results.append(value)
159         if isinstance(value, dict):
160             get_json_value_by_key(value,target_key)
161     return results
162 
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_dir
177     elif content == "cases":
178         #返回測試用例路徑
179         path = os.path.join(root_path,air)
180     else:
181         #返回根目錄
182         path = root_path
183     return path
184 
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 cases
192 
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 cases
200 
201 
202 if __name__ == '__main__':
203 
204     """
205         初始化數據
206         Init variables here
207     """
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 scripts
221     """
222     # 運行所有腳本
223     run(devices, sort_airs)

 

2.report_tpl.html

  1 <!DOCTYPE html>
  2 <html>
  3   <head>
  4     <meta http-equiv="X-UA-Compatible" content="IE=edge">
  5     <link rel="shortcut icon" type="image/png" href="http://airtest.netease.com/static/img/icon/favicon.ico">
  6     <script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
  7     <meta name="viewport" content="width=device-width, initial-scale=1">
  8     <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> 
  9     <title>Airtest 多設備並行測試結果匯總</title>
 10   </head>
 11   <style type="text/css">
 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;
 64 
 65     }
 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   </style>
210   <body class="zh">
211   <div class="container-fluid" >
212     <div class="container">
213       <div class="main">
214         <div class="material">
215           <h1>匯總報告</h1>
216           <div class="center">
217             <div class="btn lang">Switch to English version</div>
218             <div class="time zh">開始時間:{{data['start_all']}},耗時 <b>{{data['time']}}</b></div>
219             <div class="time en">Started at:{{data['start_all']}},cost <b>{{data['time']}}</b> s</div>
220           </div>
221           <div class="head">
222             <header class="zh"><span class="rate"></span>成功率:</span> {{data["success"]}}/{{data["count"]}}</header>
223             <header class="en"><span class="rate">Success rate:</span> {{data["success"]}}/{{data["count"]}}</header>
224             <div>
225               <div class="progress">
226                 <div class="progress-bar progress-bar-success" role="progressbar"  aria-valuemin="0" aria-valuemax="100" style="width: {{data['success'] *100 / data['count']}}%">
227                   <span class="">{{'%0.2f' % (data["success"] *100 / data["count"])}}%</span>
228                 </div>
229               </div>
230           </div>
231         </div>
232         <select name="" id="exit" style="width: 100px;">
233           <option class="zh" value="all">全部</option>
234           <option class="en" value="all">all</option>
235           <option class="en" value="成功">success</option>
236           <option class="zh" value="成功">成功</option>
237           <option class="en" value="失敗">failed</option>
238           <option class="zh" value="失敗">失敗</option>
239         </select>
240         <div class="table" >
241           <div class="table-title">
242             <span class="running_detail zh">用例列表</span>
243             <span class="running_detail en">Detail</span>
244           </div>
245           <div class="table-content" id="tab">
246             <div class="table-row table-head">
247               <div class="table-col short zh">序號</div>
248               <div class="table-col short zh">狀態</div>
249               <div class="table-col mid zh">用例</div>
250               <div class="table-col long zh">設備</div>
251               <div class="table-col short en">id</div>
252               <div class="table-col short en">result</div>
253               <div class="table-col mid en">case</div>
254               <div class="table-col long en">device</div>
255               <div class="table-col ">--</div>
256             </div>
257             {% set ns = namespace(found=0) %}
258             {% for dat in data['result'] %}
259             {% for dev, item in dat['tests'].items() %}
260               <div class="table-row" path="{{item['path']}}" >
261                 {% set ns.found = ns.found + 1 %}
262                 <div class="table-col short">{{ns.found}}</div>
263                 <div class="table-col short zh {{'success' if item['status']==0 else 'failed'}}">{{"成功" if item['status']==0 else "失敗"}}</div>
264                 <div class="table-col short en {{'success' if item['status']==0 else 'failed'}}">{{"success" if item['status']==0 else "failed"}}</div>
265                 <div class="table-col mid">{{dat['script']}}</div>
266                 <div class="table-col long">{{dev}}</div>
267 
268                 <div class="table-col detail zh">點擊可查看詳情</div>
269                 <div class="table-col detail en">click to see detail</div>
270               </div>
271             {% endfor %}
272             {% endfor %}
273           </div>
274         </div>
275       </div>
276     </div>
277 
278     <div class="iframe">
279       <div class="iframe-head"></div>
280       <iframe src='.'></iframe>
281       <div class="iframe-tools">
282           <div class="close"></div>
283           <a class="open" href='.' target='_blank'></a>
284       </div>
285     </div>
286   </div>
287   </body>
288   <script type="text/javascript">
289     var Lang = 'zh' // or en
290     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.body
297     var prevActiveRow = null
298     function init() {
299       for(i=0; i<rows.length; i++){
300         addEvent(rows[i], 'click', function(e){
301           path = this.getAttribute('path')
302           console.log(this)
303           if(path) {
304             showIframe(this)
305           }
306         })
307       }
308       addEvent(close, 'click', function(e){
309         iframe.className='iframe'
310       })
311       addEvent(langBtn, 'click', function(e){
312         if(Lang == 'zh'){
313           Lang = 'en';
314           this.innerText = '切換到中文版'
315         } else {
316           Lang = 'zh'
317           this.innerText = "Switch to English version"
318         }
319         document.body.className = Lang
320         if (iframe.className.indexOf('show')>=0) {
321           showIframe(prevActiveRow)
322         }
323       })
324       document.body.className = Lang
325     }
326     function showIframe(obj){
327       var num = obj.querySelector('.table-col.short').innerText
328       var device = obj.querySelector('.table-col.long').innerText
329       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 = obj
348     }
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 above
366         obj.addEventListener(type,handle);
367       }catch(e){
368         try{// IE8.0 and below
369         obj.attachEvent('on'+ type,handle);
370         }catch(e){// Browser in earlier vesion
371           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 </script>
395 </html>

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM