From a50286789640544daa24dd772f3ce9d7a0bd94d5 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 10 Apr 2026 19:22:37 +0100 Subject: [PATCH 1/3] feat(html-report): omit redundant "test body" span from trace timeline When a test has no before/after hooks, the "test body" span is the only child of the test case span and provides no additional information. Remove it in that case, re-parenting any child spans (e.g. HTTP calls) directly under the test case. Also add a SpanTestBody constant to TUnitActivitySource to eliminate the magic string. --- TUnit.Core/TUnitActivitySource.cs | 1 + TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs | 10 +++++++++- TUnit.Engine/TestExecutor.cs | 2 +- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/TUnit.Core/TUnitActivitySource.cs b/TUnit.Core/TUnitActivitySource.cs index d0c0d6448b..31a5450ce6 100644 --- a/TUnit.Core/TUnitActivitySource.cs +++ b/TUnit.Core/TUnitActivitySource.cs @@ -16,6 +16,7 @@ internal static class TUnitActivitySource internal const string SpanTestAssembly = "test assembly"; internal const string SpanTestSuite = "test suite"; internal const string SpanTestCase = "test case"; + internal const string SpanTestBody = "test body"; // Tag keys used across init/dispose spans and the HTML report. internal const string TagTestId = "tunit.test.id"; diff --git a/TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs b/TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs index abdb1e89c0..bf942f1256 100644 --- a/TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs +++ b/TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs @@ -1485,8 +1485,16 @@ function gd(s) { function renderTrace(tid, rootSpanId) { const allSpans = spansByTrace[tid]; if (!allSpans || !allSpans.length) return ''; - const sp = getDescendants(allSpans, rootSpanId); + let sp = getDescendants(allSpans, rootSpanId); if (!sp.length) return ''; + // If the only direct child of the test case is "test body", the span itself is + // redundant — remove it and re-parent its children directly under the test case + const directChildren = sp.filter(s => s.parentSpanId === rootSpanId); + if (directChildren.length === 1 && directChildren[0].name === 'test body') { + const tbId = directChildren[0].spanId; + sp = sp.filter(s => s.spanId !== tbId).map(s => s.parentSpanId === tbId ? Object.assign({}, s, {parentSpanId: rootSpanId}) : s); + } + if (sp.length <= 1) return ''; // Include the parent suite span so the test bar is shown relative to the class duration const root = bySpanId[rootSpanId]; if (root && root.parentSpanId && bySpanId[root.parentSpanId]) { diff --git a/TUnit.Engine/TestExecutor.cs b/TUnit.Engine/TestExecutor.cs index 59adb506cd..76726066a7 100644 --- a/TUnit.Engine/TestExecutor.cs +++ b/TUnit.Engine/TestExecutor.cs @@ -166,7 +166,7 @@ await _eventReceiverOrchestrator.InvokeFirstTestInClassEventReceiversAsync( if (TUnitActivitySource.Source.HasListeners()) { testBodyActivity = TUnitActivitySource.StartActivity( - "test body", + TUnitActivitySource.SpanTestBody, ActivityKind.Internal, executableTest.Context.Activity?.Context ?? default); } From a4d4981dc001727322d7eb2d1e13070bb0e1ba97 Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 10 Apr 2026 19:26:46 +0100 Subject: [PATCH 2/3] chore: use spread syntax and document SpanTestBody coupling Use object spread instead of Object.assign for consistency with the rest of the JS. Add a comment linking the JS string literal to TUnitActivitySource.SpanTestBody. --- TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs b/TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs index bf942f1256..1fbf1b24bc 100644 --- a/TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs +++ b/TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs @@ -1488,11 +1488,12 @@ function renderTrace(tid, rootSpanId) { let sp = getDescendants(allSpans, rootSpanId); if (!sp.length) return ''; // If the only direct child of the test case is "test body", the span itself is - // redundant — remove it and re-parent its children directly under the test case + // redundant — remove it and re-parent its children directly under the test case. + // Note: 'test body' must match TUnitActivitySource.SpanTestBody in C#. const directChildren = sp.filter(s => s.parentSpanId === rootSpanId); if (directChildren.length === 1 && directChildren[0].name === 'test body') { const tbId = directChildren[0].spanId; - sp = sp.filter(s => s.spanId !== tbId).map(s => s.parentSpanId === tbId ? Object.assign({}, s, {parentSpanId: rootSpanId}) : s); + sp = sp.filter(s => s.spanId !== tbId).map(s => s.parentSpanId === tbId ? {...s, parentSpanId: rootSpanId} : s); } if (sp.length <= 1) return ''; // Include the parent suite span so the test bar is shown relative to the class duration From 0764486678411ee665bb912e0b9bebb90acdf80d Mon Sep 17 00:00:00 2001 From: Tom Longhurst <30480171+thomhurst@users.noreply.github.com> Date: Fri, 10 Apr 2026 19:28:20 +0100 Subject: [PATCH 3/3] chore: trim redundant comment to just the coupling note --- TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs b/TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs index 1fbf1b24bc..28a04c6074 100644 --- a/TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs +++ b/TUnit.Engine/Reporters/Html/HtmlReportGenerator.cs @@ -1487,9 +1487,7 @@ function renderTrace(tid, rootSpanId) { if (!allSpans || !allSpans.length) return ''; let sp = getDescendants(allSpans, rootSpanId); if (!sp.length) return ''; - // If the only direct child of the test case is "test body", the span itself is - // redundant — remove it and re-parent its children directly under the test case. - // Note: 'test body' must match TUnitActivitySource.SpanTestBody in C#. + // 'test body' must match TUnitActivitySource.SpanTestBody in C# const directChildren = sp.filter(s => s.parentSpanId === rootSpanId); if (directChildren.length === 1 && directChildren[0].name === 'test body') { const tbId = directChildren[0].spanId;