|
@@ -3,6 +3,7 @@
|
|
|
// TestEngine.cs
|
|
|
using System;
|
|
|
using System.Collections.Generic;
|
|
|
+using System.IO;
|
|
|
using System.IO.Ports;
|
|
|
using System.Linq;
|
|
|
using System.Text;
|
|
@@ -16,86 +17,52 @@ namespace Bird_tool
|
|
|
|
|
|
|
|
|
|
|
|
- //public class TestResult
|
|
|
- //{
|
|
|
- // public bool IsRunning { get; set; } = true;
|
|
|
- // public int TotalSteps => Steps.Count;
|
|
|
- // public int CompletedSteps { get; set; }
|
|
|
- // public string CurrentStepName { get; set; }
|
|
|
- // public string CurrentStepDetail { get; set; }
|
|
|
- // public int CurrentStepProgress { get; set; }
|
|
|
- // public int OverallProgress => CompletedSteps * 100 / TotalSteps;
|
|
|
-
|
|
|
- // public List<TestStepResult> StepResults { get; } = new List<TestStepResult>();
|
|
|
- // public List<TestStepConfig> Steps { get; set; }
|
|
|
-
|
|
|
- // public void StartStep(TestStepConfig step)
|
|
|
- // {
|
|
|
- // CurrentStepName = step.Name;
|
|
|
- // CurrentStepDetail = "开始执行...";
|
|
|
- // CurrentStepProgress = 0;
|
|
|
-
|
|
|
- // StepResults.Add(new TestStepResult
|
|
|
- // {
|
|
|
- // Name = step.Name,
|
|
|
- // Status = TestStatus.Running,
|
|
|
- // StartTime = DateTime.Now
|
|
|
- // });
|
|
|
- // }
|
|
|
-
|
|
|
- // public void CompleteStep(TestStatus status, string details = "")
|
|
|
- // {
|
|
|
- // if (StepResults.Count == 0) return;
|
|
|
-
|
|
|
- // var currentResult = StepResults.Last();
|
|
|
- // currentResult.Status = status;
|
|
|
- // currentResult.EndTime = DateTime.Now;
|
|
|
- // currentResult.Details = details;
|
|
|
-
|
|
|
- // if (status == TestStatus.Passed || status == TestStatus.Skipped)
|
|
|
- // {
|
|
|
- // CompletedSteps++;
|
|
|
- // }
|
|
|
- // }
|
|
|
-
|
|
|
- // public void FailCurrentStep(string reason)
|
|
|
- // {
|
|
|
- // CompleteStep(TestStatus.Failed, reason);
|
|
|
- // IsRunning = false;
|
|
|
- // }
|
|
|
-
|
|
|
- // public void MarkAsCompleted()
|
|
|
- // {
|
|
|
- // CompleteStep(TestStatus.Passed, "所有步骤完成");
|
|
|
- // IsRunning = false;
|
|
|
- // }
|
|
|
-
|
|
|
- // public void SkipRemaining()
|
|
|
- // {
|
|
|
- // foreach (var step in Steps.Skip(StepResults.Count))
|
|
|
- // {
|
|
|
- // StepResults.Add(new TestStepResult
|
|
|
- // {
|
|
|
- // Name = step.Name,
|
|
|
- // Status = TestStatus.Skipped,
|
|
|
- // StartTime = DateTime.Now,
|
|
|
- // EndTime = DateTime.Now
|
|
|
- // });
|
|
|
- // }
|
|
|
- // IsRunning = false;
|
|
|
- // }
|
|
|
- //}
|
|
|
+ public class TestReport
|
|
|
+ {
|
|
|
+ public DateTime StartTime { get; set; } = DateTime.Now;
|
|
|
+ public DateTime EndTime { get; set; }
|
|
|
+ public List<TestReportItem> Items { get; set; } = new List<TestReportItem>();
|
|
|
+ public string DeviceInfo { get; set; }
|
|
|
+ public string TestResult => Items.All(i => i.Status == TestStatus.Passed) ? "PASS" : "FAIL";
|
|
|
+ // 设备信息专用字典
|
|
|
+ public Dictionary<string, string> DeviceInfoItems { get; } = new Dictionary<string, string>();
|
|
|
+ public string UniqueIdentifier { get; set; } = "report";
|
|
|
+
|
|
|
+ // 添加设备信息项
|
|
|
+ public void AddDeviceInfo(string key, string value)
|
|
|
+ {
|
|
|
+ if (!string.IsNullOrEmpty(value))
|
|
|
+ {
|
|
|
+ DeviceInfoItems[key] = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public class TestReportItem
|
|
|
+ {
|
|
|
+ public string GroupId { get; set; }
|
|
|
+ public string GroupName { get; set; }
|
|
|
+ public TestStatus Status { get; set; }
|
|
|
+ public List<TestStepResult> StepResults { get; set; } = new List<TestStepResult>();
|
|
|
+ public string Details => Status == TestStatus.Passed ? "所有步骤通过" : $"失败步骤: {FailedSteps}";
|
|
|
+ public int FailedSteps => StepResults.Count(r => r.Status == TestStatus.Failed);
|
|
|
+ public DateTime StartTime { get; set; }
|
|
|
+ public DateTime EndTime { get; set; }
|
|
|
+ }
|
|
|
|
|
|
public class TestStepResult
|
|
|
{
|
|
|
public string Name { get; set; }
|
|
|
public TestStatus Status { get; set; } = TestStatus.NotRun;
|
|
|
public string Details { get; set; } = "";
|
|
|
+ public string GroupId { get; set; } = "";
|
|
|
+ public string GroupName { get; set; } = "";
|
|
|
public DateTime StartTime { get; set; }
|
|
|
public DateTime EndTime { get; set; }
|
|
|
public TimeSpan Duration => EndTime - StartTime;
|
|
|
public int RetryCount { get; set; }
|
|
|
public string ErrorMessage { get; set; } = "";
|
|
|
+
|
|
|
}
|
|
|
|
|
|
public enum TestStatus
|
|
@@ -215,6 +182,10 @@ namespace Bird_tool
|
|
|
// 失败后是否继续测试
|
|
|
public bool FailContinue { get; set; } = false;
|
|
|
|
|
|
+ public bool IsDeviceInfoItem { get; set; } = false;
|
|
|
+ public string InfoDisplayName { get; set; }
|
|
|
+ public string InfoDisplayTemplate { get; set; }
|
|
|
+
|
|
|
public string Description { get; set; }
|
|
|
|
|
|
public int DelayBefore { get; set; } = 0;
|
|
@@ -296,13 +267,15 @@ namespace Bird_tool
|
|
|
// 当前测试项的状态
|
|
|
public TestStepConfig CurrentStep => Steps?[CurrentStepIndex];
|
|
|
public List<TestStepConfig> Steps { get; set; }
|
|
|
- //public TestResult Result { get; set; }
|
|
|
+ public List<TestStepResult> StepResults { get; } = new List<TestStepResult>();
|
|
|
+ public TestReport Report { get; set; } = new TestReport();
|
|
|
public bool IsRunning { get; set; }
|
|
|
}
|
|
|
|
|
|
class TestExecutor
|
|
|
{
|
|
|
public delegate void LogHandler(string message, LogLevel level);
|
|
|
+ public delegate void LogShowHandler(string message);
|
|
|
|
|
|
public delegate void PromptHandler(string question, Action onYes, Action onNo, int waitTime = 60);
|
|
|
public delegate bool StepValidator(string response, TestContext context);
|
|
@@ -312,6 +285,7 @@ namespace Bird_tool
|
|
|
|
|
|
|
|
|
public event LogHandler OnLog;
|
|
|
+ public event LogShowHandler OnLogShow;
|
|
|
public event PromptHandler OnPrompt;
|
|
|
public event StepChangedHandler OnStepChanged;
|
|
|
public event TestFailHandler OnFailed;
|
|
@@ -327,11 +301,14 @@ namespace Bird_tool
|
|
|
public bool isStart { get; private set; } = false;
|
|
|
public bool isInit { get; private set; } = false;
|
|
|
|
|
|
+ public string ReportFileNameTemplate { get; set; } = "TestReport_{{mac}}_{{timestamp}}";
|
|
|
+
|
|
|
public TestExecutor(SerialManager serialManager)
|
|
|
{
|
|
|
bird_tool.Log("TestExecutor ");
|
|
|
_serialManager = serialManager;
|
|
|
_serialManager.OnLineReceived += HandleResponse;
|
|
|
+
|
|
|
}
|
|
|
|
|
|
// 新增方法:将重试操作加入队列异步执行
|
|
@@ -340,7 +317,7 @@ namespace Bird_tool
|
|
|
OnLog?.Invoke($"排队重试操作: {operation.Method.Name}", LogLevel.debug);
|
|
|
Task.Run(() =>
|
|
|
{
|
|
|
- OnLog?.Invoke($"开始执行排队操作: {operation.Method.Name}", LogLevel.debug);
|
|
|
+ OnLog?.Invoke($"开始执行排队操作1: {operation.Method.Name}", LogLevel.debug);
|
|
|
// 添加短暂延迟避免立即重试
|
|
|
Thread.Sleep(100);
|
|
|
|
|
@@ -349,6 +326,7 @@ namespace Bird_tool
|
|
|
// 检查测试是否仍在运行
|
|
|
if (_context != null && _context.IsRunning)
|
|
|
{
|
|
|
+ OnLog?.Invoke($"开始执行排队操作2: {operation.Method.Name}", LogLevel.debug);
|
|
|
operation();
|
|
|
}
|
|
|
}
|
|
@@ -358,6 +336,9 @@ namespace Bird_tool
|
|
|
|
|
|
public void StopTest()
|
|
|
{
|
|
|
+
|
|
|
+ Thread.Sleep(2000);
|
|
|
+
|
|
|
lock (_lock)
|
|
|
{
|
|
|
|
|
@@ -395,7 +376,7 @@ namespace Bird_tool
|
|
|
Steps = steps, // 使用传入的步骤列表
|
|
|
CurrentStepIndex = -1
|
|
|
};
|
|
|
-
|
|
|
+
|
|
|
isStart = true;
|
|
|
|
|
|
MoveToNextStep();
|
|
@@ -607,20 +588,93 @@ namespace Bird_tool
|
|
|
// 从特定方法开始执行
|
|
|
|
|
|
|
|
|
- // 新增字符串模板替换方法
|
|
|
+ // 字符串模板替换方法
|
|
|
public string ReplaceTemplateVariables(string input, Dictionary<string, string> variables)
|
|
|
{
|
|
|
+ if (string.IsNullOrWhiteSpace(input))
|
|
|
+ return input;
|
|
|
+
|
|
|
+ // 创建大小写不敏感的合并字典
|
|
|
+ var mergedVars = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase);
|
|
|
+
|
|
|
+ // 1. 添加用户提供的变量(不修改原始字典)
|
|
|
+ if (variables != null)
|
|
|
+ {
|
|
|
+ foreach (var kv in variables)
|
|
|
+ {
|
|
|
+ mergedVars[kv.Key] = kv.Value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 2. 添加系统变量(仅当用户未定义时)
|
|
|
+ AddIfMissing(mergedVars, "timestamp", DateTime.Now.ToString("yyyyMMddHHmmss"));
|
|
|
+ AddIfMissing(mergedVars, "date", DateTime.Now.ToString("yyyyMMdd"));
|
|
|
+ AddIfMissing(mergedVars, "time", DateTime.Now.ToString("HHmmss"));
|
|
|
+ AddIfMissing(mergedVars, "datetime", DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
|
|
|
+ AddIfMissing(mergedVars, "machine", Environment.MachineName);
|
|
|
+ AddIfMissing(mergedVars, "user", Environment.UserName);
|
|
|
+
|
|
|
return Regex.Replace(input, @"\{\{(\w+)\}\}", match =>
|
|
|
{
|
|
|
var varName = match.Groups[1].Value;
|
|
|
- return variables.TryGetValue(varName, out var value) ? value : $"{{{{UNDEFINED:{varName}}}}}";
|
|
|
+
|
|
|
+ // 尝试各种格式的键名匹配
|
|
|
+ if (TryGetValueIgnoreCase(mergedVars, varName, out var value))
|
|
|
+ return value;
|
|
|
+
|
|
|
+ // 尝试下划线格式(如 device_model)
|
|
|
+ var underscored = Regex.Replace(varName, @"([a-z])([A-Z])", "$1_$2").ToLower();
|
|
|
+ if (TryGetValueIgnoreCase(mergedVars, underscored, out value))
|
|
|
+ return value;
|
|
|
+
|
|
|
+ // 尝试空格格式(如 Device Model)
|
|
|
+ var spaced = Regex.Replace(varName, @"([a-z])([A-Z])", "$1 $2");
|
|
|
+ if (TryGetValueIgnoreCase(mergedVars, spaced, out value))
|
|
|
+ return value;
|
|
|
+
|
|
|
+ // 尝试驼峰格式(如 deviceModel)
|
|
|
+ var camelCase = char.ToLower(varName[0]) + varName.Substring(1);
|
|
|
+ if (TryGetValueIgnoreCase(mergedVars, camelCase, out value))
|
|
|
+ return value;
|
|
|
+
|
|
|
+ return $"{{{{UNDEFINED:{varName}}}}}";
|
|
|
});
|
|
|
}
|
|
|
+ // 辅助方法:仅在键不存在时添加
|
|
|
+ private void AddIfMissing(Dictionary<string, string> dict, string key, string value)
|
|
|
+ {
|
|
|
+ if (!dict.ContainsKey(key))
|
|
|
+ {
|
|
|
+ dict[key] = value;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 辅助方法:忽略大小写获取值
|
|
|
+ private bool TryGetValueIgnoreCase(Dictionary<string, string> dict, string key, out string value)
|
|
|
+ {
|
|
|
+ // 直接匹配
|
|
|
+ if (dict.TryGetValue(key, out value))
|
|
|
+ return true;
|
|
|
+
|
|
|
+ // 大小写不敏感匹配
|
|
|
+ var keyComparer = StringComparer.OrdinalIgnoreCase;
|
|
|
+ foreach (var kv in dict)
|
|
|
+ {
|
|
|
+ if (keyComparer.Equals(kv.Key, key))
|
|
|
+ {
|
|
|
+ value = kv.Value;
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ value = null;
|
|
|
+ return false;
|
|
|
+ }
|
|
|
|
|
|
private async void ExecuteCurrentStep()
|
|
|
{
|
|
|
var step = _context.CurrentStep;
|
|
|
-
|
|
|
+ RecordStepResult(step, TestStatus.Running, "步骤开始执行");
|
|
|
// 记录步骤开始
|
|
|
OnLog?.Invoke($"开始步骤: {step.Name}", LogLevel.info);
|
|
|
step.stepStatus = StepStatus.Running;
|
|
@@ -821,11 +875,13 @@ namespace Bird_tool
|
|
|
|
|
|
private void HandleTestFail()
|
|
|
{
|
|
|
+ HandleTestEnd(false);
|
|
|
OnFailed?.Invoke(_context.CurrentStep, _context);
|
|
|
StopTest();
|
|
|
}
|
|
|
private void HandleTestSuccess()
|
|
|
{
|
|
|
+ HandleTestEnd(true);
|
|
|
OnSuccess?.Invoke(_context);
|
|
|
StopTest();
|
|
|
}
|
|
@@ -857,10 +913,217 @@ namespace Bird_tool
|
|
|
_timeoutTimer = null;
|
|
|
var step = _context.CurrentStep;
|
|
|
step.stepStatus = StepStatus.Success;
|
|
|
+ RecordStepResult(step, TestStatus.Passed, message);
|
|
|
+ // 捕获设备信息
|
|
|
+ if (step.IsDeviceInfoItem)
|
|
|
+ {
|
|
|
+ // 使用用户指定的显示名称或默认使用步骤名
|
|
|
+ var infoKey = !string.IsNullOrEmpty(step.InfoDisplayName)
|
|
|
+ ? step.InfoDisplayName
|
|
|
+ : step.Name;
|
|
|
+
|
|
|
+ // 尝试从变量中提取信息值
|
|
|
+ string infoValue = "";
|
|
|
+
|
|
|
+ // 情况1:步骤提取了变量
|
|
|
+ if (step.VariableNames.Count > 0 &&
|
|
|
+ _context.Variables.TryGetValue(step.VariableNames[0], out var varValue))
|
|
|
+ {
|
|
|
+ infoValue = varValue;
|
|
|
+ }
|
|
|
+ // 情况2:使用成功消息
|
|
|
+ else if (!string.IsNullOrEmpty(message))
|
|
|
+ {
|
|
|
+ infoValue = message;
|
|
|
+ }
|
|
|
+ // 判断是否有 InfoDisplayTemplate 输出模板
|
|
|
+ if (!string.IsNullOrEmpty(step.InfoDisplayTemplate))
|
|
|
+ {
|
|
|
+ infoValue = ReplaceTemplateVariables(step.InfoDisplayTemplate, _context.Variables);
|
|
|
+ }
|
|
|
+
|
|
|
+ _context.Report.AddDeviceInfo(infoKey, infoValue);
|
|
|
+ }
|
|
|
+
|
|
|
+ RecordStepResult(step, TestStatus.Passed, message);
|
|
|
+
|
|
|
OnStepChanged?.Invoke(step, _context, false);
|
|
|
OnLog?.Invoke($"✅ 步骤完成: {step.Name} ({message})", LogLevel.info);
|
|
|
MoveToNextStep();
|
|
|
}
|
|
|
+ // 修改HTML生成方法,添加设备信息展示
|
|
|
+ private void SaveReportToHtml(TestReport report)
|
|
|
+ {
|
|
|
+ // 生成设备信息表格
|
|
|
+ string deviceInfoHtml = "";
|
|
|
+ if (report.DeviceInfoItems.Count > 0)
|
|
|
+ {
|
|
|
+ deviceInfoHtml = $@"
|
|
|
+ <h2>设备信息</h2>
|
|
|
+ <table class='device-info'>
|
|
|
+ {string.Join("\n", report.DeviceInfoItems.Select(kv => $@"
|
|
|
+ <tr>
|
|
|
+ <td><strong>{kv.Key}</strong></td>
|
|
|
+ <td>{kv.Value}</td>
|
|
|
+ </tr>"))}
|
|
|
+ </table>";
|
|
|
+ }
|
|
|
+
|
|
|
+ var html = $@"
|
|
|
+ <!DOCTYPE html>
|
|
|
+ <html>
|
|
|
+ <head>
|
|
|
+ <title>设备测试报告</title>
|
|
|
+ <style>
|
|
|
+ /* ... 现有样式 ... */
|
|
|
+ .device-info td:first-child {{ width: 30%; font-weight: bold; }}
|
|
|
+ .device-info td {{ vertical-align: top; padding: 6px 8px; }}
|
|
|
+ </style>
|
|
|
+ </head>
|
|
|
+ <body>
|
|
|
+ <h1>设备测试报告</h1>
|
|
|
+ <p><strong>测试结果:</strong> <span class='{report.TestResult.ToLower()}'>{
|
|
|
+ report.TestResult}</span></p>
|
|
|
+ <p><strong>开始时间:</strong> {report.StartTime:yyyy-MM-dd HH:mm:ss}</p>
|
|
|
+ <p><strong>结束时间:</strong> {report.EndTime:yyyy-MM-dd HH:mm:ss}</p>
|
|
|
+
|
|
|
+
|
|
|
+ {deviceInfoHtml}
|
|
|
+
|
|
|
+ <h2>测试项汇总</h2>
|
|
|
+ <table>
|
|
|
+ <tr>
|
|
|
+ <th>测试项</th>
|
|
|
+ <th>状态</th>
|
|
|
+ <th>开始时间</th>
|
|
|
+ <th>结束时间</th>
|
|
|
+ <th>详情</th>
|
|
|
+ </tr>
|
|
|
+ {string.Join("\n", report.Items.Select(i => $@"
|
|
|
+ <tr class='{i.Status.ToString().ToLower()}'>
|
|
|
+ <td>{i.GroupName}</td>
|
|
|
+ <td>{i.Status}</td>
|
|
|
+ <td>{i.StartTime:HH:mm:ss}</td>
|
|
|
+ <td>{i.EndTime:HH:mm:ss}</td>
|
|
|
+ <td>{i.Details}</td>
|
|
|
+ </tr>"))}
|
|
|
+ </table>
|
|
|
+
|
|
|
+ <h2>详细步骤</h2>
|
|
|
+ <table>
|
|
|
+ <tr>
|
|
|
+ <th>步骤名称</th>
|
|
|
+ <th>所属测试项</th>
|
|
|
+ <th>状态</th>
|
|
|
+ <th>开始时间</th>
|
|
|
+ <th>结束时间</th>
|
|
|
+ <th>详情</th>
|
|
|
+ </tr>
|
|
|
+ {string.Join("\n", report.Items.SelectMany(i => i.StepResults.Select(s => $@"
|
|
|
+ <tr class='{s.Status.ToString().ToLower()}'>
|
|
|
+ <td>{s.Name}</td>
|
|
|
+ <td>{i.GroupName}</td>
|
|
|
+ <td>{s.Status}</td>
|
|
|
+ <td>{s.StartTime:HH:mm:ss}</td>
|
|
|
+ <td>{s.EndTime:HH:mm:ss}</td>
|
|
|
+ <td>{s.Details}</td>
|
|
|
+ </tr>")))}
|
|
|
+ </table>
|
|
|
+ </body>
|
|
|
+ </html>";
|
|
|
+ Directory.CreateDirectory("TestReport");
|
|
|
+ var fileName = GetSafeFileName("TestReport");
|
|
|
+ File.WriteAllText(fileName, html);
|
|
|
+ string str = $"测试报告已生成: {Path.GetFullPath(fileName)}";
|
|
|
+ OnLog?.Invoke(str, LogLevel.info);
|
|
|
+ OnLogShow?.Invoke(str);
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ public string GetSafeFileName(string dir, string extension = "html")
|
|
|
+ {
|
|
|
+ // 移除非法字符
|
|
|
+ var invalidChars = Path.GetInvalidFileNameChars();
|
|
|
+ var safeName = ReplaceTemplateVariables(ReportFileNameTemplate, _context.Variables);
|
|
|
+
|
|
|
+ // 替换空格
|
|
|
+ // 替换空格和特殊字符
|
|
|
+ safeName = safeName.Replace(" ", "_")
|
|
|
+ .Replace(":", "-")
|
|
|
+ .Replace("/", "-");
|
|
|
+
|
|
|
+ // 确保长度合理
|
|
|
+ if (safeName.Length > 100)
|
|
|
+ safeName = safeName.Substring(0, 100);
|
|
|
+
|
|
|
+ // 确保非空
|
|
|
+ if (string.IsNullOrWhiteSpace(safeName))
|
|
|
+ safeName = "TestReport";
|
|
|
+
|
|
|
+ return $"{dir}/{safeName}.{extension}";
|
|
|
+ }
|
|
|
+
|
|
|
+ private void RecordStepResult(TestStepConfig step, TestStatus status, string message = "")
|
|
|
+ {
|
|
|
+ var result = new TestStepResult
|
|
|
+ {
|
|
|
+ Name = step.Name,
|
|
|
+ Status = status,
|
|
|
+ Details = message,
|
|
|
+ StartTime = DateTime.Now,
|
|
|
+ EndTime = DateTime.Now,
|
|
|
+ GroupId = step.EffectiveGroupId,
|
|
|
+ GroupName = step.GroupName
|
|
|
+ };
|
|
|
+
|
|
|
+ _context.StepResults.Add(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ private void GenerateTestReport()
|
|
|
+ {
|
|
|
+
|
|
|
+ // 按GroupId分组聚合结果
|
|
|
+ var groupedResults = _context.StepResults
|
|
|
+ .Where(r => !string.IsNullOrEmpty(r.GroupId))
|
|
|
+ .GroupBy(r => r.GroupId)
|
|
|
+ .Select(g => new TestReportItem
|
|
|
+ {
|
|
|
+ GroupId = g.Key,
|
|
|
+ GroupName = g.First().GroupName,
|
|
|
+ Status = g.Any(r => r.Status == TestStatus.Failed) ?
|
|
|
+ TestStatus.Failed : TestStatus.Passed,
|
|
|
+ StepResults = g.ToList(),
|
|
|
+ StartTime = g.Min(r => r.StartTime),
|
|
|
+ EndTime = g.Max(r => r.EndTime)
|
|
|
+ })
|
|
|
+ .ToList();
|
|
|
+
|
|
|
+ // 添加未分组步骤
|
|
|
+ var ungroupedResults = _context.StepResults
|
|
|
+ .Where(r => string.IsNullOrEmpty(r.GroupId))
|
|
|
+ .Select(r => new TestReportItem
|
|
|
+ {
|
|
|
+ GroupId = "single_" + r.Name,
|
|
|
+ GroupName = r.Name,
|
|
|
+ Status = r.Status,
|
|
|
+ StepResults = new List<TestStepResult> { r },
|
|
|
+ StartTime = r.StartTime,
|
|
|
+ EndTime = r.EndTime
|
|
|
+ });
|
|
|
+
|
|
|
+ _context.Report.Items.AddRange(groupedResults);
|
|
|
+ _context.Report.Items.AddRange(ungroupedResults);
|
|
|
+ _context.Report.EndTime = DateTime.Now;
|
|
|
+ }
|
|
|
+ // 在测试结束时调用生成报告
|
|
|
+ private void HandleTestEnd(bool saveReport)
|
|
|
+ {
|
|
|
+ GenerateTestReport();
|
|
|
+ if (saveReport)
|
|
|
+ {
|
|
|
+ SaveReportToHtml(_context.Report);
|
|
|
+ }
|
|
|
+ }
|
|
|
|
|
|
private void HandleStepFailure(string message, bool isQuestion = false)
|
|
|
{
|
|
@@ -878,7 +1141,7 @@ namespace Bird_tool
|
|
|
string failStr = $"【{step.Name}】失败 {message} (重试 {step.RetryCount}/{step.MaxRetries})";
|
|
|
|
|
|
OnLog?.Invoke(failStr, LogLevel.info);
|
|
|
-
|
|
|
+ RecordStepResult(step, TestStatus.Failed, message);
|
|
|
LogLevel level = LogLevel.info;
|
|
|
// 判断是跳转到特定任务继续执行还是走失败的逻辑
|
|
|
bool isJump = false;
|