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)
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>
六、生成的报告
6.1、发送到邮箱的格式
因为我的demo脚本只有一个,所以列表现在只有一个【多个脚本会在列表展示】