Skip to content

Add Microsoft.Testing.Extensions.CtrfReport extension (CTRF reporter)#8903

Open
Evangelink wants to merge 15 commits into
microsoft:mainfrom
Evangelink:dev/amauryleve/ctrf-report-extension
Open

Add Microsoft.Testing.Extensions.CtrfReport extension (CTRF reporter)#8903
Evangelink wants to merge 15 commits into
microsoft:mainfrom
Evangelink:dev/amauryleve/ctrf-report-extension

Conversation

@Evangelink

@Evangelink Evangelink commented Jun 7, 2026

Copy link
Copy Markdown
Member

What

Adds a new MTP extension Microsoft.Testing.Extensions.CtrfReport that 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/TrxReport extensions:

  • file extension must be .json
  • relative paths must stay under the results directory
  • requires --report-ctrf when filename is set
  • not allowed together with --list-tests

How it maps to CTRF

Top-level: reportFormat: "CTRF", specVersion: "0.0.0", random reportId, ISO 8601 timestamp, generatedBy: "Microsoft.Testing.Extensions.CtrfReport@<ver>".

results:

  • tool: { name, version } from ITestFramework
  • summary: 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 with retries: N, retryAttempts[], and flaky: true when the final attempt passes after earlier failures.
  • environment: osPlatform, osVersion, and extra.{user,machine,exitCode,testApplication}

Status mapping (CTRF enum: passed/failed/skipped/pending/other):

  • Passed → passed
  • Skipped → skipped
  • Failed / Error / Timeout → failed (rawStatus top-level field preserves the original where it differs)
  • Cancelled → other

JSON is written via Utf8JsonWriter with the default safe encoder (escapes <, >, &, ', ") so the report is safe to embed in HTML/JS dashboards.

Tests

  • Unit 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.
  • Acceptance tests (test/IntegrationTests/Microsoft.Testing.Platform.Acceptance.IntegrationTests/CtrfReportTests.cs): end-to-end (file generated, valid JSON, CLI options).
  • Help/info expectations (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:

MTP (this PR) xUnit (DotnetCtrfJsonReporter)
Schema-valid ❌ (emits suite as string; spec requires array)
skipped correctly reported ❌ (rounds to status: "other", summary.skipped: 0)
summary.duration
summary.flaky aggregate
environment block
Retry collapsing / retryAttempts[]
Theory cases 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.

Evangelink and others added 2 commits June 7, 2026 10:31
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>
Copilot AI review requested due to automatic review settings June 7, 2026 08:37

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.CtrfReport extension 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

@bradwilson

Copy link
Copy Markdown

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.

@bradwilson

Copy link
Copy Markdown

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.

@Evangelink

Copy link
Copy Markdown
Member Author

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).

@bradwilson

bradwilson commented Jun 7, 2026

Copy link
Copy Markdown

Some diffs I can see comparing against xUnit.net's built-in CTRF:

  1. We write computer name to /results/extra/computer, you write to /results/extra/machine
  2. We write the OS version to /results/environment/osRelease, you write to /results/environment/osVersion
  3. As expected, we count un-run explicit tests in /results/summary/pending, you count them in /results/summary/skipped
  4. You are (illegally) placing traits inside /results/tests/[]/labels, which does not appear to be part of the spec for Test Object; we are placing them inside /results/tests/[]/extra/traits. Additionally, we are also placing any trait named Category into /results/tests/[]/tags. Would be curious to know where this lands on your side, since "trait" is a pretty xUnit.net-centric name.
  5. For all tests, we write the CLR test class name to /results/tests/[]/extra/type, you only appear to write this into the array at /results/tests/[]/suite/[] (and it appears that you're writing Name rather than FullName, which would presumably group tests together which are in the types with the same Name but different Namespace). Our suite value seems incorrect, since the spec says this should be a string array, not a string. (I don't know if this changed in the spec or I just output the wrong thing...)
  6. For all tests, you write start and stop timestamps to /results/tests/[], we do not
  7. For failed tests, we write the CLR exception type to /results/tests/[]/extra/exception, you write to /results/tests/[]/extra/exceptionType
  8. For unrun (explicit) tests, as expected we mark this in /results/tests/[]/status as pending, you mark it as skipped
  9. You place test output into /results/tests/[]/stdout/[], we place it in /results/tests/[]/extra/output. Interestingly, you don't appear to be splitting on newlines, so you always end up with a single string in the array; the spec is not 100% clear on what "lines of output" means, but it probably means splitting on newlines.
  10. We write warnings to /results/tests/[]/extra/warnings/[]. There is no mechanism today to report warnings to MTP.
  11. We write attachments to /results/tests/[]/attachments, you do not appear to log attachments.

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:

  • Duplicate OS version into /results/environment/osVersion
  • Writing /results/tests/[]/suite/[] incorrectly
  • Writing /results/tests/[]/start and /results/tests/[]/stop
  • Writing /results/tests/[]/message for explicit tests to align with the output from MTP (helpful to the user)
  • Move /results/tests/[]/extra/output to /results/tests/[]/stdout/[] (using split lines)

I'll leave it for you to decide if the reconciliation of our extra property names is important.

@bradwilson

Copy link
Copy Markdown

All of the xUnit.net-side fixes are now available in v3 4.0.0-pre.139 https://xunit.net/docs/using-ci-builds

…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>
@Evangelink

Copy link
Copy Markdown
Member Author

Follow-ups in 288620d (and the rawStatus thread reply):

  • Experimental marker: the public surface is already gated on TPEXP:

    • [Experimental(""TPEXP"", UrlFormat = ""https://aka.ms/testingplatform/diagnostics#{0}"")] on CtrfReportExtensions
    • [TPEXP] prefix in PublicAPI/PublicAPI.Unshipped.txt
    • > **⚠️ Experimental:** callout in PACKAGE.md

    This mirrors Microsoft.Testing.Extensions.HtmlReport exactly. The TestingPlatformBuilderHook class is intentionally not marked (same as HtmlReport).

  • Side-by-side comparison sample added under samples/CtrfPlayground/: two MTP test apps that exercise the same fixtures (pass / fail / skip / theory / throw) and produce a CTRF JSON each — one via this extension (MSTest), one via xunit.v3's built-in -ctrf reporter — so the two outputs can be diffed directly.

  • rawStatus: clarified on the open thread that rawStatus is a top-level test property in the CTRF schema, so the implementation is schema-correct. Updated the PR description wording (was incorrectly written as extra.rawStatus).

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>
Copilot AI review requested due to automatic review settings June 8, 2026 08:45
@Evangelink

Copy link
Copy Markdown
Member Author

Also switched to the experimental alpha versioning scheme (with true), so the package now ships as 1.0.0-alpha.* like the other experimental MTP extensions (Logging, OpenTelemetry, AI) rather than the repo-default -preview.* label. Commit: f7aeac3

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 45/45 changed files
  • Comments generated: 1

Comment thread samples/CtrfPlayground/XunitMtp/XunitMtp.csproj Outdated
Copilot AI added 2 commits June 8, 2026 11:43
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>
@Evangelink

Copy link
Copy Markdown
Member Author

Follow-up commits on this branch:

  • 7aeac3ec — Apply the experimental versioning scheme (VersionPrefix=1.0.0, PreReleaseVersionLabel=alpha, SuppressFinalPackageVersion=true) to Microsoft.Testing.Extensions.CtrfReport.csproj.
  • 2a0fd39de — Convert the CTRF acceptance test from structural contains-style assertions to a verbatim full-content snapshot of the generated JSON. Runtime-variable fields (GUID report id, ISO timestamp, epoch-ms times, extension version, OS info, user/machine, absolute test app path) are folded into stable tokens via field-scoped regexes; everything else (key order, indentation, conditional emission, dummy framework values) must match byte-for-byte.
  • 4704b5421 — Plumb a dedicated MicrosoftTestingExtensionsCtrfReportVersion property/placeholder through AcceptanceTestBase so the asset csproj can reference the (now-experimental) CTRF package at the version actually produced by the local pack. Also fixes two pre-existing MSTEST0068 analyzer errors in CtrfReportEngineTests.cs that were blocking -pack.

All 15 CtrfReportTests pass locally against the new snapshot. Bumping for visibility cc @microsoft/testfx-maintainers.

- 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>
Copilot AI review requested due to automatic review settings June 8, 2026 11:45
@Evangelink

Copy link
Copy Markdown
Member Author

Follow-up commit on this branch:

  • 22d2f1d — Correct the misleading XunitMtp.csproj comment to mention xunit.v3's -ctrf <file> switch, and switch HelpInfoAllExtensionsTests to the dedicated \\$ placeholder so the test no longer requests the platform version.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 46/46 changed files
  • Comments generated: 1

Comment thread src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportEngine.cs Outdated
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 8, 2026 12:37
@Evangelink Evangelink marked this pull request as ready for review June 8, 2026 12:37
Comment thread samples/CtrfPlayground/XunitMtp/XunitMtp.csproj Outdated
Comment thread samples/CtrfPlayground/XunitMtp/XunitMtp.csproj Outdated
Co-authored-by: Amaury Levé <amauryleve@microsoft.com>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 46/46 changed files
  • Comments generated: 2

Comment on lines 103 to 106
<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$" />

@Evangelink Evangelink Jun 8, 2026

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks - both MSBuild.KnownExtensionRegistration.cs test methods (lines 22 and 106) were updated in a2bd24f to use $MicrosoftTestingExtensionsCtrfReportVersion$ (resolved via AcceptanceTestBase.MicrosoftTestingExtensionsCtrfReportVersion).

Comment on lines +31 to 33
testHostResult.AssertOutputContains("--report-ctrf");
testHostResult.AssertOutputContains("--report-html");
testHostResult.AssertOutputContains("--report-trx");

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks - same fix landed in a2bd24f.

…acceptance test, expand snapshot, fix markdown lint

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 8, 2026 13:10

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 47/47 changed files
  • Comments generated: 1

Comment thread src/Platform/Microsoft.Testing.Extensions.CtrfReport/CtrfReportGenerator.cs Outdated
…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>
@Evangelink

Copy link
Copy Markdown
Member Author

Thanks @bradwilson - really appreciate the detailed side-by-side, this was super helpful. Going through each item:

1. environment.extra.machine vs environment.extra.computer - Both are inside the freeform extra object (the spec marks environment as additionalProperties: false, so anything non-canonical has to live there), so they're both spec-compliant; the difference is purely naming. I'd rather not chase parity with the only other current MTP CTRF producer if it means churning consumers that already keyed off our shape. Happy to align if the CTRF folks bless a canonical name - I'll open an issue on ctrf-io for items 1/4/7 below.

2. osVersion vs osRelease - These are two different first-class fields in the spec (osRelease = release version, osVersion = version string). We emit osVersion. If your reader adds it too on the xunit side we'll both be good.

3. unrun-explicit-tests = pending - MTP doesn't surface an "explicit/pending" concept (a Skip attribute always lands as MTP's skipped state). Nothing to do here on our side without a platform change. If we want runtime-explicit semantics, that's a TestFramework concern.

4. labels vs extra.traits - I read the spec slightly differently here: labels is defined as "Structured key-value metadata for the test case (e.g. priority, severity, external identifiers)" typed as an object - which is exactly what MTP TestMetadataProperty traits are (key=value pairs). The "e.g." reads to me as non-exhaustive examples, not a restriction. I'd rather use the first-class spec slot than push them into extra. That said, this is one of the items I'll ask the CTRF maintainers about.

5. suite (array vs string) - We already emit it as an array of strings ([Namespace, ClassName]), so we should be schema-conformant. Whether to use Name or FullName is your decision on the xunit.v3 side.

6. start / stop per test - We already emit both. Glad you'll add them too - it'll make our outputs easier to diff.

7. extra.exception vs extra.exceptionType - Same situation as #1: both in extra, purely a naming choice. Our field is the .NET type name (e.g. System.InvalidOperationException); exceptionType felt more accurate than exception (which sounds like it should be the rendered exception object). Will include in the ctrf-io issue.

8. Same as #3 - no action.

9. stdout / stderr split into lines - You're right, the spec says "lines of output" and we were emitting a single multi-line string. Fixed in 32fbbb4: we now split on LF (normalizing CRLF, omitting the trailing empty entry from a trailing newline), with unit-test coverage for the boundary cases.

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 FileArtifactProperty, so this is achievable - just out of scope for this initial PR. Will track as a follow-up.

So to summarize what I'm doing here vs deferring:

@Evangelink

Copy link
Copy Markdown
Member Author

Filed ctrf-io/ctrf#53 covering the naming and labels clarification questions (items 1, 4, 7) plus a follow-up on whether absent stdout/stderr should be [] or omitted.

@bradwilson

bradwilson commented Jun 8, 2026

Copy link
Copy Markdown

I don't see labels in the spec: https://ctrf.io/docs/specification/test

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:

"labels": {
    "Name": "Value1",
    "Name": "Value2"
}

@Evangelink

Copy link
Copy Markdown
Member Author

@copilot resolve the merge conflicts in this pull request

Evangelink and others added 2 commits June 9, 2026 11:30
- CtrfReport: opt-in only via EnableMicrosoftTestingExtensionsCtrfReport (centralized 1.0.0-alpha version flowed through MSTest.Sdk template).

- HtmlReport: auto-enabled by AllMicrosoft profile and opt-in via EnableMicrosoftTestingExtensionsHtmlReport (uses platform-aligned MicrosoftTestingExtensionsCommonVersion).

- VSTest.targets: add Error guards for the new opt-ins and the previously missing AzureDevOpsReport one.

- Acceptance: cover CtrfReport (--report-ctrf) and HtmlReport (--report-html) selective-enable rows and append --report-html to the AllMicrosoft EnableAll run.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds Microsoft.Testing.Extensions.OpenTelemetry to the MSTest.Sdk wiring so users can opt-in via the EnableMicrosoftTestingExtensionsOpenTelemetry MSBuild property.

OpenTelemetry is independently versioned (1.0.0-alpha, matching the CtrfReport pattern) because it is still experimental ([TPEXP]). The extension is API-only — there is no CLI flag — so the enable property only opts the package into the build; activation requires the user to call builder.AddOpenTelemetryProvider(...) from their Program.cs.

- Directory.Build.props: centralized version definitions

- src/Platform/.../Microsoft.Testing.Extensions.OpenTelemetry.csproj: consume centralized version properties

- MSTest.Sdk.csproj: compute and propagate _MicrosoftTestingExtensionsOpenTelemetryVersion (ci/dev/official override)

- Sdk.props.template / ClassicEngine.targets / VSTest.targets: wire the opt-in PackageReference, version line, and Error guard

- SdkTests.cs: add an acceptance row that validates the package restores and runs cleanly (empty CLI enable arg; --crashdump asserts no other extensions leaked in)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings June 9, 2026 14:33

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot's findings

  • Files reviewed: 54/54 changed files
  • Comments generated: 2

Not auto-enabled by any profile - it has its own pre-release version line
(see MicrosoftTestingExtensionsCtrfReportVersion flowed through MSTest.Sdk.csproj).
-->
<MicrosoftTestingExtensionsCtrfReportVersion Condition=" '$(MicrosoftTestingExtensionsCtrfReportVersion)' == '' " >$(MicrosoftTestingExtensionsCtrfReportVersion)</MicrosoftTestingExtensionsCtrfReportVersion>
package into the build. Has its own pre-release version line
(see MicrosoftTestingExtensionsOpenTelemetryVersion flowed through MSTest.Sdk.csproj).
-->
<MicrosoftTestingExtensionsOpenTelemetryVersion Condition=" '$(MicrosoftTestingExtensionsOpenTelemetryVersion)' == '' " >$(MicrosoftTestingExtensionsOpenTelemetryVersion)</MicrosoftTestingExtensionsOpenTelemetryVersion>
… feedback

- Remove top-level 'labels' object (not in CTRF spec; emitted illegal JSON
  with duplicate keys for multi-value traits like [TestCategory]). Instead
  emit:
    * top-level 'tags[]' populated from TestCategory trait values
      (mirrors the CTRF spec field and xunit's Category mapping)
    * 'extra.traits' as a grouped object { key: [values...] } that safely
      handles non-adjacent duplicate keys
  Addresses review feedback from @bradwilson.

- Mark TestingPlatformBuilderHook with [Experimental("TPEXP")] alongside
  CtrfReportExtensions, matching the user's request that all public APIs
  in this new extension ship as experimental. Updated PublicAPI.Unshipped.txt
  entries with the [TPEXP] prefix.

- Dogfood the extension in unit and integration test projects:
    * register AddCtrfReportProvider() in every test Program.cs that already
      lists the other built-in providers
    * add --report-ctrf --report-ctrf-filename to test/Directory.Build.targets
      TestingPlatformCommandLineArguments
    * add --report-ctrf to azure-pipelines.yml Release script so CI emits
      CTRF reports alongside the existing TRX and AzDO reports

- Update CtrfReportEngineTests for the new JSON shape (tags + extra.traits,
  no labels).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@Evangelink

Copy link
Copy Markdown
Member Author

@bradwilson — you're absolutely right on both points. Fixed in 208e9eb:

  1. labels removed. It wasn't in the CTRF spec, and as you noted the previous block would have emitted illegal JSON (duplicate property names) the moment a test had two [TestCategory] attributes.
  2. Replaced with two new outputs:
    • Top-level tags[] — populated from TestCategory trait values (the CTRF spec field, and matches xunit.v3's Categorytags convention).
    • extra.traits as a grouped object { key: [values...] }, so multi-valued traits stay representable without violating JSON's uniqueness recommendation. The grouping is robust against non-adjacent duplicate keys.

Example for a test with [TestCategory("A")], [TestCategory("B")], [Priority("1")]:

"tags": ["A", "B"],
"extra": {
  "traits": {
    "TestCategory": ["A", "B"],
    "Priority": ["1"]
  }
}

The corresponding unit test (GenerateReportAsync_IncludesTraitsUnderExtraTraits_AndPromotesTestCategoryToTags) and the "no traits → no tags/extra.traits" test were both updated. Let me know if the shape still looks off.

While in there I also addressed two other items from the owner discussion:

  • All public APIs marked [Experimental("TPEXP")]. Both CtrfReportExtensions and TestingPlatformBuilderHook now carry the attribute; PublicAPI.Unshipped.txt entries are prefixed with [TPEXP]. This diverges from the (older) HtmlReport/JUnit hook pattern but matches the explicit ask that the new surface ship as experimental.
  • CI dogfooding wired up. Added --report-ctrf to azure-pipelines.yml and test/Directory.Build.targets, and registered AddCtrfReportProvider() in every test Program.cs so the pipeline (and local dotnet test) actually emit CTRF reports alongside TRX/AzDO.

Verified locally: all 475 tests in Microsoft.Testing.Extensions.UnitTests pass, and a real CTRF JSON is produced under TestResults/quick.ctrf.json with the expected new shape.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants