# -*- coding: utf-8 -*-
|
"""
|
@File : message_template.py
|
@Time : 2023/11/21 17:29
|
@Author : geekbing
|
@LastEditTime : -
|
@LastEditors : -
|
@Description : 消息模板
|
"""
|
from typing import Dict
|
from django.conf import settings
|
import time
|
from datetime import datetime
|
|
|
def parse_message(summary: Dict, **kwargs):
|
"""
|
解析消息模板
|
|
:param summary: 测试报告摘要
|
:param kwargs: 其他参数
|
:return:
|
"""
|
task_name = summary["task_name"]
|
rows_count = summary["stat"]["testsRun"]
|
pass_count = summary["stat"]["successes"]
|
fail_count = summary["stat"]["failures"]
|
error_count = summary["stat"]["errors"]
|
skipped_count = summary["stat"].get("skipped", 0)
|
expected_failures_count = summary["stat"].get("expectedFailures", 0)
|
unexpected_successes_count = summary["stat"].get("unexpectedSuccesses", 0)
|
duration = "%.2fs" % summary["time"]["duration"]
|
report_id = summary["report_id"]
|
base_url = settings.IM_REPORT_SETTING.get("base_url")
|
port = settings.IM_REPORT_SETTING.get("port")
|
report_url = f"{base_url}:{port}/api/lunarlink/reports/{report_id}"
|
executed = rows_count
|
fail_rate = "{:.2%}".format(fail_count / executed) if executed > 0 else "0.00%"
|
success_rate = "{:.2%}".format(pass_count / executed) if executed > 0 else "0.00%"
|
case_count = kwargs.get("case_count")
|
creator = kwargs.get("creator", "")
|
updater = kwargs.get("updater", "")
|
|
start_at = summary.get("time", {}).get("start_at", 0)
|
if start_at:
|
start_time = datetime.fromtimestamp(start_at).strftime("%Y-%m-%d %H:%M:%S")
|
end_time = datetime.fromtimestamp(start_at + summary["time"]["duration"]).strftime("%Y-%m-%d %H:%M:%S")
|
else:
|
start_time = ""
|
end_time = ""
|
|
setup_hooks_duration = summary.get("time", {}).get("setup_hooks_duration", 0)
|
teardown_hooks_duration = summary.get("time", {}).get("teardown_hooks_duration", 0)
|
|
platform_info = summary.get("platform", {})
|
python_version = platform_info.get("python_version", "")
|
httprunner_version = platform_info.get("httprunner_version", "")
|
|
avg_duration = 0
|
if executed > 0:
|
avg_duration = summary["time"]["duration"] / executed
|
|
failed_cases = []
|
error_cases = []
|
|
details = summary.get("details", [])
|
for detail in details:
|
records = detail.get("records", [])
|
for record in records:
|
if record.get("status") in ["failure", "error"]:
|
case_name = record.get("name", "未知用例")
|
case_status = record.get("status", "")
|
meta_data = record.get("meta_data", {})
|
case_url = meta_data.get("request", {}).get("url", "")
|
case_method = meta_data.get("request", {}).get("method", "")
|
case_error = record.get("attachment", "")
|
|
if case_error:
|
case_error_lines = case_error.split('\n')
|
if len(case_error_lines) > 1:
|
case_error = case_error_lines[0]
|
if len(case_error_lines) > 2:
|
case_error += f"\n... (共{len(case_error_lines)}行错误信息)"
|
case_error = case_error.strip()
|
else:
|
case_error = "无错误信息"
|
|
case_info = {
|
"name": case_name,
|
"url": case_url,
|
"method": case_method,
|
"error": case_error[:150] if case_error else "无错误信息"
|
}
|
|
if case_status == "failure":
|
failed_cases.append(case_info)
|
elif case_status == "error":
|
error_cases.append(case_info)
|
|
failed_cases = failed_cases[:10]
|
error_cases = error_cases[:10]
|
|
return {
|
"task_name": task_name,
|
"duration": duration,
|
"case_count": case_count,
|
"pass_count": pass_count,
|
"error_count": error_count,
|
"fail_count": fail_count,
|
"fail_rate": fail_rate,
|
"success_rate": success_rate,
|
"report_url": report_url,
|
"creator": creator,
|
"updater": updater,
|
"start_time": start_time,
|
"end_time": end_time,
|
"skipped_count": skipped_count,
|
"expected_failures_count": expected_failures_count,
|
"unexpected_successes_count": unexpected_successes_count,
|
"setup_hooks_duration": setup_hooks_duration,
|
"teardown_hooks_duration": teardown_hooks_duration,
|
"python_version": python_version,
|
"httprunner_version": httprunner_version,
|
"avg_duration": avg_duration,
|
"failed_cases": failed_cases,
|
"error_cases": error_cases,
|
}
|
|
|
def email_msg_template(
|
task_name,
|
duration,
|
case_count,
|
pass_count,
|
error_count,
|
fail_count,
|
fail_rate,
|
report_url,
|
success_rate,
|
creator,
|
updater,
|
start_time,
|
failed_cases,
|
error_cases,
|
end_time,
|
skipped_count,
|
expected_failures_count,
|
unexpected_successes_count,
|
setup_hooks_duration,
|
teardown_hooks_duration,
|
python_version,
|
httprunner_version,
|
avg_duration,
|
):
|
"""
|
定制邮件报告消息模板(高级优化版)
|
|
:param task_name: 任务名称
|
:param duration: 总耗时
|
:param case_count: 用例个数
|
:param pass_count: 成功接口个数
|
:param error_count: 异常接口个数
|
:param fail_count: 失败接口个数
|
:param fail_rate: 失败比例
|
:param report_url: 报告链接
|
:param success_rate: 成功率
|
:param creator: 创建人
|
:param updater: 更新人
|
:param start_time: 开始时间
|
:param failed_cases: 失败用例列表
|
:param error_cases: 异常用例列表
|
:param end_time: 结束时间
|
:param skipped_count: 跳过用例数
|
:param expected_failures_count: 预期失败数
|
:param unexpected_successes_count: 意外成功数
|
:param setup_hooks_duration: 前置钩子耗时
|
:param teardown_hooks_duration: 后置钩子耗时
|
:param python_version: Python版本
|
:param httprunner_version: HttpRunner版本
|
:param avg_duration: 平均用例耗时
|
:return: 邮件主题及 HTML 内容
|
"""
|
email_subject = f"【自动化测试报告】{task_name}"
|
|
status_icon = "✅" if fail_count == 0 and error_count == 0 else ("⚠️" if fail_count == 0 else "❌")
|
|
if error_count == 0 and fail_count == 0:
|
status_text = "测试通过"
|
status_color = "#2ecc71"
|
status_bg = "#e8f8f5"
|
elif fail_count == 0:
|
status_text = "部分异常"
|
status_color = "#f39c12"
|
status_bg = "#fef5e7"
|
else:
|
status_text = "测试失败"
|
status_color = "#e74c3c"
|
status_bg = "#fdedec"
|
|
failed_cases_html = ""
|
if failed_cases:
|
failed_cases_html = "<div class='case-list'>"
|
for idx, case in enumerate(failed_cases, 1):
|
failed_cases_html += f"""
|
<div class='case-item'>
|
<div class='case-header'>
|
<span class='case-index'>{idx}</span>
|
<span class='case-name'>{case['name']}</span>
|
</div>
|
<div class='case-details'>
|
<span class='case-method'>{case['method']}</span>
|
<span class='case-url'>{case['url'][:60]}...</span>
|
</div>
|
<div class='case-error'>❌ {case['error']}</div>
|
</div>
|
"""
|
failed_cases_html += "</div>"
|
|
error_cases_html = ""
|
if error_cases:
|
error_cases_html = "<div class='case-list'>"
|
for idx, case in enumerate(error_cases, 1):
|
error_cases_html += f"""
|
<div class='case-item'>
|
<div class='case-header'>
|
<span class='case-index'>{idx}</span>
|
<span class='case-name'>{case['name']}</span>
|
</div>
|
<div class='case-details'>
|
<span class='case-method'>{case['method']}</span>
|
<span class='case-url'>{case['url'][:60]}...</span>
|
</div>
|
<div class='case-error'>⚠️ {case['error']}</div>
|
</div>
|
"""
|
error_cases_html += "</div>"
|
|
email_content = f"""<!DOCTYPE html>
|
<html lang="zh-CN">
|
<head>
|
<meta charset="UTF-8">
|
<title>自动化测试报告</title>
|
<link href="https://fonts.googleapis.com/css?family=Roboto:400,500,700&display=swap" rel="stylesheet">
|
<style>
|
body {{
|
margin: 0;
|
padding: 0;
|
background-color: #f5f7fa;
|
font-family: 'Roboto', 'Microsoft YaHei', sans-serif;
|
color: #333;
|
}}
|
.container {{
|
max-width: 800px;
|
margin: 40px auto;
|
background: #fff;
|
border-radius: 16px;
|
overflow: hidden;
|
box-shadow: 0 10px 40px rgba(0,0,0,0.1);
|
}}
|
.header {{
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
padding: 40px 30px;
|
text-align: center;
|
color: #fff;
|
}}
|
.header h1 {{
|
margin: 0;
|
font-size: 32px;
|
font-weight: 700;
|
letter-spacing: 1px;
|
}}
|
.header .subtitle {{
|
margin: 12px 0 0;
|
font-size: 16px;
|
opacity: 0.95;
|
}}
|
.status-badge {{
|
display: inline-block;
|
margin-top: 20px;
|
padding: 8px 24px;
|
background: rgba(255,255,255,0.2);
|
border-radius: 20px;
|
font-size: 14px;
|
font-weight: 500;
|
}}
|
.content {{
|
padding: 40px 50px;
|
}}
|
.section-title {{
|
font-size: 18px;
|
font-weight: 600;
|
color: #2c3e50;
|
margin-bottom: 20px;
|
padding-bottom: 10px;
|
border-bottom: 2px solid #f0f0f0;
|
}}
|
.summary-grid {{
|
display: grid;
|
grid-template-columns: repeat(4, 1fr);
|
gap: 20px;
|
margin-bottom: 30px;
|
}}
|
.summary-grid-extended {{
|
display: grid;
|
grid-template-columns: repeat(3, 1fr);
|
gap: 20px;
|
margin-bottom: 30px;
|
}}
|
.card {{
|
background: #f8f9fa;
|
border-radius: 12px;
|
padding: 24px 16px;
|
text-align: center;
|
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
}}
|
.card:hover {{
|
transform: translateY(-2px);
|
box-shadow: 0 4px 12px rgba(0,0,0,0.1);
|
}}
|
.card .label {{
|
font-size: 13px;
|
color: #7f8c8d;
|
margin-bottom: 10px;
|
display: block;
|
font-weight: 500;
|
}}
|
.card .value {{
|
font-size: 28px;
|
font-weight: 700;
|
line-height: 1;
|
}}
|
.value.success {{
|
color: #2ecc71;
|
}}
|
.value.error {{
|
color: #e74c3c;
|
}}
|
.value.warning {{
|
color: #f39c12;
|
}}
|
.value.primary {{
|
color: #667eea;
|
}}
|
.info-table {{
|
width: 100%;
|
border-collapse: collapse;
|
margin-bottom: 30px;
|
}}
|
.info-table td {{
|
padding: 12px 0;
|
border-bottom: 1px solid #f0f0f0;
|
}}
|
.info-table td:first-child {{
|
color: #7f8c8d;
|
font-weight: 500;
|
width: 120px;
|
}}
|
.info-table td:last-child {{
|
color: #2c3e50;
|
font-weight: 600;
|
}}
|
.progress-bar {{
|
width: 100%;
|
height: 8px;
|
background: #e0e0e0;
|
border-radius: 4px;
|
overflow: hidden;
|
margin: 20px 0;
|
}}
|
.progress-fill {{
|
height: 100%;
|
background: linear-gradient(90deg, #2ecc71, #27ae60);
|
border-radius: 4px;
|
transition: width 0.3s ease;
|
}}
|
.case-list {{
|
margin-top: 15px;
|
}}
|
.case-item {{
|
background: #fff;
|
border: 1px solid #e9ecef;
|
border-radius: 8px;
|
padding: 16px;
|
margin-bottom: 12px;
|
transition: box-shadow 0.2s ease;
|
}}
|
.case-item:hover {{
|
box-shadow: 0 2px 8px rgba(0,0,0,0.08);
|
}}
|
.case-header {{
|
display: flex;
|
align-items: center;
|
margin-bottom: 10px;
|
}}
|
.case-index {{
|
display: inline-flex;
|
align-items: center;
|
justify-content: center;
|
width: 24px;
|
height: 24px;
|
background: #667eea;
|
color: #fff;
|
border-radius: 50%;
|
font-size: 12px;
|
font-weight: 600;
|
margin-right: 10px;
|
}}
|
.case-name {{
|
font-weight: 600;
|
color: #2c3e50;
|
font-size: 14px;
|
flex: 1;
|
}}
|
.case-details {{
|
display: flex;
|
align-items: center;
|
gap: 10px;
|
margin-bottom: 8px;
|
}}
|
.case-method {{
|
display: inline-block;
|
padding: 4px 10px;
|
background: #e8f4f8;
|
color: #2980b9;
|
border-radius: 4px;
|
font-size: 12px;
|
font-weight: 600;
|
}}
|
.case-url {{
|
flex: 1;
|
color: #7f8c8d;
|
font-size: 12px;
|
overflow: hidden;
|
text-overflow: ellipsis;
|
white-space: nowrap;
|
}}
|
.case-error {{
|
background: #fdedec;
|
color: #c0392b;
|
padding: 10px 12px;
|
border-radius: 6px;
|
font-size: 12px;
|
font-family: 'Courier New', monospace;
|
white-space: pre-wrap;
|
word-break: break-word;
|
line-height: 1.5;
|
}}
|
.no-cases {{
|
text-align: center;
|
padding: 30px;
|
color: #95a5a6;
|
font-size: 14px;
|
}}
|
.report-btn {{
|
display: block;
|
width: 280px;
|
margin: 30px auto 0;
|
text-align: center;
|
padding: 16px;
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
color: #fff;
|
text-decoration: none;
|
border-radius: 8px;
|
font-weight: 600;
|
font-size: 16px;
|
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
|
}}
|
.report-btn:hover {{
|
transform: translateY(-2px);
|
box-shadow: 0 6px 20px rgba(102, 126, 234, 0.4);
|
}}
|
.footer {{
|
text-align: center;
|
font-size: 13px;
|
color: #95a5a6;
|
padding: 25px;
|
background: #f8f9fa;
|
border-top: 1px solid #e9ecef;
|
}}
|
.divider {{
|
height: 1px;
|
background: #e9ecef;
|
margin: 30px 0;
|
}}
|
.icon {{
|
font-size: 20px;
|
margin-right: 8px;
|
}}
|
@media (max-width: 768px) {{
|
.container {{
|
margin: 20px;
|
}}
|
.content {{
|
padding: 30px 25px;
|
}}
|
.summary-grid {{
|
grid-template-columns: repeat(2, 1fr);
|
}}
|
.summary-grid-extended {{
|
grid-template-columns: repeat(2, 1fr);
|
}}
|
}}
|
@media (max-width: 480px) {{
|
.summary-grid {{
|
grid-template-columns: 1fr;
|
}}
|
.summary-grid-extended {{
|
grid-template-columns: 1fr;
|
}}
|
.header h1 {{
|
font-size: 24px;
|
}}
|
}}
|
</style>
|
</head>
|
<body>
|
<div class="container">
|
<div class="header">
|
<h1>🚀 自动化测试报告</h1>
|
<p class="subtitle">{task_name}</p>
|
<div class="status-badge">{status_icon} {status_text}</div>
|
</div>
|
|
<div class="content">
|
<div class="section-title">📊 测试概览</div>
|
<div class="summary-grid">
|
<div class="card">
|
<span class="label">总耗时</span>
|
<span class="value primary">{duration}</span>
|
</div>
|
<div class="card">
|
<span class="label">用例总数</span>
|
<span class="value">{case_count}</span>
|
</div>
|
<div class="card">
|
<span class="label">成功率</span>
|
<span class="value success">{success_rate}</span>
|
</div>
|
<div class="card">
|
<span class="label">失败率</span>
|
<span class="value error">{fail_rate}</span>
|
</div>
|
<div class="card">
|
<span class="label">✅ 成功</span>
|
<span class="value success">{pass_count}</span>
|
</div>
|
<div class="card">
|
<span class="label">❌ 失败</span>
|
<span class="value error">{fail_count}</span>
|
</div>
|
<div class="card">
|
<span class="label">⚠️ 异常</span>
|
<span class="value warning">{error_count}</span>
|
</div>
|
<div class="card">
|
<span class="label">📈 通过率</span>
|
<span class="value success">{success_rate}</span>
|
</div>
|
</div>
|
|
<div class="summary-grid-extended">
|
<div class="card">
|
<span class="label">⏭️ 跳过</span>
|
<span class="value" style="color: #95a5a6;">{skipped_count}</span>
|
</div>
|
<div class="card">
|
<span class="label">🎯 预期失败</span>
|
<span class="value" style="color: #f39c12;">{expected_failures_count}</span>
|
</div>
|
<div class="card">
|
<span class="label">🎉 意外成功</span>
|
<span class="value" style="color: #3498db;">{unexpected_successes_count}</span>
|
</div>
|
</div>
|
|
<div class="summary-grid-extended">
|
<div class="card">
|
<span class="label">⚡ 平均耗时</span>
|
<span class="value primary">{avg_duration:.3f}s</span>
|
</div>
|
<div class="card">
|
<span class="label">🔧 前置钩子</span>
|
<span class="value" style="color: #7f8c8d;">{setup_hooks_duration:.3f}s</span>
|
</div>
|
<div class="card">
|
<span class="label">🔧 后置钩子</span>
|
<span class="value" style="color: #7f8c8d;">{teardown_hooks_duration:.3f}s</span>
|
</div>
|
</div>
|
|
<div class="divider"></div>
|
|
<div class="section-title">📋 详细信息</div>
|
<table class="info-table">
|
<tr>
|
<td><span class="icon">📝</span>任务名称</td>
|
<td>{task_name}</td>
|
</tr>
|
<tr>
|
<td><span class="icon">⏱️</span>执行耗时</td>
|
<td>{duration}</td>
|
</tr>
|
<tr>
|
<td><span class="icon">⚡</span>平均耗时</td>
|
<td>{avg_duration:.3f}s</td>
|
</tr>
|
<tr>
|
<td><span class="icon">📅</span>开始时间</td>
|
<td>{start_time if start_time else '未记录'}</td>
|
</tr>
|
<tr>
|
<td><span class="icon">🏁</span>结束时间</td>
|
<td>{end_time if end_time else '未记录'}</td>
|
</tr>
|
<tr>
|
<td><span class="icon">👤</span>创建人</td>
|
<td>{creator if creator else '未记录'}</td>
|
</tr>
|
<tr>
|
<td><span class="icon">✏️</span>更新人</td>
|
<td>{updater if updater else '未记录'}</td>
|
</tr>
|
<tr>
|
<td><span class="icon">🐍</span>Python版本</td>
|
<td>{python_version if python_version else '未记录'}</td>
|
</tr>
|
<tr>
|
<td><span class="icon">🏃</span>HttpRunner版本</td>
|
<td>{httprunner_version if httprunner_version else '未记录'}</td>
|
</tr>
|
</table>
|
|
<div class="divider"></div>
|
|
<div class="section-title">📈 测试结果分析</div>
|
<div style="background: #f8f9fa; padding: 20px; border-radius: 8px; margin-bottom: 20px;">
|
<div style="display: flex; justify-content: space-between; margin-bottom: 10px;">
|
<span style="color: #7f8c8d; font-size: 14px;">测试通过率</span>
|
<span style="color: #2ecc71; font-weight: 700; font-size: 18px;">{success_rate}</span>
|
</div>
|
<div class="progress-bar">
|
<div class="progress-fill" style="width: {success_rate};"></div>
|
</div>
|
<div style="display: flex; justify-content: space-between; font-size: 12px; color: #95a5a6;">
|
<span>成功: {pass_count}</span>
|
<span>失败: {fail_count}</span>
|
<span>异常: {error_count}</span>
|
</div>
|
</div>
|
|
{f'<div class="divider"></div><div class="section-title">❌ 失败用例列表</div>{failed_cases_html}' if failed_cases else ''}
|
{f'<div class="divider"></div><div class="section-title">⚠️ 异常用例列表</div>{error_cases_html}' if error_cases else ''}
|
|
<a href="{report_url}" class="report-btn">👉 点击查看详细报告</a>
|
</div>
|
|
<div class="footer">
|
<p>本邮件由系统自动发出,请勿回复</p>
|
<p style="margin-top: 8px;">如有疑问,请联系相关负责人</p>
|
</div>
|
</div>
|
</body>
|
</html>
|
"""
|
return {
|
"subject": email_subject,
|
"html_message": email_content,
|
}
|
|
|
def qy_msg_template(
|
task_name,
|
duration,
|
case_count,
|
pass_count,
|
error_count,
|
fail_count,
|
fail_rate,
|
report_url,
|
msg_type: str = "markdown",
|
):
|
"""
|
定制企业微信消息模板
|
|
:param task_name:
|
:param duration:
|
:param case_count:
|
:param pass_count:
|
:param error_count:
|
:param fail_count:
|
:param fail_rate:
|
:param report_url:
|
:param msg_type:
|
:return:
|
"""
|
if msg_type == "markdown":
|
msg_template = {"msgtype": "markdown", "markdown": {"content": ""}}
|
content = f"""<font color=\'warning\'>**接口自动化测试报告**</font> \n
|
>任务名称: <font color=\'comment\'>{task_name}</font>
|
>总共耗时: <font color=\'comment\'>{duration}</font>
|
>用例个数: <font color=\'comment\'>{case_count}</font>
|
>成功接口: <font color=\'info\'>**{pass_count}**</font>
|
>异常接口: <font color=\'comment\'>**{error_count}**</font>
|
>失败接口: <font color=\'comment\'>**{fail_count}**</font>
|
>失败比例: <font color=\'comment\'>**{fail_rate}**</font>
|
>测试报告: <font color=\'comment\'>[点击查看]({report_url})</font>"""
|
msg_template["markdown"]["content"] = content
|
return msg_template
|
text = f" 任务名称: {task_name}\n 总共耗时: {duration}\n 成功接口: {pass_count}个\n 异常接口: {error_count}个\n 失败接口: {fail_count}个\n 失败比例: {fail_rate}\n 查看详情: {report_url}"
|
return text
|
|
def dd_msg_template(
|
task_name,
|
duration,
|
case_count,
|
pass_count,
|
error_count,
|
fail_count,
|
fail_rate,
|
report_url,
|
msg_type: str = "markdown",
|
creator=None,
|
updater=None,
|
):
|
"""
|
定制钉钉消息模板:包含自动评价、图标提示、markdown美观布局
|
|
:param task_name: 任务名称
|
:param duration: 总耗时
|
:param case_count: 用例个数
|
:param pass_count: 成功接口个数
|
:param error_count: 异常接口个数
|
:param fail_count: 失败接口个数
|
:param fail_rate: 失败比例(字符串或百分比)
|
:param report_url: 报告链接
|
:param msg_type: 消息类型,默认 markdown
|
:return: 钉钉消息内容(字典格式)
|
"""
|
|
# 安全转换失败比例
|
try:
|
fr = float(str(fail_rate).strip().replace("%", "").replace("%", ""))
|
except Exception:
|
fr = 0.0
|
|
# 状态图标
|
status_icon = "✅" if fail_count == 0 and error_count == 0 else ("⚠️" if fail_count == 0 else "❌")
|
|
# 自动评价等级
|
if error_count == 0 and fail_count == 0:
|
evaluation = "测试结果优秀"
|
evaluation_icon = "🟢"
|
elif fr <= 5:
|
evaluation = "测试结果良好"
|
evaluation_icon = "🟡"
|
elif fr <= 10:
|
evaluation = "测试结果一般"
|
evaluation_icon = "🟠"
|
else:
|
evaluation = "测试结果严重异常"
|
evaluation_icon = "🔴"
|
|
if msg_type == "markdown":
|
content = (
|
f"## {status_icon} **接口自动化测试完成通知**\n\n"
|
f"### 📌 任务名称:**{task_name}**\n"
|
f"### 📈 自动评价:{evaluation_icon} `{evaluation}`\n\n"
|
f"> ⏱️ **总耗时:** {duration} \n"
|
f"> 📊 **执行用例数:** {case_count} \n"
|
f"> ✅ **成功接口数:** {pass_count} \n"
|
f"> ⚠️ **异常接口数:** {error_count} \n"
|
f"> ❌ **失败接口数:** {fail_count} \n"
|
f"> 📉 **失败比例:** {fail_rate} \n\n"
|
f"> 👤 **创建人:** {creator if creator else '未记录'} \n" # 新增创建人显示
|
f"> ✏️ **更新人:** {updater if updater else '未记录'} \n\n" # 新增更新人显示
|
f"🔗 [👉 点击查看详细报告]({report_url})\n\n"
|
f"📬 详细内容也可通过邮件查看。\n\n"
|
"---\n"
|
"如有异常,请及时处理!🎉"
|
)
|
return {"msgtype": "markdown", "markdown": {"content": content}}
|
|
else:
|
# 普通 text 消息 fallback
|
content = (
|
f"{status_icon} 接口自动化测试完成通知\n"
|
f"任务名称:{task_name}\n"
|
f"自动评价:{evaluation_icon} {evaluation}\n"
|
f"总耗时:{duration}\n"
|
f"执行用例数:{case_count}\n"
|
f"成功接口数:{pass_count}\n"
|
f"异常接口数:{error_count}\n"
|
f"失败接口数:{fail_count}\n"
|
f"失败比例:{fail_rate}\n"
|
f"创建人:{creator}\n"
|
f"更新人:{updater}\n"
|
f"报告地址:{report_url}\n"
|
f"详细内容请查看邮件,如有异常,请及时处理!。"
|
)
|
return {"msgtype": "text", "text": {"content": content}}
|