diff --git a/common/mail_sender.py b/common/mail_sender.py index af246fe..e1508f4 100644 --- a/common/mail_sender.py +++ b/common/mail_sender.py @@ -16,18 +16,17 @@ from common.exceptions import SendMessageError class MailSender: """发送邮件功能""" - def __init__(self, mail_subject, sender_mail_address, sender_username, sender_password, receiver_mail_list, + def __init__(self, mail_subject, sender_username, sender_password, receiver_mail_list, smtp_domain, smtp_port): - self.subject = mail_subject - self.sender_mail_address = sender_mail_address - self.sender_username = sender_username - self.sender_password = sender_password - self.receiver_mail_list = receiver_mail_list - self.smtp_domain = smtp_domain - self.smtp_port = smtp_port + self.subject = mail_subject # 邮件标题 + self.sender_username = sender_username # 发件人邮箱 + self.sender_password = sender_password # 发件人邮箱授权码 + self.receiver_mail_list = receiver_mail_list # 收件人邮箱 + self.smtp_domain = smtp_domain # 发送邮箱的域名 + self.smtp_port = smtp_port # 发送邮箱的端口号 self.message = MIMEMultipart() - self.message['From'] = Header(self.sender_mail_address, 'utf-8') + self.message['From'] = Header(self.sender_username, 'utf-8') self.message['To'] = ';'.join(self.receiver_mail_list) self.message['subject'] = Header(self.subject, 'utf-8') @@ -67,9 +66,9 @@ class MailSender: return key return 'application/octet-stream' # 一切未知类型 - def attach_text(self, text_to_send): + def attach_text(self, content): """添加邮件正文内容""" - self.message.attach(MIMEText(text_to_send, 'html', 'utf-8')) + self.message.attach(MIMEText(content, 'html', 'utf-8')) return self def attach_file(self, file_path): @@ -87,7 +86,7 @@ class MailSender: # 使用with可以加入超时等待30s,并且发送完成后自动关闭链接,省去了smtp_obj.quit()步骤 with smtplib.SMTP_SSL(self.smtp_domain, self.smtp_port, timeout=30) as smtp_obj: smtp_obj.login(self.sender_username, self.sender_password) - smtp_obj.sendmail(from_addr=self.sender_mail_address, to_addrs=self.receiver_mail_list, + smtp_obj.sendmail(from_addr=self.sender_username, to_addrs=self.receiver_mail_list, msg=self.message.as_string()) logger.info("发送邮件成功") except smtplib.SMTPException as e: @@ -99,6 +98,6 @@ class MailSender: if __name__ == '__main__': - MailSender('测试邮件发送', 'xxxxx@xxxx.com', 'xxxxx@xxxx.com', 'xxxxxxx', + MailSender('测试邮件发送', 'xxxxx@xxxx.com', 'xxxxxxx', ['xxxxx@xxxx.com'], 'smtp.exmail.qq.com', 465).attach_text( '测试邮件').attach_file('/Users/wangjie/SensoroApi/outFiles/pytest_report/report.html').send() diff --git a/common/robot_sender.py b/common/robot_sender.py index 92d3a2e..fd3850d 100644 --- a/common/robot_sender.py +++ b/common/robot_sender.py @@ -5,23 +5,9 @@ # @File : robot_sender.py # @project : SensoroApi -import os import requests from common.base_log import logger from common.exceptions import SendMessageError, ValueTypeError -from common.settings import ENV -from utils.reportdatahandle import ReportDataHandle - - -def get_env_from_jenkins(name, base=''): - """从Jenkins中获取全局环境变量""" - return os.getenv(name) and os.getenv(name).strip() or base - - -ProjectName = get_env_from_jenkins("JOB_NAME") # Jenkins构建项目名称 -BUILD_URL = get_env_from_jenkins("BUILD_URL") # Jenkins构建项目URL -BUILD_NUMBER = get_env_from_jenkins("BUILD_NUMBER") # Jenkins构建编号 -ALLURE_URL = BUILD_URL + 'allure/' # Jenkins构建的allure报告地址 class EnterpriseWechatNotification: @@ -31,7 +17,6 @@ class EnterpriseWechatNotification: # 企业微信群机器人的hook地址,一个机器人就一个,多个就定义多个,可以写死,也可以写在配置类中 self.hook_urls = hook_urls self.header = {'Content-Type': 'application/json'} - self.pytest_result = ReportDataHandle.pytest_json_report_case_count() def send_text(self, content, mentioned_mobile_list=None): """ @@ -57,29 +42,12 @@ class EnterpriseWechatNotification: else: raise ValueTypeError("手机号码列表必须是list类型.") - def send_markdown(self, content=''): + def send_markdown(self, content): """ 发送markdown消息 :param content: markdown格式内容 :return: """ - content = f"""******用例执行结果统计****** - > 项目名称:{ProjectName} - > 构件编号:#{BUILD_NUMBER} - > 测试环境:{ENV.name} - > 总用例数:{self.pytest_result['total_case']}条 - > 通过用例数:{self.pytest_result['pass_case']}条 - > 失败用例数:{self.pytest_result['fail_case']}条 - > 报错用例数:{self.pytest_result['error_case']}条 - > 跳过用例数:{self.pytest_result['skip_case']}条 - > 预期失败用例数:{self.pytest_result['xfail_case']}条 - > 预期通过用例数:{self.pytest_result['xpass_case']}条 - > 通过率:{self.pytest_result['pass_rate']}% - > 用例执行时长:{self.pytest_result['case_duration']}s - > 测试报告,点击查看>>[测试报告入口]({ALLURE_URL}) - > 构建详情,点击查看>>[控制台入口]({BUILD_URL}) - > {content}""" - payload = { "msgtype": "markdown", "markdown": { @@ -130,5 +98,4 @@ class EnterpriseWechatNotification: if __name__ == '__main__': EnterpriseWechatNotification( - ['hook_url']).send_markdown( - '<@汪杰>') + ['hook_url']).send_markdown('<@汪杰>') diff --git a/common/settings.py b/common/settings.py index 5d27ed0..d0dd4e2 100644 --- a/common/settings.py +++ b/common/settings.py @@ -6,8 +6,16 @@ # @project : SensoroApi from common.models import Environment +from utils.jenkins_handle import ProjectName, BUILD_NUMBER, ALLURE_URL, BUILD_URL +from utils.reportdatahandle import ReportDataHandle # 设置运行的环境变量 +''' +开发环境:Environment.DEV +测试环境:Environment.TEST +生产环境:Environment.PROD +点军环境:Environment.DIANJUN +''' ENV = Environment.TEST # 设置是否需要发送邮件:Ture发送,False不发送 @@ -17,7 +25,70 @@ IS_SEND_EMAIL = False IS_SEND_WECHAT = False # 设置是否开启debug日志 -LOG_DEBUG = False +LOG_DEBUG = True # 设置是否开启控制台日志 LOG_CONSOLE = True + +# ------------------------------------ 邮件配置信息 ----------------------------------------------------# + +# 发送邮件的相关配置信息 +email_config = { + 'mail_subject': '接口自动化测试报告', # 邮件标题 + 'sender_username': 'xxxxx@qq.com', # 发件人邮箱 + 'sender_password': 'ASDsdasda', # 发件人邮箱授权码 + 'receiver_mail_list': ['xxxxx@qq.com', ], # 收件人邮箱 + 'smtp_domain': 'smtp.exmail.qq.com', # 发送邮箱的域名 + 'smtp_port': 465, # 发送邮箱的端口号 +} + +# TODO:使用模板替换的方式替换该部分内容,否则此处会读取上次执行的报告 +# 邮件通知内容 +pytest_result = ReportDataHandle.pytest_json_report_case_count() +email_content = f""" + 各位同事, 大家好:
+ + 自动化用例于 {pytest_result["case_start_time"]} 开始运行,运行时长:{pytest_result['case_duration']}s, 目前已执行完成。
+ ---------------------------------------------------------------------------------------------------------------
+ 项目名称:{ProjectName}
+ 构件编号:#{BUILD_NUMBER}
+ 项目环境:{ENV.name}
+ ---------------------------------------------------------------------------------------------------------------
+ 执行结果如下:
+   用例运行总数: {pytest_result['total_case']}条
+   通过用例个数(passed): {pytest_result['pass_case']}条
+   失败用例个数(failed): {pytest_result['fail_case']}条
+   报错用例个数(error): {pytest_result['error_case']}条
+   跳过用例个数(skipped): {pytest_result['skip_case']}条
+   预期失败用例个数(xfail): {pytest_result['xfail_case']}条
+   预期通过用例个数(xpass): {pytest_result['xpass_case']}条
+   通过率: {pytest_result['pass_rate']}%
+   测试报告,点击查看: [测试报告入口]
+   构建详情,点击查看: [控制台入口]
+ + **********************************
+ 附件为具体的测试报告,详细情况可下载附件查看, 非相关负责人员可忽略此消息。谢谢。 + """ + +# ------------------------------------ 企业微信相关配置 ----------------------------------------------------# +# 企业微信通知群聊 +wechat_webhook_url = ["https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxxxxxx"] + +# 企业微信通知内容 +wechat_content = f"""******用例执行结果统计****** + > 项目名称:{ProjectName} + > 构件编号:#{BUILD_NUMBER} + > 测试环境:{ENV.name} + > 总用例数:{pytest_result['total_case']}条 + > 通过用例数:{pytest_result['pass_case']}条 + > 失败用例数:{pytest_result['fail_case']}条 + > 报错用例数:{pytest_result['error_case']}条 + > 跳过用例数:{pytest_result['skip_case']}条 + > 预期失败用例数:{pytest_result['xfail_case']}条 + > 预期通过用例数:{pytest_result['xpass_case']}条 + > 通过率:{pytest_result['pass_rate']}% + > 用例开始时间:{pytest_result["case_start_time"]} + > 用例执行时长:{pytest_result['case_duration']}s + > 测试报告,点击查看>>[测试报告入口]({ALLURE_URL}) + > 构建详情,点击查看>>[控制台入口]({BUILD_URL}) + > <@汪杰>""" diff --git a/configs/dir_path_config.py b/configs/dir_path_config.py index 5098c0a..5b92154 100644 --- a/configs/dir_path_config.py +++ b/configs/dir_path_config.py @@ -7,31 +7,46 @@ import os -# 当前项目的路径 +# 当前项目的根路径 BASE_DIR = os.path.split(os.path.split(os.path.abspath(__file__))[0])[0] + # common目录的路径 COMMON_DIR = os.path.join(BASE_DIR, 'common') + # configs目录的路径 CONFIGS_DIR = os.path.join(BASE_DIR, 'configs') + # datas目录的路径 DATAS_DIR = os.path.join(BASE_DIR, 'datas') + # pageApi目录的路径 PAGE_API_DIR = os.path.join(BASE_DIR, 'pageApi') + # testCase目录的路径 TEST_CASE_DIR = os.path.join(BASE_DIR, 'testCase') + # utils目录的路径 UTILS_DIR = os.path.join(BASE_DIR, 'utils') + # outFiles目录的路径 OUT_FILES_DIR = os.path.join(BASE_DIR, 'outFiles') + # logs目录的路径 LOGS_DIR = os.path.join(OUT_FILES_DIR, 'logs') + # pytest_report目录的路径 PYTEST_REPORT_DIR = os.path.join(OUT_FILES_DIR, 'pytest_report') + # pytest_result目录的路径 PYTEST_RESULT_DIR = os.path.join(OUT_FILES_DIR, 'pytest_result') + # allure_report目录的路径 ALLURE_REPORT_DIR = os.path.join(OUT_FILES_DIR, 'allure_report') + # screeShot目录的路径 SCREENSHOT_DIR = os.path.join(OUT_FILES_DIR, 'screeShot') + # Temp目录的路径 TEMP_DIR = os.path.join(OUT_FILES_DIR, 'Temp') +if not os.path.exists(TEMP_DIR): + os.makedirs(TEMP_DIR) diff --git a/run.py b/run.py index 15c0700..5429bd5 100755 --- a/run.py +++ b/run.py @@ -17,11 +17,10 @@ from common.base_log import logger from utils.command_parser import command_parser from common.mail_sender import MailSender from common.robot_sender import EnterpriseWechatNotification -from common.settings import IS_SEND_EMAIL, IS_SEND_WECHAT -from configs.dir_path_config import BASE_DIR, TEMP_DIR, ALLURE_REPORT_DIR, PYTEST_REPORT_DIR, CONFIGS_DIR, \ - PYTEST_RESULT_DIR +from common.settings import IS_SEND_EMAIL, IS_SEND_WECHAT, wechat_webhook_url, wechat_content, email_content, \ + email_config +from configs.dir_path_config import BASE_DIR, TEMP_DIR, PYTEST_REPORT_DIR, PYTEST_RESULT_DIR, ALLURE_REPORT_DIR from utils.file_handle import FileHandle -from utils.yaml_handle import YamlHandle if __name__ == '__main__': logger.info(""" @@ -45,9 +44,9 @@ if __name__ == '__main__': 'testCase/', # 执行用例的目录 '--alluredir', f'{TEMP_DIR}', '--clean-alluredir', # 先清空旧的alluredir目录,再将生成Allure原始报告需要的数据,并存放在 /Temp 目录 f'--html={os.path.join(PYTEST_REPORT_DIR, "pytest_report.html")}', # 指定pytest-html报告的存放位置 + '--self-contained-html', # 将css样式合并到pytest-html报告文件中,便于发送邮件 '--json-report', '--json-report-summary', # 生成简化版json报告 f'--json-report-file={os.path.join(PYTEST_RESULT_DIR, "pytest_result.json")}', # 指定json报告存放位置 - '--self-contained-html', # 将css样式合并到pytest-html报告文件中,便于发送邮件 '--capture=no', # 捕获stderr和stdout,这里是使pytest-html中失败的case展示错误日志,会导致case中的print不打印 # '-p', 'no:logging', # 表示禁用logging插件,使报告中不显示log信息,只会显示stderr和stdoyt信息,避免log和stderr重复。 '-p', 'no:sugar', # 禁用pytest-sugar美化控制台结果 @@ -68,25 +67,22 @@ if __name__ == '__main__': # 发送企业微信群聊 if IS_SEND_WECHAT: # 判断是否需要发送企业微信 - EnterpriseWechatNotification( - [ - 'hook_url']).send_markdown( - "<@汪杰>") + EnterpriseWechatNotification(wechat_webhook_url).send_markdown(wechat_content) - # 发送邮件 + # 发送邮件 if IS_SEND_EMAIL: # 判断是否需要发送邮件 file_path = PYTEST_REPORT_DIR + os.sep + 'pytest_report.html' - with open(file_path, 'rb') as f: - text_to_send = f.read() + # with open(file_path, 'rb') as f: + # text_to_send = f.read() - config = YamlHandle(CONFIGS_DIR + os.sep + 'mail_config.yaml').read_yaml() + # config = YamlHandle(CONFIGS_DIR + os.sep + 'mail_config.yaml').read_yaml() + config = email_config ms = MailSender( mail_subject=config['mail_subject'], - sender_mail_address=config['sender_mail_address'], sender_username=config['sender_username'], sender_password=config['sender_password'], receiver_mail_list=config['receiver_mail_list'], smtp_domain=config['smtp_domain'], smtp_port=config['smtp_port'], ) - ms.attach_text(text_to_send).attach_file(file_path).send() + ms.attach_text(email_content).attach_file(file_path).send() diff --git a/utils/jenkins_handle.py b/utils/jenkins_handle.py new file mode 100644 index 0000000..6dff32e --- /dev/null +++ b/utils/jenkins_handle.py @@ -0,0 +1,18 @@ +# !/usr/bin/python +# -*- coding:utf-8 -*- +# @Time : 2023/9/8 19:13 +# @Author : wangjie +# @File : jenkins_handle.py +# @project : SensoroApiAutoTest +import os + + +def get_env_from_jenkins(name, base=''): + """从Jenkins中获取全局环境变量""" + return os.getenv(name) and os.getenv(name).strip() or base + + +ProjectName = get_env_from_jenkins("JOB_NAME") # Jenkins构建项目名称 +BUILD_URL = get_env_from_jenkins("BUILD_URL") # Jenkins构建项目URL +BUILD_NUMBER = get_env_from_jenkins("BUILD_NUMBER") # Jenkins构建编号 +ALLURE_URL = BUILD_URL + 'allure/' # Jenkins构建的allure报告地址 diff --git a/utils/reportdatahandle.py b/utils/reportdatahandle.py index f365470..815890f 100644 --- a/utils/reportdatahandle.py +++ b/utils/reportdatahandle.py @@ -8,6 +8,7 @@ import json import os from configs.dir_path_config import BASE_DIR, PYTEST_RESULT_DIR +from utils.time_utils import TimeUtil class ReportDataHandle: @@ -21,7 +22,7 @@ class ReportDataHandle: @staticmethod def pytest_json_report_case_count(): """统计pytest_json_report报告收集的case数量""" - with open(PYTEST_RESULT_DIR + os.sep+'pytest_result.json', 'r', encoding='utf-8') as f: + with open(PYTEST_RESULT_DIR + os.sep + 'pytest_result.json', 'r', encoding='utf-8') as f: pytest_result = json.loads(f.read()) case_count = {} case_count["total_case"] = pytest_result['summary'].get("total", 0) # 用例总数 @@ -40,6 +41,7 @@ class ReportDataHandle: # 如果未运行用例,则成功率为 0.0 case_count["pass_rate"] = 0.0 case_count["case_duration"] = round(pytest_result["duration"], 2) # 用例运行时间 + case_count["case_start_time"] = TimeUtil.unix_time_to_str(int('%.f' % pytest_result["created"])) # 用例开始时间 return case_count