Skip to main content

Shared HTML renderer

BDTest.Rendering is a signed netstandard2.0 package that depends only on BDTest.Contracts. Both BDTest.Reporting and BDTest.Razor.Reports reference it transitively. A tool that already has immutable report data can reference it directly without loading execution, a test adapter, or ASP.NET Core:

dotnet add package BDTest.Rendering
Illustrative fragment
using BDTest.Contracts;
using BDTest.Rendering;

BDTestReport report = BDTestReportJson.Deserialize(json);
string html = ReportHtmlRenderer.GenerateHtmlReport(report);
File.WriteAllText("report.html", html);

Use matching versions of all BDTest packages. BDTestReportJson reads the current schema. Normalize unversioned historical input through the historical reader or a hosted HostedReportJson reader before rendering. The renderer accepts immutable contracts and never deserializes execution objects.

Rendering decision​

The shared implementation emits HTML on the server with the existing embedded CSS and event-listener script. This preserves the standalone single-file offline report without requiring a JavaScript framework, browser build, or runtime network requests. It uses the contracts package's source-generated JSON metadata for tag data. The parent CSP and custom HTML sandbox remain in place. The hosted shell loads the same scoped styles and interaction script, so disclosure, copy and wrap controls work in both report surfaces.

ReportHtmlRenderer.RenderRunOverview renders the verdict, timing, run metadata, status totals and custom properties from a ReportRunOverview. FromReport builds this small immutable projection from a report. Hosted summary pages supply the same fields from their compact cached projections, without reconstructing a report or retaining steps, examples, exceptions or output. A status URL callback turns offline filter controls into hosted navigation; a separate URL points to tests that were declared but never ran. RenderNotRun supplies the same encoded warning rows to the offline report and hosted warnings page. Hosted and standalone summaries infer timing from scenario timestamps when the report timer is missing or has a missing start or finish timestamp. Complete report timers take precedence, and reports without scenarios preserve their supplied timer.

Scenario pages and offline reports share the complete scenario detail renderer: steps and timings, structured arguments, exceptions, examples, retries, startup and teardown output, custom information, attachments and sandboxed custom HTML. Errors recorded only on an outline, such as cancellation between examples, remain visible; exceptions already shown in an example are not repeated at the outline level. Hosted routing and standalone filtering wrap the shared sections. Tables retain the twenty-row preview, and captured output retains copy and wrap controls. RenderArguments and RenderException also remain available as smaller encoded fragments.

The renderer does not open attachment URIs, authorize requests, select a run, materialize hosted cache entries, or upload reports. Those responsibilities stay with their current owners. Hosted attachment URLs continue to be generated from the stored report identity by the authorized attachment route.

Migration and integration boundary​

Existing BDTest.Helpers.BDTestHtmlHelper, ExceptionHtmlHelper and StepArgumentHtmlHelper signatures remain in BDTest.Reporting as forwarding facades. Run-based exports still use its single model builder and validate the selected run. No caller migration is needed beyond the reporting package reference; tools wanting only rendering can use ReportHtmlRenderer directly.

Hosted Razor views supply their navigation and history panels around the shared content. RenderAttention shares failure-group cards over compact ReportFailureOverview and ReportFailureLink display data. The host retains its grouping analysis, three-group/five-scenario overview limits and links to additional failures; standalone reports retain their root-cause grouping and in-page scenario navigation. Compact page caches, customization and historical reading remain in the hosted package. Attachment URL callbacks use the stored report identity and the authorized hosted attachment route. Without a route resolver, attachments show their metadata and unavailable-content label. Captured attachment URIs never become links or previews. URLs are encoded; attachment links must be application-relative routes and executable URI schemes are rejected. Neither surface lets custom HTML escape its sandbox or bypass hosted attachment authorization.

For custom host integration, ReportRenderOptions.AttachmentUrl resolves each attachment to an authorized application-relative route, or returns null when content is unavailable. Set Hosted = true for the stricter hosted custom-HTML sandbox. Side panels can set OmitCustomHtml = true while offering a link to the full scenario page. Use RenderScenario, RenderExamples, RenderRetryHistory and RenderAttachments for the corresponding sections. RenderStyles returns the shared style element; GetScript returns script text for a host-owned script element with the host's CSP nonce. Containers use the rpt class so styles and interaction handlers stay scoped to the report content.