一、项目目录
二、各文件说明
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
四、 说明
我的demo是基于图像识别的浏览器case,没有连接设备,执行手机的在my_runner.py将devices添加进去即可
五、各文件源码
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_runner.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 17 18 class CustomAirtestCase(AirtestCase): 19 # @classmethod 20 # def setUpClass(cls): 21 # super(CustomAirtestCase,cls).setUpClass() 22 23 24 def setUp(self): 25 print("custom setup") 26 super(CustomAirtestCase, self).setUp() 27 28 def tearDown(self): 29 print("custom tearDown") 30 super(CustomAirtestCase, self).setUp() 31 32 def run_air(self, root_dir='', device=[], scriptname='air_case'): 33 # 用例目录 34 script_path = root_dir + "/" + scriptname 35 # 聚合结果 36 results = [] 37 # 创建log文件 38 root_log = root_dir + '/' + 'log' 39 if os.path.isdir(root_log): 40 shutil.rmtree(root_log) 41 else: 42 os.makedirs(root_log) 43 print(str(root_log) + ' is created') 44 45 # 创建export_log文件 46 export_log = root_dir + '/' + 'export_log' 47 if os.path.isdir(export_log): 48 shutil.rmtree(export_log) 49 else: 50 os.makedirs(export_log) 51 print(str(export_log) + ' is created') 52 53 for f in os.listdir(script_path): 54 if f.endswith(".air"): 55 # f为.air案例名称:login.air 56 airName = f 57 script = os.path.join(script_path, f) 58 # airName_path为.air的全路径/Users/zhangxue/Documents/study/airtest_fppui/air_case/login.air 59 print("当前运行脚本路径:" + str(script)) 60 # 日志存放路径和名称:/Users/zhangxue/Documents/study/airtest_fppui/log/login/log.html 61 log = os.path.join(root_dir, 'log' + '/' + airName.replace('.air', '')) 62 print("log路径:" + str(log)) 63 if os.path.isdir(log): 64 shutil.rmtree(log) 65 else: 66 os.makedirs(log) 67 print(str(log) + ' is created') 68 # global args 69 args = Namespace(device=device, log=log, recording=None, script=script, compress=1) 70 try: 71 run_script(args, AirtestCase) 72 except: 73 pass 74 finally: 75 export_output_file = os.path.join(export_log + "/" + airName.replace('.air', '.log') + '/log.html') 76 rpt = report.LogToHtml(script_root=script, log_root=log, export_dir=export_log) 77 rpt.report("log_template.html", output_file=export_output_file) 78 result = {} 79 result["name"] = airName.replace('.air', '') 80 result["result"] = rpt.test_result 81 results.append(result) 82 83 # 生成聚合报告 84 env = jinja2.Environment( 85 loader=jinja2.FileSystemLoader(root_dir), 86 extensions=(), 87 autoescape=True 88 ) 89 template = env.get_template("summary_template.html", root_dir) 90 html = template.render({"results": results}) 91 output_file = os.path.join(export_log, "summary.html") 92 with io.open(output_file, 'w', encoding="utf-8") as f: 93 f.write(html) 94 print(output_file) 95 96 97 if __name__ == '__main__': 98 test = CustomAirtestCase() 99 root = os.path.abspath(".") 100 print("root_path路径: " + root) 101 102 device = [''] 103 104 test.run_air(root) 105 106 my_runner.py文件
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)
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 7emem; 10 text-align: center; 11 } 12 .success { 13 color: green; 14 7emem; 15 text-align: center; 16 } 17 .details-col-elapsed { 18 7em; 19 text-align: center; 20 } 21 .details-col-msg { 22 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>