diff --git a/Command/CommandRunningTime.cs b/Command/CommandRunningTime.cs
new file mode 100644
index 0000000..c0650e2
--- /dev/null
+++ b/Command/CommandRunningTime.cs
@@ -0,0 +1,47 @@
+using Common.Attributes;
+using Common.Tools;
+using System;
+
+namespace Command
+{
+ ///
+ /// 步骤运行耗时统计命令。
+ ///
+ /// 命令库为纯 .NET 类库,不直接引用宿主的步骤管理器类型;
+ /// 实际的步骤数据查询能力由宿主(StepRunning)在执行测试步骤前通过
+ /// 注入(依赖倒置),
+ /// 多台架并发运行时各作用域的查询器随异步流隔离,互不串扰。
+ ///
+ ///
+ [ADPCommand]
+ public static class CommandRunningTime
+ {
+ ///
+ /// 计算测试步骤运行时间之和:统计步骤管理器主程序中,
+ /// 序号从起始序号到结束序号(含两端)范围内所有步骤的运行耗时总和。
+ ///
+ /// 起始步骤序号(从 1 开始)
+ /// 结束步骤序号(含本身)
+ /// 范围内步骤运行时间之和(毫秒);序号超界、范围内无步骤或查询异常时输出 0
+ public static double 获取步骤运行时间之和(int 起始序号, int 结束序号)
+ {
+ // 读取当前异步执行流注入的步骤耗时查询器(由 StepRunning 设置)
+ var query = StepRuntimeContext.StepTimeQuery.Value;
+ if (query == null)
+ {
+ Console.WriteLine($"[步骤耗时] 宿主未注入步骤耗时查询器,无法计算序号 {起始序号}~{结束序号} 的运行时间之和");
+ return 0;
+ }
+
+ try
+ {
+ return query(起始序号, 结束序号);
+ }
+ catch (Exception ex)
+ {
+ Console.WriteLine($"[步骤耗时] 计算序号 {起始序号}~{结束序号} 的运行时间之和失败: {ex.Message}");
+ return 0;
+ }
+ }
+ }
+}
diff --git a/Common/Tool/StepRuntimeContext.cs b/Common/Tool/StepRuntimeContext.cs
new file mode 100644
index 0000000..7dc618a
--- /dev/null
+++ b/Common/Tool/StepRuntimeContext.cs
@@ -0,0 +1,27 @@
+using System;
+using System.ComponentModel;
+
+namespace Common.Tools
+{
+ ///
+ /// 步骤运行时上下文桥接器(命令库与宿主之间的依赖倒置通道)。
+ ///
+ /// 命令库(Command 项目)不引用宿主类型,无法直接访问步骤管理器的数据;
+ /// 宿主(StepRunning)在执行测试步骤前,将当前作用域的步骤耗时查询委托写入
+ /// ;命令库中的耗时统计命令在执行时读取该委托完成查询。
+ ///
+ ///
+ /// 使用 承载:委托随异步执行流(ExecutionContext)流动,
+ /// 多台架并发运行时各自持有自己作用域的查询器,天然隔离互不串扰。
+ ///
+ ///
+ public static class StepRuntimeContext
+ {
+ ///
+ /// 当前异步执行流的步骤耗时查询委托:(起始序号, 结束序号) → 该范围内步骤运行时间之和(毫秒)。
+ /// 由宿主(StepRunning)在每次执行测试步骤方法前设置;未设置时命令侧按未注入处理。
+ ///
+ [Browsable(false)]
+ public static readonly AsyncLocal?> StepTimeQuery = new();
+ }
+}
diff --git a/UIShare/GlobalVariable/StepRunning.cs b/UIShare/GlobalVariable/StepRunning.cs
index c9ade2a..9dbe7bf 100644
--- a/UIShare/GlobalVariable/StepRunning.cs
+++ b/UIShare/GlobalVariable/StepRunning.cs
@@ -73,6 +73,8 @@ namespace UIShare
ResetAllStepStatus(program.ErrorStepCollection);
tmpParameters.Clear();
TestRoundID = Guid.NewGuid();
+ // 注入步骤耗时查询器:AsyncLocal 随异步执行流流动,命令库(CommandRunningTime)据此查询当前作用域主程序的步骤耗时
+ StepRuntimeContext.StepTimeQuery.Value = QueryStepRunTimeSum;
}
foreach (var item in program.Parameters)
{
@@ -261,6 +263,8 @@ namespace UIShare
tmpParameters.Clear();
testItemStack.Clear();
TestRoundID = Guid.NewGuid();
+ // 注入步骤耗时查询器:AsyncLocal 随异步执行流流动,命令库(CommandRunningTime)据此查询当前作用域主程序的步骤耗时
+ StepRuntimeContext.StepTimeQuery.Value = QueryStepRunTimeSum;
}
int initialLoopStackCount = loopStack.Count;
foreach (var item in program.Parameters)
@@ -800,6 +804,30 @@ namespace UIShare
}
}
+ ///
+ /// 查询当前作用域主程序步骤集合中,序号位于 [起始序号, 结束序号](含两端)范围内步骤的运行时间之和(毫秒)。
+ /// 由 ExecuteSteps / ExecuteErrorSteps 顶层启动时经 StepRuntimeContext 注入(AsyncLocal 随异步流流动),
+ /// 供命令库 CommandRunningTime.获取步骤运行时间之和 调用(依赖倒置:命令库不引用宿主类型)。
+ /// 序号倒置、超界(范围内不存在步骤)时记录错误日志并返回 0;未执行步骤的耗时按 0 计入。
+ ///
+ private double QueryStepRunTimeSum(int startIndex, int endIndex)
+ {
+ var steps = _scopedContext.Program.StepCollection;
+ if (startIndex > endIndex)
+ {
+ LoggerHelper.Error($"[步骤耗时] 查询失败:起始序号 {startIndex} 大于结束序号 {endIndex},返回 0");
+ return 0;
+ }
+ var matched = steps.Where(s => s.Index >= startIndex && s.Index <= endIndex).ToList();
+ if (matched.Count == 0)
+ {
+ LoggerHelper.Error($"[步骤耗时] 查询失败:序号 {startIndex}~{endIndex} 范围内不存在测试步骤(主程序共 {steps.Count} 步),返回 0");
+ return 0;
+ }
+ // 尚未执行或未启用的步骤 RunTime 为 null,按 0 计入
+ return matched.Sum(s => s.RunTime ?? 0);
+ }
+
private void UpdateCurrentStepResult(StepVM step, bool paraResult = true, bool stepResult = true, int depth = 0)
{
if (stepResult && paraResult)