Loading

【airtest】airtest学习汇总--进阶篇:批量运行脚本、汇总报告、导出报告

 
 ps:该文章是进阶篇,如果对airtest没有任何了解的,可以先看下【入门篇】https://www.cnblogs.com/zhangxue521/p/14874848.html
然后再看本章内容

一、项目目录

 

 

二、各文件说明

1、air_case。需要执行的脚本air文件,例如login.air。后续直接添加该文件即可,其他的文件都不用动
 
2、export_log。该文件夹自动生成,是自动导出的日志,发给其他人的时候,直接发送该包,日志中的路径用的相对路径。可以直接找到文件
 
3、log。该文件夹自动生成,是运行中产生的log.txt文件
 
4、my_runner.py。运行case的启动器,整个demo的入口文件。【一般报错的都是这个文件】已经将这个文件改成使用导出模板的方式
 
5、report.py。生成报告入口,但是my_runner.py已经运行完case,生成了报告,无需再执行此文件
 
6、summary_template.html。汇总报告的模版,执行完case汇总的报告是根据该文件的样式产生的。
 
7、util.py。工具
 

三、运行方式

python3 my_runner.py
 

四、 说明

需要连接设备的直接在my_runner.py执行脚本中,添加devices即可。
怎么获取device数据,稍后更新airtest的小白教程。
 

五、各文件源码

 

my_runner.py文件

  1 #!/usr/bin/env python
  2 # -*- coding: utf-8 -*-
  3 # @Time    : 2020-02-23 13:33
  4 # @Author  : zhangxue
  5 # @File    : my_runner1.py
  6 # @Desc    :
  7 #!/usr/bin/env python
  8 # -*- coding: utf-8 -*-
  9 from airtest.cli.runner import AirtestCase, run_script
 10 from argparse import *
 11 import airtest.report.report as report
 12 import jinja2
 13 import shutil
 14 import os
 15 import io
 16 from lib import Send_email
 17 import os.path
 18 
 19 class CustomAirtestCase(AirtestCase):
 20     # @classmethod
 21     # def setUpClass(cls):
 22     #     super(CustomAirtestCase,cls).setUpClass()
 23 
 24 
 25     def setUp(self):
 26         print("custom setup")
 27         super(CustomAirtestCase, self).setUp()
 28 
 29     def tearDown(self):
 30         print("custom tearDown")
 31         super(CustomAirtestCase, self).setUp()
 32 
 33     def run_air(self, root_dir='', device=[], scriptname='air_case'):
 34         # 用例目录
 35         # script_path = root_dir + "/" + scriptname
 36         script_path = os.path.join(root_dir, scriptname)
 37         # 聚合结果
 38         results = []
 39         # 创建log文件
 40         # root_log = root_dir + '/' + 'log'
 41         root_log = os.path.join(root_dir, 'log')
 42         if os.path.isdir(root_log):
 43             shutil.rmtree(root_log)
 44         else:
 45             os.makedirs(root_log)
 46             print(str(root_log) + '  is created')
 47 
 48         # 创建export_log文件
 49         # export_log = root_dir + '/' + 'export_log'
 50         export_log = os.path.join(root_dir, 'export_log')
 51         if os.path.isdir(export_log):
 52             shutil.rmtree(export_log)
 53         else:
 54             os.makedirs(export_log)
 55             print(str(export_log) + '  is created')
 56 
 57         for f in os.listdir(script_path):
 58             if f.endswith(".air"):
 59                 # f为.air案例名称:login.air
 60                 airName = f
 61                 script = os.path.join(script_path, f)
 62                 # airName_path为.air的全路径/Users/zhangxue/Documents/study/airtest_fppui/air_case/login.air
 63                 print("当前运行脚本路径:" + str(script))
 64                 # 日志存放路径和名称:/Users/zhangxue/Documents/study/airtest_fppui/log/login/log.html
 65                 log = os.path.join(root_dir, 'log', airName.replace('.air', ''))
 66                 print("log路径:" + str(log))
 67                 if os.path.isdir(log):
 68                     shutil.rmtree(log)
 69                 else:
 70                     os.makedirs(log)
 71                     print(str(log) + '  is created')
 72                 # global args该行代码注意,下载的不同的airtest版本,可能参数不太一样,缺少参数直接添加对应的值即可
 73                 args = Namespace(device=device, log=log, recording=None, script=script, compress=1, no_image='')
 74                 try:
 75                     run_script(args, AirtestCase)
 76                 except:
 77                     pass
 78                 finally:
 79                     # export_output_file = os.path.join(export_log + "/" + airName.replace('.air', '.log') + '/log.html')
 80                     export_output_file = os.path.join(export_log, airName.replace('.air', '.log'), 'log.html')
 81                     rpt = report.LogToHtml(script_root=script, log_root=log, export_dir=export_log)
 82                     rpt.report("log_template.html", output_file=export_output_file)
 83                     result = {}
 84                     result["name"] = airName.replace('.air', '')
 85                     result["result"] = rpt.test_result
 86                     results.append(result)
 87 
 88         # 生成聚合报告
 89         env = jinja2.Environment(
 90             loader=jinja2.FileSystemLoader(root_dir),
 91             extensions=(),
 92             autoescape=True
 93         )
 94         template = env.get_template("summary_template.html", root_dir)
 95         html = template.render({"results": results})
 96         output_file = os.path.join(export_log, "summary.html")
 97         with io.open(output_file, 'w', encoding="utf-8") as f:
 98             f.write(html)
 99         print(output_file)
100 
101         # 将报告发送到邮件
102         Send_email.send_mail_report("UI自动化测试报告!!!")
103 
104 if __name__ == '__main__':
105     test = CustomAirtestCase()
106     root = os.path.abspath(".")
107     print("root_path路径:  " + root)
108 
109     device = ['android://127.0.0.1:5037/TPG4C18308000271']
110     # device = ['android://127.0.0.1:5037/2476a88e','android://127.0.0.1:5037/99.12.74.40:7237']
111 
112     test.run_air(root_dir=root, device=device)

 

 

report.py文件

  1 # -*- coding: utf-8 -*-
  2 
  3 import os
  4 import io
  5 import types
  6 import shutil
  7 import json
  8 import jinja2
  9 from airtest.utils.compat import decode_path
 10 import airtest.report.report as R
 11 
 12 HTML_FILE = "log.html"
 13 HTML_TPL = "log_template.html"
 14 STATIC_DIR = os.path.dirname(R.__file__)
 15 
 16 def get_parger(ap):
 17     ap.add_argument("script", help="script filepath")
 18     ap.add_argument("--outfile", help="output html filepath, default to be log.html")
 19     ap.add_argument("--static_root", help="static files root dir")
 20     ap.add_argument("--log_root", help="log & screen data root dir, logfile should be log_root/log.txt")
 21     ap.add_argument("--record", help="custom screen record file path", nargs="+")
 22     ap.add_argument("--export", help="export a portable report dir containing all resources")
 23     ap.add_argument("--lang", help="report language", default="en")
 24     ap.add_argument("--plugins", help="load reporter plugins", nargs="+")
 25     return ap
 26 
 27 
 28 def get_script_info(script_path):
 29     script_name = os.path.basename(script_path)
 30     result_json = {"name": script_name, "author": None, "title": script_name, "desc": None}
 31     return json.dumps(result_json)
 32 
 33 
 34 def _make_export_dir(self):
 35     dirpath = self.script_root
 36     logpath = self.script_root
 37     # copy static files
 38     for subdir in ["css", "fonts", "image", "js"]:
 39         dist = os.path.join(dirpath, "static", subdir)
 40         shutil.rmtree(dist, ignore_errors=True)
 41         self.copy_tree(os.path.join(STATIC_DIR, subdir), dist)
 42 
 43     return dirpath, logpath
 44 
 45 
 46 def report(self, template_name, output_file=None, record_list=None):
 47     """替换LogToHtml中的report方法"""
 48     self._load()
 49     steps = self._analyse()
 50     # 修改info获取方式
 51     info = json.loads(get_script_info(self.script_root))
 52 
 53     if self.export_dir:
 54         self.script_root, self.log_root = self._make_export_dir()
 55         output_file = os.path.join(self.script_root, HTML_FILE)
 56         self.static_root = "static/"
 57 
 58     if not record_list:
 59         record_list = [f for f in os.listdir(self.log_root) if f.endswith(".mp4")]
 60     records = [os.path.join(self.log_root, f) for f in record_list]
 61 
 62     if not self.static_root.endswith(os.path.sep):
 63         self.static_root = self.static_root.replace("\\", "/")
 64         self.static_root += "/"
 65 
 66     data = {}
 67     data['steps'] = steps
 68     data['name'] = os.path.basename(self.script_root)
 69     data['scale'] = self.scale
 70     data['test_result'] = self.test_result
 71     data['run_end'] = self.run_end
 72     data['run_start'] = self.run_start
 73     data['static_root'] = self.static_root
 74     data['lang'] = self.lang
 75     data['records'] = records
 76     data['info'] = info
 77 
 78     return self._render(template_name, output_file, **data)
 79 
 80 
 81 def get_result(self):
 82     return self.test_result
 83 
 84 
 85 def main(args):
 86     # script filepath
 87     path = decode_path(args.script)
 88     record_list = args.record or []
 89     log_root = decode_path(args.log_root) or path
 90     static_root = args.static_root or STATIC_DIR
 91     static_root = decode_path(static_root)
 92     export = decode_path(args.export) if args.export else None
 93     lang = args.lang if args.lang in ['zh', 'en'] else 'zh'
 94     plugins = args.plugins
 95 
 96     # gen html report
 97     rpt = R.LogToHtml(path, log_root, static_root, export_dir=export, lang=lang, plugins=plugins)
 98     # override methods
 99     rpt._make_export_dir = types.MethodType(_make_export_dir, rpt)
100     rpt.report = types.MethodType(report, rpt)
101     rpt.get_result = types.MethodType(get_result, rpt)
102 
103     rpt.report(HTML_TPL, output_file=args.outfile, record_list=record_list)
104 
105     return rpt.get_result()
106 
107 
108 if __name__ == "__main__":
109     import argparse
110     ap = argparse.ArgumentParser()
111     args = get_parger(ap).parse_args()
112     print(str(args) + "     111111111111111")
113     basedir = os.path.dirname(os.path.realpath(__file__))
114     print(basedir)
115     logdir = os.path.realpath(args.script)
116     print(logdir + "2222222")
117 
118     # 聚合结果
119     results = []
120 
121     # 遍历所有日志
122     for subdir in os.listdir(logdir):
123         if os.path.isfile(os.path.join(logdir, subdir)):
124             continue
125         args.script = os.path.join(logdir, subdir)
126         args.outfile = os.path.join(args.script, HTML_FILE)
127         result = {}
128         result["name"] = subdir
129         result["result"] = main(args)
130         results.append(result)
131 
132     # 生成聚合报告
133     env = jinja2.Environment(
134         loader=jinja2.FileSystemLoader(basedir),
135         extensions=(),
136         autoescape=True
137     )
138     template = env.get_template("summary_template.html")
139     html = template.render({"results": results})
140 
141     output_file = os.path.join(logdir, "summary.html")
142     with io.open(output_file, 'w', encoding="utf-8") as f:
143         f.write(html)
144     print(output_file)
report.py文件

 

summary_template.html文件

 1 <!DOCTYPE html>
 2 <html>
 3 <head>
 4     <title>测试结果汇总</title>
 5     <meta charset="UTF-8">
 6     <style>
 7         .fail {
 8             color: red;
 9          width: 7emem;
10          text-align: center;
11         }
12         .success {
13             color: green;
14          width: 7emem;
15          text-align: center;
16         }
17       .details-col-elapsed {
18          width: 7em;
19          text-align: center;
20       }
21       .details-col-msg {
22          width: 7em;
23          text-align: center;
24          background-color:#ccc;
25       }
26 
27     </style>
28 </head>
29 <body>
30 <div>
31 <div><h2>Test Statistics</h2></div>
32 
33     <table width="800" border="thin" cellspacing="0" cellpadding="0">
34         <tr  width="600">
35             <th width="300" class='details-col-msg'>案例名称</th>
36             <th class='details-col-msg'>执行结果</th>
37         </tr>
38         {% for r in results %}
39         <tr width="600">
40             <td class='details-col-elapsed'><a href="{{r.name}}.log/log.html" target="view_window">{{r.name}}</a></td>
41             <td class="{{'success' if r.result else 'fail'}}">{{"成功" if r.result else "失败"}}</td>
42         </tr>
43         {% endfor %}
44     </table>
45 </div>
46 </body>
47 </html>
summary_template.html文件

 

 

六、生成的报告

6.1、发送到邮箱的格式

因为我的demo脚本只有一个,所以列表现在只有一个【多个脚本会在列表展示】

 

6.2、浏览器打开导出文件格式

 

6.3、每个测试用例的报告

 

 

 

 

 

posted @ 2020-02-23 14:42  爱笑的眼睛真美  阅读(2976)  评论(34编辑  收藏  举报