Add Microsoft.Testing.Extensions.CtrfReport extension (CTRF reporter)#8903
Add Microsoft.Testing.Extensions.CtrfReport extension (CTRF reporter)#8903Evangelink wants to merge 11 commits into
Conversation
Adds a new MTP extension that emits a CTRF (Common Test Report Format, https://ctrf.io) JSON file at the end of a test session, mirroring the layout of Microsoft.Testing.Extensions.HtmlReport. CLI options: - --report-ctrf: enables generation - --report-ctrf-filename <name.json>: optional file name (default: {user}_{machine}_{module}_{tfm}_{timestamp}.ctrf.json) Refs microsoft#8858. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… fallback Apply spec/expert-review fixes to the CTRF report extension: * Collapse same-UID captures into a single tests[] entry with retryAttempts[]; set retries=N-1 and flaky=true when the final attempt passed after earlier failures (CTRF retry idiom). * Update summary.tests to count unique UIDs (and add summary.flaky) so CTRF aggregators no longer double-count retries. * Drop UnsafeRelaxedJsonEscaping: HTML/JS metachars in test names could become XSS vectors in downstream CTRF dashboards. * Cap WriteWithRetryAsync attempts at 1000 in addition to the existing 5s wall-clock bound. * Fall back to UID when DisplayName is empty so tests[].name keeps the schema-required minLength: 1. * Add unit tests for the collapsed retry shape, empty result set, HTML-escaping behavior, and DisplayName -> UID fallback. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a new Microsoft.Testing.Platform (MTP) report extension, Microsoft.Testing.Extensions.CtrfReport, to emit test results in CTRF (Common Test Report Format) JSON at the end of a test session, including CLI integration and end-to-end coverage in unit + acceptance tests.
Changes:
- Introduces the new
Microsoft.Testing.Extensions.CtrfReportextension project (engine, command line provider, generator, MSBuild hook, packaging metadata, localization resources). - Adds unit tests validating CLI validation, CTRF JSON structure/aggregation, retry collapsing/flaky detection, and HTML/JS-safe JSON escaping.
- Updates acceptance tests + help/info expectations + MSBuild known-extension registration to include the new CTRF extension and CLI options.
Show a summary per file
| File | Description |
|---|---|
| TestFx.slnx | Adds the CTRF report extension project to the main solution. |
| NonWindowsTests.slnf | Includes the CTRF report extension in the non-Windows solution filter. |
| Microsoft.Testing.Platform.slnf | Includes the CTRF report extension in the platform solution filter. |
| src/Platform/Microsoft.Testing.Platform/Microsoft.Testing.Platform.csproj | Grants InternalsVisibleTo access for the new CTRF extension. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/Microsoft.Testing.Extensions.CtrfReport.csproj | New extension project definition and packaging layout. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/PACKAGE.md | NuGet package documentation for CTRF report extension. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/BannedSymbols.txt | Enforces platform conventions (IClock/RoslynString/etc.) for the new project. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/PublicAPI/PublicAPI.Shipped.txt | Initializes shipped API surface tracking for the new package. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/PublicAPI/PublicAPI.Unshipped.txt | Declares newly introduced public APIs for the CTRF extension package. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/TestingPlatformBuilderHook.cs | MSBuild hook entry point to register the CTRF extension into the builder. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportExtensions.cs | Public extension method (AddCtrfReportProvider) for registering CTRF reporting. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportGeneratorCommandLine.cs | Implements --report-ctrf and --report-ctrf-filename options + validation. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportGenerator.cs | Captures test node updates and publishes CTRF JSON report as a session artifact. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/TestResultCapture.cs | Projects terminal test node updates into bounded DTOs for reporting. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/CapturedTestResult.cs | Defines the captured test result DTO used by the CTRF engine. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.cs | Generates CTRF JSON (summary, environment, collapsed retries/flaky) and writes report file. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/buildMultiTargeting/Microsoft.Testing.Extensions.CtrfReport.props | Declares the well-known MSBuild extension hook metadata. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/buildTransitive/Microsoft.Testing.Extensions.CtrfReport.props | Adds transitive build import for the extension hook. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/build/Microsoft.Testing.Extensions.CtrfReport.props | Adds build-time import for the extension hook. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/Resources/ExtensionResources.resx | Adds localizable strings for CLI validation and artifact metadata. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/Resources/xlf/ExtensionResources.cs.xlf | Adds localization XLF for cs. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/Resources/xlf/ExtensionResources.de.xlf | Adds localization XLF for de. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/Resources/xlf/ExtensionResources.es.xlf | Adds localization XLF for es. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/Resources/xlf/ExtensionResources.fr.xlf | Adds localization XLF for fr. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/Resources/xlf/ExtensionResources.it.xlf | Adds localization XLF for it. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/Resources/xlf/ExtensionResources.ja.xlf | Adds localization XLF for ja. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/Resources/xlf/ExtensionResources.ko.xlf | Adds localization XLF for ko. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/Resources/xlf/ExtensionResources.pl.xlf | Adds localization XLF for pl. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/Resources/xlf/ExtensionResources.pt-BR.xlf | Adds localization XLF for pt-BR. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/Resources/xlf/ExtensionResources.ru.xlf | Adds localization XLF for ru. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/Resources/xlf/ExtensionResources.tr.xlf | Adds localization XLF for tr. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/Resources/xlf/ExtensionResources.zh-Hans.xlf | Adds localization XLF for zh-Hans. |
| src/Platform/Microsoft.Testing.Extensions.CtrfReport/Resources/xlf/ExtensionResources.zh-Hant.xlf | Adds localization XLF for zh-Hant. |
| test/UnitTests/Microsoft.Testing.Extensions.UnitTests/Microsoft.Testing.Extensions.UnitTests.csproj | References the new CTRF extension project for unit testing. |
| test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportGeneratorCommandLineTests.cs | Adds unit tests for CTRF CLI option validation behavior. |
| test/UnitTests/Microsoft.Testing.Extensions.UnitTests/CtrfReportEngineTests.cs | Adds unit tests for CTRF JSON generation, collapsing retries, and environment/test shape. |
| test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/CtrfReportTests.cs | Adds end-to-end acceptance tests for CTRF file generation and JSON shape. |
| test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/HelpInfoAllExtensionsTests.cs | Updates help/info expectations to document CTRF CLI options and provider info. |
| test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/MSBuild.KnownExtensionRegistration.cs | Ensures the CTRF extension is registered/visible via MSBuild known-extension wiring. |
Copilot's findings
- Files reviewed: 39/39 changed files
- Comments generated: 1
|
Would it be useful to use this with an xUnit.net test project and compare the results against our built-in report as well? I'd be curious to know if/where we diverge. |
|
One of the things that makes me wonder about this is, for example, how our TRX reports diverge. I'm thinking less about the current display bug in VS, and more about the fact that we have more information than MTP has (like, knowing about explicit tests), so knowing if (a) our commonalities all end up the same in CTRF, and (b) how the metadata differences manifest. |
|
Makes total sense to me, I'll post project and diffs for us to analyze. I still need to work out what to do with explicit case (I know this is long overdue). |
|
Some diffs I can see comparing against xUnit.net's built-in CTRF:
On the whole, this looks very good to me. We seem be in 99% agreement where possible (we can't realistically expect you to identify explicit tests, for example). I think I exercised every feature of xUnit.net that should've caused something to show in CTRF, but I may have missed something. From this, I see these changes I will make for xUnit.net 4.0:
I'll leave it for you to decide if the reconciliation of our |
|
All of the xUnit.net-side fixes are now available in v3 |
…omparison Provides two MTP test apps that generate CTRF reports for the same set of tests (pass / fail / skip / theory / throw) using two different generators: - samples/CtrfPlayground/Mtp: MSTest + Microsoft.Testing.Extensions.CtrfReport (the experimental extension shipped by this repository) - samples/CtrfPlayground/XunitMtp: xunit.v3 with its built-in '-ctrf <file>' reporter This allows reviewers to diff the CTRF JSON produced by both stacks against the same test fixtures. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Follow-ups in 288620d (and the rawStatus thread reply):
|
Match the versioning scheme used by other experimental Microsoft.Testing.Platform extensions (Logging, OpenTelemetry, AI). Packages will now ship as 1.0.0-alpha.* instead of the repo-default *-preview.*. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Also switched to the experimental alpha versioning scheme (with true), so the package now ships as |
Replace structural contains-style assertions with a verbatim snapshot of the entire JSON document. Runtime-variable fields (GUID report id, ISO timestamp, epoch-ms times, extension version, OS info, user/machine, absolute test app path) are normalized via field-scoped regexes to deterministic tokens, while everything else (key order, indentation, conditional emission, dummy framework values) must match byte-for-byte. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
After the CtrfReport package was moved to the experimental versioning scheme (1.0.0-alpha) it no longer matches the platform version requested by the acceptance test asset csproj. Introduce a dedicated MicrosoftTestingExtensionsCtrfReportVersion property on AcceptanceTestBase and a \\\$\ placeholder so the dummy app references the package at the version actually produced by the local pack. Also fix two pre-existing MSTEST0068 analyzer errors in CtrfReportEngineTests.cs that block packing. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Follow-up commits on this branch:
All 15 |
- Correct the misleading comment in samples/CtrfPlayground/XunitMtp/XunitMtp.csproj: xunit.v3 exposes its built-in CTRF reporter via the `-ctrf <file>` switch, not a generic `report-ctrf` option. - Switch the HelpInfoAllExtensionsTests asset csproj to the dedicated \\$ placeholder (the CTRF extension now follows the experimental versioning scheme and no longer matches \\$). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Follow-up commit on this branch:
|
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>
| <PackageReference Include="Microsoft.Testing.Extensions.AzureDevOpsReport" Version="$MicrosoftTestingPlatformVersion$" /> | ||
| <PackageReference Include="Microsoft.Testing.Extensions.CrashDump" Version="$MicrosoftTestingPlatformVersion$" /> | ||
| <PackageReference Include="Microsoft.Testing.Extensions.CtrfReport" Version="$MicrosoftTestingPlatformVersion$" /> | ||
| <PackageReference Include="Microsoft.Testing.Extensions.HangDump" Version="$MicrosoftTestingPlatformVersion$" /> |
There was a problem hiding this comment.
Thanks - both MSBuild.KnownExtensionRegistration.cs test methods (lines 22 and 106) were updated in a2bd24f to use $MicrosoftTestingExtensionsCtrfReportVersion$ (resolved via AcceptanceTestBase.MicrosoftTestingExtensionsCtrfReportVersion).
| testHostResult.AssertOutputContains("--report-ctrf"); | ||
| testHostResult.AssertOutputContains("--report-html"); | ||
| testHostResult.AssertOutputContains("--report-trx"); |
…acceptance test, expand snapshot, fix markdown lint Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| // for the whole session. We never deduplicate on TestNode.Uid: some | ||
| // frameworks emit several distinct results sharing the same UID | ||
| // (parameterized rows, theory data, in-process retries). The engine | ||
| // surfaces all of them so no data is dropped. |
There was a problem hiding this comment.
Good catch - reworded the comment in 32fbbb4 to remove the misleading "never deduplicate" wording and explicitly point at CtrfReportEngine.CollapseAttempts, which collapses same-UID captures into retryAttempts[] (and sets flaky when an earlier attempt failed).
…und in CI
* Skip samples/CtrfPlayground/{Mtp,XunitMtp} under 'dotnet test -p:UsingDotNetTest=true' (the playground intentionally contains failing/throwing/skipped tests for diffing against xunit.v3 CTRF output). Mirrors the WasiPlayground pattern. Fixes the Windows/MacOS Debug Test CI failures.
* Split CTRF stdout/stderr into per-line array entries. The CTRF schema types these as 'lines of output'; we now split on LF (normalizing CRLF) and omit the trailing empty entry when input ends with a newline. Brad Wilson's xunit.v3 CTRF comparison item microsoft#9.
* Replace the misleading 'we never deduplicate on TestNode.Uid' wording in CtrfReportGenerator with a comment pointing at CtrfReportEngine.CollapseAttempts (which DOES collapse same-UID captures into retryAttempts[]).
Adds two unit tests for the new line-splitting behavior (CRLF normalization, trailing-newline handling, single-line output).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
|
Thanks @bradwilson - really appreciate the detailed side-by-side, this was super helpful. Going through each item: 1. 2. 3. unrun-explicit-tests = pending - MTP doesn't surface an "explicit/pending" concept (a 4. 5. 6. 7. 8. Same as #3 - no action. 9. 10. warnings - MTP doesn't expose a first-class "warnings" channel today. Not actionable from the extension; would need a platform-level addition. 11. attachments - MTP has So to summarize what I'm doing here vs deferring:
|
|
Filed ctrf-io/ctrf#53 covering the naming and |
|
I don't see In addition, your current implementation generates illegal JSON when a trait name has more than one value, because it emits the same name twice, like: |
What
Adds a new MTP extension
Microsoft.Testing.Extensions.CtrfReportthat emits a CTRF (Common Test Report Format) JSON file at the end of a test session. This is the discussion outcome of #8858 — opening a draft so we can iterate.CTRF is a vendor-neutral JSON schema for test results (spec v0.0.0) that is gaining adoption (xUnit, Jest, Mocha, Cypress, Playwright, Pytest, RSpec, JUnit, and others all have CTRF reporters). Supporting it natively in MTP lets users plug their results into the same dashboards/integrations regardless of the test framework.
CLI surface
--report-ctrf— enables generation.--report-ctrf-filename <name.json>— optional file name (defaults to{user}_{machine}_{module}_{tfm}_{timestamp}.ctrf.json).Validation mirrors the existing
HtmlReport/TrxReportextensions:.json--report-ctrfwhen filename is set--list-testsHow it maps to CTRF
Top-level:
reportFormat: "CTRF",specVersion: "0.0.0", randomreportId, ISO 8601timestamp,generatedBy: "Microsoft.Testing.Extensions.CtrfReport@<ver>".results:tool:{ name, version }fromITestFrameworksummary:tests/passed/failed/skipped/pending/other/flaky/start/stop/duration(counts unique UIDs)tests[]: one entry per unique test UID. Re-runs of the same UID collapse into a single entry withretries: N,retryAttempts[], andflaky: truewhen the final attempt passes after earlier failures.environment:osPlatform,osVersion, andextra.{user,machine,exitCode,testApplication}Status mapping (CTRF enum: passed/failed/skipped/pending/other):
passedskippedfailed(rawStatustop-level field preserves the original where it differs)otherJSON is written via
Utf8JsonWriterwith the default safe encoder (escapes<,>,&,',") so the report is safe to embed in HTML/JS dashboards.Tests
test/UnitTests/Microsoft.Testing.Extensions.UnitTests): 35 tests covering CLI option validation, JSON shape, retry collapsing, empty-results handling, name fallback, status mapping, and XSS-safe escaping.test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/CtrfReportTests.cs): end-to-end (file generated, valid JSON, CLI options).HelpInfoAllExtensionsTests.cs) and MSBuild known-extension registration (MSBuild.KnownExtensionRegistration.cs) updated.Validation against the CTRF schema
The generated reports validate against
ctrf.schema.json. I also ran a side-by-side comparison against the most popular xUnit CTRF reporter (DotnetCtrfJsonReporter, ~650k NuGet downloads) on matching test suites — full notes in the session artifacts. TL;DR:DotnetCtrfJsonReporter)suiteas string; spec requires array)skippedcorrectly reportedstatus: "other",summary.skipped: 0)summary.durationsummary.flakyaggregateenvironmentblockretryAttempts[]Name (1,1,2)Name(a: 1, b: 1, expected: 2)Closes (or follows up on) #8858.
cc @bradwilson (xUnit, CTRF contributor) for visibility per the discussion thread.