Conversation
Replace the basic table-based HTML reporter with a feature-rich,
always-on HTML test report with a modern dark theme ("Dark Observatory").
New features:
- Always-on by default (disable via TUNIT_DISABLE_HTML_REPORTER)
- Summary dashboard with animated SVG donut chart
- Expandable test details with exception, stack trace, stdout/stderr
- Type-ahead search and status filter pills
- Class-based grouping with collapsible sections
- OpenTelemetry trace timeline (Activity spans on .NET 8+)
- GitHub Actions artifact upload via Twirp protocol
- Retry badge display for genuinely retried tests
- Dark theme with glass-morphism cards and CSS animations
- Responsive layout with print styles
- AOT-compatible JSON serialization via source generators
- Deprecation warning for --report-html flag
New files:
- Html/HtmlReporter.cs — Main reporter (IDataConsumer, ITestHostApplicationLifetime)
- Html/HtmlReportGenerator.cs — Self-contained HTML/CSS/JS template builder
- Html/HtmlReportDataModel.cs — POCO types with JsonPropertyName attributes
- Html/HtmlReportJsonContext.cs — JsonSerializable source-gen context
- Html/ActivityCollector.cs — ActivityListener for OTel span capture
- Html/GitHubArtifactUploader.cs — Twirp artifact upload with retry
- docs/guides/html-report.md — User documentation
1 task
Add tunit.test.node_uid activity tag so the ActivityCollector can map each "test case" span back to its TestNode UID. The HtmlReporter now populates TraceId and SpanId on each ReportTestResult, enabling the trace timeline to be linked directly to individual tests. Changes: - TestExecutor: add tunit.test.node_uid tag to test case Activity - ActivityCollector: add GetTestSpanLookup() mapping node UID → (traceId, spanId) - HtmlReporter: populate TraceId/SpanId on test results during BuildReportData - HtmlReportDataModel: change TraceId/SpanId from init to set
- Fix race condition: change _updates from List to ConcurrentBag for thread-safe concurrent Add() calls in ConsumeAsync - Move _outputPath initialization from IsEnabledAsync to BeforeRunAsync to remove side effect from a query method - Extract AccumulateStatus helper to deduplicate summary counting logic - Add global span cap (50K) to ActivityCollector to prevent unbounded memory growth from non-TUnit activity sources - Log warning on retry exhaustion in GitHubArtifactUploader - Improve last-update selection to explicitly find final-state updates instead of relying on bag ordering
Each test's trace timeline was rendering all spans sharing the same traceId (e.g., the entire class-level trace). Now renderTrace filters to only the test's own span (identified by spanId) plus its descendant spans, making the timeline relevant and scalable for large suites.
- Global "Execution Timeline" below the dashboard shows session, assembly, and test suite spans as a high-level overview - "Class Timeline" inside each group shows the test suite span and its non-test-case children (hooks, setup, teardown) - Per-test trace continues to show only the test case span + descendants - Refactored renderTrace into reusable renderSpanRows/getDescendants helpers shared across all three levels
Wrap global, class, and per-test timelines in collapsible sections that start collapsed. Click the header arrow to expand. Keeps the report compact on first load, especially for large test suites.
This was referenced Mar 2, 2026
Open
Open
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
TUNIT_DISABLE_HTML_REPORTER=true;--report-htmlflag now shows deprecation warningNew Files
Html/HtmlReporter.csIDataConsumer,ITestHostApplicationLifetime,IFilterReceiverHtml/HtmlReportGenerator.csHtml/HtmlReportDataModel.cs[JsonPropertyName]for camelCase serializationHtml/HtmlReportJsonContext.cs[JsonSerializable]source-gen context for AOT compatibilityHtml/ActivityCollector.csActivityListenerfor OTel span capture (guarded with#if NET)Html/GitHubArtifactUploader.csdocs/guides/html-report.mdModified Files
EnvironmentConstants.cs— AddedTUNIT_DISABLE_HTML_REPORTERand GitHub Actions runtime env varsTestApplicationBuilderExtensions.cs— Updated registration for new reporter, deprecation warning for--report-htmldocs/reference/environment-variables.md— AddedTUNIT_DISABLE_HTML_REPORTERdocumentationReporters/HtmlReporter.csTest plan
TUNIT_DISABLE_HTML_REPORTER=truesuppresses report generation--report-html-filenamecustom path still works