async def run(request): """ run接口,get请求,把测试任务放入队列中 :param request: :return: """ system_name = request.match_info['name'] case_path = os.path.join(cfg.getConfig('case_path'), system_name) record_path = os.path.join(case_path, cfg.getConfig('record_name')) build_file = os.path.join(cfg.getConfig('case_path'), system_name, 'build.xml') email_file = glob.glob(os.path.join(case_path, 'email_*.txt')) if os.path.exists(case_path): if len(email_file) == 0: return web.Response(body=json.dumps( { 'code': 0, 'message': '没有设置收件人邮箱地址的txt文件,测试任务执行失败', 'data': None }, ensure_ascii=False)) elif len(email_file) > 1: return web.Response(body=json.dumps( { 'code': 0, 'message': '应该只有一个收件人邮箱地址的txt文件,但是找到了多个,测试任务执行失败', 'data': None }, ensure_ascii=False)) if not os.path.exists(record_path): f = open(record_path, 'a') f.close() if os.path.exists(build_file): schedule.task = [case_path, email_file[0]] return web.Response(body=json.dumps( { 'code': 1, 'message': '操作成功,测试任务正在准备执行', 'data': None }, ensure_ascii=False)) else: return web.Response(body=json.dumps( { 'code': 0, 'message': 'build.xml文件不存在,测试任务执行失败', 'data': None }, ensure_ascii=False)) else: return web.Response(body=json.dumps( { 'code': 0, 'message': '未找到与系统名称对应的脚本,请确认系统名称是否正确,脚本是否存在!', 'data': None }, ensure_ascii=False))
def run(self, case_email_path): """ 执行测试任务 :param case_email_path: 列表,第一个元素是测试用例文件路径,第二个元素是收件人的txt文件路径 :return: """ if int(cfg.getConfig('is_git')): logger.info('准备从git上拉取最新版本') repo = Repo(cfg.getConfig('git_path')) remote = repo.remote() remote.pull() logger.info('从git上拉取版本成功') file_name = None case_path = case_email_path[0] email_path = case_email_path[1] build_path = os.path.join(case_path, 'build.xml') logger.info(f'开始执行测试任务{build_path}') start_time = time.strftime('%Y-%m-%d %H:%M:%S') res = os.popen( 'ant -f {}'.format(build_path)).readlines() # 执行测试,并等待测试完成 for i in range(len(res)): if 'Build failed' in res[i]: # 如果有失败日志,打印出 logger.error('{}\n{}'.format(res[i - 1], res[i])) break if 'xslt' in res[i] and 'Processing' in res[i] and 'to' in res[ i]: # 获取测试报告文件名 line = res[i].strip() logger.debug(line) if '/' in line: file_name = line.split('/')[-1] else: file_name = line.split('\\')[-1] logger.info(file_name) break if file_name: logger.info('测试任务执行完成') time.sleep(2) msg = self.parse_html(file_name, case_path) # 重组html sendMsg(msg['fail_case'], email_path) # 发送邮件 string = f"{start_time},{build_path},{msg['total_num']},{msg['failure_num']}\n" self.lock.acquire() logger.info(f'写测试记录到本地, {string}') with open(os.path.join(case_path, cfg.getConfig('record_name')), 'a', encoding='utf-8') as f: f.write(string) self.lock.release() logger.info('测试完成') else: logger.error('测试任务执行失败')
def __init__(self): self.testing = Testing() self.thread_pool_size = max(1, int(cfg.getConfig('thread_pool'))) self.test_task = queue.Queue() # 创建队列 self.executor = ThreadPoolExecutor(self.thread_pool_size) # 创建线程池 self.run()
def lookup(self): while True: time.sleep(2) n = len(self.tasking) logger.info(f'当前正在执行的任务数为{n}') inds = [] for i in range(n): html_path = os.path.join( cfg.getConfig('report_path'), self.tasking[i]["file_name"]) + '.html' logger.info(html_path) if not os.path.exists(html_path): if time.time( ) - self.tasking[i]["start_time"] < self.interval: continue else: logger.error(f'测试任务执行超时') inds.append(i) html = f'<html><body>' \ f'<h3>异常提醒:{self.tasking[i]["build_file"]} 测试任务执行超时,请检查!</h3>' \ f'<p style="color:blue;">此邮件自动发出,请勿回复。</p></body></html>' try: sendMsg(html, self.tasking[i], is_path=False) except Exception as err: logger.error(err) else: time.sleep(1) logger.info('测试任务执行完成') flag = self.post_deal(self.tasking[i]) if flag: inds.append(i) for j in range(len(inds) - 1, -1, -1): self.tasking.pop(inds[j])
async def sendEmail(request): """ get请求,用于发送邮件,用于客户端异常时发送邮件提醒 :param request: :return: """ name = request.match_info['name'] port = request.match_info['port'] ind = request.match_info['ind'] IP = request.match_info['IP'] email_file = glob.glob( os.path.join(cfg.getConfig('case_path'), name, 'email_*.txt')) if len(email_file) == 0: return web.Response(body=json.dumps( { 'code': 0, 'message': '没有设置收件人邮箱地址的txt文件,测试任务执行失败', 'data': None }, ensure_ascii=False)) elif len(email_file) > 1: return web.Response(body=json.dumps( { 'code': 0, 'message': '应该只有一个收件人邮箱地址的txt文件,但是找到了多个,测试任务执行失败', 'data': None }, ensure_ascii=False)) if int(ind) == 1: msg = f'{IP} 服务器上的 {port} 端口已经停了,无法执行 {name} 的接口自动化测试,请及时检查端口状态' else: msg = f'{IP} 服务器上的 {name} 接口自动化测试执行异常,请检查测试用例,或手动执行get请求 http://{IP}:{PORT}/run?systemName={name} ' html = f'<html><body>' \ f'<h3>异常提醒:{msg}!</h3>' \ f'<p style="color:blue;">此邮件自动发出,请勿回复。</p></body></html>' try: sendMsg(html, email_file[0], is_path=False) return web.Response( body=json.dumps({ 'code': 1, 'message': '邮件提醒发送成功!', 'data': None }, ensure_ascii=False)) except Exception as err: return web.Response(body=json.dumps( { 'code': 0, 'message': err, 'data': None }, ensure_ascii=False))
def run(self, paths): """ 执行测试任务 :param paths: 字典 :return: """ try: if int(cfg.getConfig('is_git')): logger.info('准备从git上拉取最新版本') repo = Repo(cfg.getConfig('git_path')) remote = repo.remote() remote.pull() logger.info('从git上拉取版本成功') except Exception as err: logger.error(err) build_path = paths["build_file"] logger.info(f'开始执行测试任务{build_path}') try: if paths["method"] == "GET": shutil.copy(paths["config_file"], paths["new_config_file"]) # 复制,用于jmx执行 else: if paths["post_data"].get('params'): replace_config(paths["config_file"], paths["post_data"]['params'], paths["new_config_file"]) report_name = f'{paths["system_name"]}-{int(time.time() * 1000)}' _ = os.popen('nohup ant -f {} -DReportName={} &'.format( build_path, report_name)) # 执行测试 paths.update({"file_name": report_name}) paths.update({"start_time": time.time()}) self.tasking.append(paths) time.sleep(3) except Exception as err: logger.error(err)
async def get_list(request): """ 获取当前测试用例目录下的系统名,及执行测试任务需要的get请求的url """ case_path = cfg.getConfig('case_path') max_num = 0 for i in os.listdir(case_path): if max_num < len(i): max_num = len(i) dirs = [ d + ' ' * (max_num - len(d)) + '\t\t' + 'http://' + IP + ':' + PORT + '/run?systemName=' + d for d in os.listdir(case_path) if os.path.isdir(os.path.join(case_path, d)) ] return web.Response(body='\n'.join(dirs))
async def sendEmail(request): """ get请求,用于发送邮件,用于客户端异常时发送邮件提醒 :param request: :return: """ name = request.match_info['name'] port = request.match_info['port'] email_file = glob.glob( os.path.join(cfg.getConfig('case_path'), name, 'email_*.txt')) if len(email_file) == 0: return web.Response(body=json.dumps( { 'code': 0, 'message': '没有设置收件人邮箱地址的txt文件,测试任务执行失败', 'data': None }, ensure_ascii=False)) elif len(email_file) > 1: return web.Response(body=json.dumps( { 'code': 0, 'message': '应该只有一个收件人邮箱地址的txt文件,但是找到了多个,测试任务执行失败', 'data': None }, ensure_ascii=False)) html = f'<html><body>' \ f'<h3>异常提醒:{name}的接口自动化测试环境对应的{port}端口已经停了,请及时重启或更换端口!</h3>' \ f'<p style="color:blue;">此邮件自动发出,请勿回复。</p></body></html>' try: sendMsg(html, email_file[0], is_path=False) return web.Response( body=json.dumps({ 'code': 1, 'message': '邮件提醒发送成功', 'data': None }, ensure_ascii=False)) except Exception as err: return web.Response(body=json.dumps( { 'code': 0, 'message': err, 'data': None }, ensure_ascii=False))
def sendMsg(html, receiver_email, is_path=True, is_send=True): """ 发送邮件 :param html: 邮件正文用到的html文件路径,或者html :param receiver_email: 收件人邮箱地址的txt文件路径 :param is_path: bool,True表示html是一个路径,False表示html是html :param is_send: bool,是否发邮件,仅用于第一次发送失败后,再次发送 :return: """ if int(cfg.getConfig('is_email')): try: receive_name = re.findall('email_(.*?).txt', receiver_email)[0] # 提取收件人姓名 with open(receiver_email, 'r', encoding='utf-8') as f: sends = f.readlines() subject = sends[0] send_to = sends[1] logger.info('开始发送邮件,收件人{}'.format(send_to)) message = MIMEMultipart() message['From'] = Header(cfg.getConfig('sender_name')) # 发件人名字 message['To'] = Header(receive_name) # 收件人名字 message['Subject'] = Header(subject, 'utf-8') # 邮件主题 if is_path: with open(html, 'r', encoding='utf-8') as f: fail_case = f.read() else: fail_case = html email_text = MIMEText(fail_case, 'html', 'utf-8') message.attach(email_text) # 添加邮件正文 try: server = smtplib.SMTP_SSL(cfg.getConfig('smtp'), 465) # server.connect(cfg.getConfig('smtp')) except Exception as err: logger.error(err) server = smtplib.SMTP(cfg.getConfig('smtp'), 25) # server.connect(cfg.getConfig('smtp')) server.login(cfg.getConfig('sender_email'), '123456') # 登陆邮箱 server.sendmail(cfg.getConfig('sender_email'), send_to.split(','), message.as_string()) # 发送邮件 server.quit() del email_text del message logger.info('邮件发送成功') except Exception as err: logger.error(err) if is_send: sendMsg(html, receiver_email, is_path=is_path, is_send=False) else: logger.info('设置为不自动发送邮件,已跳过。')
async def run(request): """ run接口,把测试任务放入队列中 :param request: :return: """ if request.method == 'GET': paths = {"method": "GET"} system_name = request.query.get('systemName') # 待执行测试的系统根路径 case_path = os.path.join(cfg.getConfig('case_path'), system_name) # 测试用例路径 paths.update({"system_name": system_name}) paths.update({"case_path": case_path}) # 测试用例路径 paths.update({ "record_path": os.path.join(case_path, cfg.getConfig('record_name')) }) # 测试结果记录路径 paths.update({ "build_file": os.path.join(cfg.getConfig('case_path'), system_name, 'build.xml') }) # build.xml路径 paths.update( {"email_file": os.path.join(case_path, 'email_default.txt')}) # 邮件默认配置文件 paths.update({ "config_file": os.path.join(case_path, 'config_default.txt') }) # jmx执行的配置文件对应的默认配置文件 paths.update({"new_email_file": os.path.join(case_path, 'email.txt')}) # 邮件配置文件 paths.update( {"new_config_file": os.path.join(case_path, 'config.txt')}) # jmx执行的配置文件 if os.path.exists(case_path): if not os.path.exists(paths["record_path"]): f = open(paths["record_path"], 'a') f.close() if os.path.exists(paths["build_file"]): schedule.task = paths return web.Response(body=json.dumps( { 'code': 1, 'message': '操作成功,测试任务正在准备执行', 'data': None }, ensure_ascii=False)) else: return web.Response(body=json.dumps( { 'code': 0, 'message': 'build.xml文件不存在,测试任务执行失败', 'data': None }, ensure_ascii=False)) else: return web.Response(body=json.dumps( { 'code': 0, 'message': '未找到与系统名称对应的脚本,请确认系统名称是否正确,脚本是否存在!', 'data': None }, ensure_ascii=False)) else: post_data = await request.json() paths = {"method": "POST"} system_name = request.query.get('systemName') # 待执行测试的系统根路径 case_path = os.path.join(cfg.getConfig('case_path'), system_name) # 测试用例路径 paths.update({"system_name": system_name}) paths.update({"case_path": case_path}) # 测试用例路径 paths.update({ "record_path": os.path.join(case_path, cfg.getConfig('record_name')) }) # 测试结果记录路径 paths.update({ "build_file": os.path.join(cfg.getConfig('case_path'), system_name, 'build.xml') }) # build.xml路径 paths.update( {"email_file": os.path.join(case_path, 'email_default.txt')}) # 邮件默认配置文件 paths.update({ "config_file": os.path.join(case_path, 'config_default.txt') }) # jmx执行的配置文件对应的默认配置文件 paths.update({"new_email_file": os.path.join(case_path, 'email.txt')}) # 邮件配置文件 paths.update( {"new_config_file": os.path.join(case_path, 'config.txt')}) # jmx执行的配置文件 paths.update({"post_data": post_data}) if os.path.exists(case_path): if not os.path.exists(paths["record_path"]): f = open(paths["record_path"], 'a') f.close() if os.path.exists(paths["build_file"]): schedule.task = paths return web.Response(body=json.dumps( { 'code': 1, 'message': '操作成功,测试任务正在准备执行', 'data': None }, ensure_ascii=False)) else: return web.Response(body=json.dumps( { 'code': 0, 'message': 'build.xml文件不存在,测试任务执行失败', 'data': None }, ensure_ascii=False)) else: return web.Response(body=json.dumps( { 'code': 0, 'message': '未找到与系统名称对应的脚本,请确认系统名称是否正确,脚本是否存在!', 'data': None }, ensure_ascii=False))
#!/usr/bin/env python # -*- coding: utf-8 -*- # Author: leeyoshinari import os import glob import json import asyncio from aiohttp import web from schedule import Scheduler from sendEmail import sendMsg from logger import logger, cfg schedule = Scheduler() report_path = cfg.getConfig('report_path') IP = cfg.getConfig('host') PORT = cfg.getConfig('port') if not os.path.exists(report_path): os.mkdir(report_path) async def get_list(request): """ 获取当前测试用例目录下的系统名,及执行测试任务需要的get请求的url """ case_path = cfg.getConfig('case_path') max_num = 0 for i in os.listdir(case_path): if max_num < len(i): max_num = len(i) dirs = [ d + ' ' * (max_num - len(d)) + '\t\t' + 'http://' + IP + ':' + PORT +
async def run(request): """ run接口,把测试任务放入队列中 :param request: :return: """ if request.method == 'GET': system_name = request.query.get('systemName') # 待执行测试的系统根路径 case_path = os.path.join(cfg.getConfig('case_path'), system_name) # 测试用例路径 record_path = os.path.join(case_path, cfg.getConfig('record_name')) # 测试结果记录路径 build_file = os.path.join(cfg.getConfig('case_path'), system_name, 'build.xml') # build.xml路径 email_file = os.path.join(case_path, 'email_default.txt') # 邮件默认配置文件 config_file = os.path.join(case_path, 'config_default.txt') # jmx执行的配置文件对应的默认配置文件 new_email_file = os.path.join(case_path, 'email.txt') # 邮件配置文件 new_config_file = os.path.join(case_path, 'config.txt') # jmx执行的配置文件 if os.path.exists(case_path): shutil.copy(email_file, new_email_file) # 复制,用于发送邮件 shutil.copy(config_file, new_config_file) # 复制,用于jmx执行 if not os.path.exists(record_path): f = open(record_path, 'a') f.close() if os.path.exists(build_file): schedule.task = [case_path, new_email_file] return web.Response(body=json.dumps( { 'code': 1, 'message': '操作成功,测试任务正在准备执行', 'data': None }, ensure_ascii=False)) else: return web.Response(body=json.dumps( { 'code': 0, 'message': 'build.xml文件不存在,测试任务执行失败', 'data': None }, ensure_ascii=False)) else: return web.Response(body=json.dumps( { 'code': 0, 'message': '未找到与系统名称对应的脚本,请确认系统名称是否正确,脚本是否存在!', 'data': None }, ensure_ascii=False)) else: post_data = await request.json() system_name = request.query.get('systemName') # 待执行测试的系统根路径 case_path = os.path.join(cfg.getConfig('case_path'), system_name) # 测试用例路径 record_path = os.path.join(case_path, cfg.getConfig('record_name')) # 测试结果记录路径 build_file = os.path.join(cfg.getConfig('case_path'), system_name, 'build.xml') # build.xml路径 email_file = os.path.join(case_path, 'email_default.txt') # 邮件默认配置文件 config_file = os.path.join(case_path, 'config_default.txt') # jmx执行的配置文件对应的默认配置文件 new_email_file = os.path.join(case_path, 'email.txt') # 邮件配置文件 new_config_file = os.path.join(case_path, 'config.txt') # jmx执行的配置文件 if os.path.exists(case_path): if not os.path.exists(record_path): f = open(record_path, 'a') f.close() if post_data.get('email'): replace_email(email_file, post_data['email'], new_email_file) if post_data.get('params'): replace_config(config_file, post_data['params'], new_config_file) if os.path.exists(build_file): schedule.task = [case_path, new_email_file] return web.Response(body=json.dumps( { 'code': 1, 'message': '操作成功,测试任务正在准备执行', 'data': None }, ensure_ascii=False)) else: return web.Response(body=json.dumps( { 'code': 0, 'message': 'build.xml文件不存在,测试任务执行失败', 'data': None }, ensure_ascii=False)) else: return web.Response(body=json.dumps( { 'code': 0, 'message': '未找到与系统名称对应的脚本,请确认系统名称是否正确,脚本是否存在!', 'data': None }, ensure_ascii=False))
def sendMsg(html, paths, failure_num=1, is_path=True, is_send=True): """ 发送邮件 :param html: 邮件正文用到的html文件路径,或者html :param paths: :param failure_num: 失败的用例数 :param is_path: bool,True表示html是一个路径,False表示html是html :param is_send: bool,是否发邮件,仅用于第一次发送失败后,再次发送 :return: """ flag = 0 is_email = int(cfg.getConfig('is_email')) if is_email: if is_email == 1: flag = 1 if is_email == 2: if failure_num > 0: flag = 1 else: logger.info('所有用例执行成功,不发送邮件,已跳过。') if is_email == 3: if failure_num == 0: flag = 1 else: logger.info('有执行失败的用例,不发送邮件,已跳过。') else: logger.info('设置为不自动发送邮件,已跳过。') if flag: try: if paths["method"] == "GET": shutil.copy(paths["email_file"], paths["new_email_file"]) else: if paths["post_data"].get('email'): replace_email(paths["email_file"], paths["post_data"]['email'], paths["new_email_file"]) email_dict = json.load(open(paths["new_email_file"], 'r', encoding='utf-8')) subject = email_dict['subject'] send_to = email_dict['receiveEmail'] receive_name = email_dict['receiveName'] logger.info('开始发送邮件,收件人{}'.format(send_to)) message = MIMEMultipart() message['From'] = Header(cfg.getConfig('sender_name')) # 发件人名字 message['To'] = Header(receive_name) # 收件人名字 message['Subject'] = Header(subject, 'utf-8') # 邮件主题 if is_path: with open(html, 'r', encoding='utf-8') as f: fail_case = f.read() else: fail_case = html email_text = MIMEText(fail_case, 'html', 'utf-8') message.attach(email_text) # 添加邮件正文 try: server = smtplib.SMTP_SSL(cfg.getConfig('smtp'), 465) except Exception as err: logger.error(err) server = smtplib.SMTP(cfg.getConfig('smtp'), 25) server.login(cfg.getConfig('sender_email'), '123456') # 登陆邮箱 server.sendmail(cfg.getConfig('sender_email'), send_to.split(','), message.as_string()) # 发送邮件 server.quit() del fail_case, email_text, message, server logger.info('邮件发送成功') except Exception as err: logger.error(err) if is_send: sendMsg(html, paths, is_path=is_path, is_send=False) # 发送失败后,重发一次
def parse_html(self, file_name, case_path): """ 提取自动生成的测试报告中的一些信息,重组测试报告用于邮件发送 :param case_path: 测试用例路径 :param file_name: 测试报告名称 :return: """ try: all_case = os.path.join(cfg.getConfig('report_path'), file_name) # 完整的测试报告路径 fail_case = os.path.join(cfg.getConfig('report_path'), 'send_' + file_name) # 处理好用于邮件发送的测试报告路径 logger.info('开始处理html测试报告{}'.format(all_case)) with open(all_case, 'r', encoding='utf-8') as f: htmls = f.readlines() html = '' for line in htmls: html += line.strip() # 提取用例总数,成功率数据 case_num = re.findall( '响应时间最大值</th>.*<td align="center">(\d+)</td><td align="center">(\d+)</td>' '<td align="center">\d{1,3}.\d+%', html)[0] total_num = [int(case_num[0])] failure_num = [int(case_num[1])] # 提取出概览和失败用例,用于邮件发送 # res = re.findall('(.*?)<h2>所有用例', html)[0] res = html.split('<h2>所有用例')[0] url = 'http://{}:{}/testReport/{}'.format(cfg.getConfig('host'), cfg.getConfig('port'), file_name) logger.info(f'详细测试报告跳转链接为 {url}') # 添加完整测试报告路径跳转链接 jump_url = f'<span style="font-size: 125%; margin-left: 2.5%;">如需查看详细测试结果,<a href="{url}">请点我</a></span>' # 添加历史数据 self.lock.acquire() with open(os.path.join(case_path, cfg.getConfig('record_name')), 'r', encoding='utf-8') as f: history = f.readlines() self.lock.release() for line in history: datas = line.split(',') total_num.append(int(datas[-2])) failure_num.append(int(datas[-1])) ratio = 100 - round(100 * sum(failure_num) / sum(total_num), 2) history = f'<hr align="center" width="95%" size="1"><h2>历史数据概览</h2><table width="95%" cellspacing="2" ' \ f'cellpadding="5" border="0" class="details" align="center"><tbody><tr valign="top"><th>累计执行次数' \ f'</th><th>累计执行用例数</th><th>累计执行失败用例数</th><th>执行成功率</th></tr><tr valign="top" ' \ f'style="font-weight: bold;"><td align="center">{len(total_num)}</td><td align="center">{sum(total_num)}</td>' \ f'<td align="center">{sum(failure_num)}</td><td align="center">{ratio}%</td></tr></tbody></table>' res1 = re.sub('<span>(.*?)</span>', jump_url + history, res) # 添加尾巴 res = res1 + '<p style="color:blue;font-size: 125%;">此邮件自动发出,请勿回复。</p></body></html>' # 写到本地 with open(fail_case, 'w', encoding='utf-8') as f: f.writelines(res) logger.info('html测试报告处理完成') return { 'all_case': all_case, 'fail_case': fail_case, 'total_num': total_num[0], 'failure_num': failure_num[0] } except Exception as err: logger.error(err)
def __init__(self): self.interval = int(cfg.getConfig('interval')) self.tasking = [] t = Thread(target=self.lookup) t.start()
def run(self, case_email_path): """ 执行测试任务 :param case_email_path: 列表,第一个元素是测试用例文件路径,第二个元素是收件人的txt文件路径 :return: """ try: if int(cfg.getConfig('is_git')): logger.info('准备从git上拉取最新版本') repo = Repo(cfg.getConfig('git_path')) remote = repo.remote() remote.pull() logger.info('从git上拉取版本成功') except Exception as err: logger.error(err) file_name = None error_msg = None case_path = case_email_path[0] email_path = case_email_path[1] build_path = os.path.join(case_path, 'build.xml') logger.info(f'开始执行测试任务{build_path}') try: start_time = time.strftime('%Y-%m-%d %H:%M:%S') res = os.popen( 'ant -f {}'.format(build_path)).readlines() # 执行测试,并等待测试完成 logger.debug(res) length = len(res) for i in range(length, -1, -1): if 'Failed' in res[i]: # 如果有失败日志,打印出 error_msg = '{}\n{}'.format(res[i], res[i - 1]) logger.error(error_msg) break if 'xslt' in res[i] and 'Processing' in res[i] and 'to' in res[ i]: # 获取测试报告文件名 line = res[i].strip() logger.debug(line) if '/' in line: file_name = line.split('/')[-1] else: file_name = line.split('\\')[-1] logger.info(file_name) break del res if file_name: logger.info('测试任务执行完成') time.sleep(2) msg = self.parse_html(file_name, case_path) # 重组html sendMsg(msg['fail_case'], email_path, failure_num=msg['failure_num']) # 发送邮件 string = f"{start_time},{build_path},{msg['total_num']},{msg['failure_num']}\n" self.lock.acquire() logger.info(f'写测试记录到本地, {string}') with open(os.path.join(case_path, cfg.getConfig('record_name')), 'a', encoding='utf-8') as f: f.write(string) self.lock.release() logger.info('测试完成') else: error_msg = 'html格式的测试报告未找到' except Exception as err: error_msg = err logger.error(err) if error_msg: logger.error(f'测试任务执行失败,失败信息:{error_msg}') html = f'<html><body>' \ f'<h3>异常提醒:{build_path} 测试任务执行失败,请重新执行! 失败信息:{error_msg}</h3>' \ f'<p style="color:blue;">此邮件自动发出,请勿回复。</p></body></html>' try: sendMsg(html, email_path, is_path=False) except Exception as err: logger.error(err)