From f430b4a1309bee7a7c30afb8cf71ef533b6f0350 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Fri, 17 Jul 2026 15:32:25 +0200 Subject: [PATCH 1/3] Document MTP message bus file artifacts and correct stale extension APIs Addresses microsoft/testfx issue #3261. Updates the MTP extensions and test framework articles to match current testfx main: Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 77cb3512-ac1c-45cc-a0c8-3c583c919e8e --- ...esting-platform-architecture-extensions.md | 141 +++++++++++++++--- ...ng-platform-architecture-test-framework.md | 9 +- 2 files changed, 129 insertions(+), 21 deletions(-) diff --git a/docs/core/testing/microsoft-testing-platform-architecture-extensions.md b/docs/core/testing/microsoft-testing-platform-architecture-extensions.md index aa32c4872d13e..b952d347769ba 100644 --- a/docs/core/testing/microsoft-testing-platform-architecture-extensions.md +++ b/docs/core/testing/microsoft-testing-platform-architecture-extensions.md @@ -3,7 +3,7 @@ title: Build extensions for Microsoft.Testing.Platform (MTP) description: Learn how to create in-process and out-of-process extensions for Microsoft.Testing.Platform (MTP). author: MarcoRossignoli ms.author: mrossignoli -ms.date: 06/16/2026 +ms.date: 07/17/2026 ai-usage: ai-assisted --- @@ -150,9 +150,9 @@ Please note that the `ValidateCommandLineOptionsAsync` method provides the [`ICo ### The `ITestSessionLifetimeHandler` extensions -The `ITestSessionLifeTimeHandler` is an *in-process* extension that enables the execution of code *before* and *after* the test session. +The `ITestSessionLifetimeHandler` is an *in-process* extension that enables the execution of code *before* and *after* the test session. -To register a custom `ITestSessionLifeTimeHandler`, utilize the following API: +To register a custom `ITestSessionLifetimeHandler`, utilize the following API: ```csharp var builder = await TestApplication.CreateBuilderAsync(args); @@ -160,7 +160,7 @@ var builder = await TestApplication.CreateBuilderAsync(args); // ... builder.TestHost.AddTestSessionLifetimeHandle( - static serviceProvider => new CustomTestSessionLifeTimeHandler()); + static serviceProvider => new CustomTestSessionLifetimeHandler()); ``` The factory utilizes the [IServiceProvider](./microsoft-testing-platform-architecture-services.md#the-imessagebus-service) to gain access to the suite of services offered by the testing platform. @@ -168,18 +168,21 @@ The factory utilizes the [IServiceProvider](./microsoft-testing-platform-archite > [!IMPORTANT] > The sequence of registration is significant, as the APIs are called in the order they were registered. -The `ITestSessionLifeTimeHandler` interface includes the following methods: +The `ITestSessionLifetimeHandler` interface includes the following methods: ```csharp public interface ITestSessionLifetimeHandler : ITestHostExtension { - Task OnTestSessionStartingAsync( - SessionUid sessionUid, - CancellationToken cancellationToken); + Task OnTestSessionStartingAsync(ITestSessionContext testSessionContext); - Task OnTestSessionFinishingAsync( - SessionUid sessionUid, - CancellationToken cancellationToken); + Task OnTestSessionFinishingAsync(ITestSessionContext testSessionContext); +} + +public interface ITestSessionContext +{ + SessionUid SessionUid { get; } + + CancellationToken CancellationToken { get; } } public readonly struct SessionUid(string value) @@ -192,15 +195,18 @@ public interface ITestHostExtension : IExtension } ``` +> [!IMPORTANT] +> In MTP 2.0.0, both methods changed to take a single `ITestSessionContext` parameter that exposes the `SessionUid` and the `CancellationToken`. In MTP 1.x, each method took a separate `SessionUid` and `CancellationToken` argument. For more information, see [Migrate from Microsoft.Testing.Platform (MTP) v1 to v2](microsoft-testing-platform-migration-from-v1-to-v2.md#api-signature-changes). + The `ITestSessionLifetimeHandler` is a type of `ITestHostExtension`, which serves as a base for all *test host* extensions. Like all other extension points, it also inherits from [IExtension](./microsoft-testing-platform-architecture-test-framework.md#the-iextension-interface). Therefore, like any other extension, you can choose to enable or disable it using the `IExtension.IsEnabledAsync` API. Consider the following details for this API: -`OnTestSessionStartingAsync`: This method is invoked prior to the commencement of the test session and receives the `SessionUid` object, which provides an opaque identifier for the current test session. +`OnTestSessionStartingAsync`: This method is invoked prior to the commencement of the test session and receives the `ITestSessionContext`, whose `SessionUid` provides an opaque identifier for the current test session. `OnTestSessionFinishingAsync`: This method is invoked after the completion of the test session, ensuring that the [testing framework](./microsoft-testing-platform-architecture-test-framework.md#test-framework-extension) has finished executing all tests and has reported all relevant data to the platform. Typically, in this method, the extension employs the [`IMessageBus`](./microsoft-testing-platform-architecture-services.md#the-imessagebus-service) to transmit custom assets or data to the shared platform bus. This method can also signal to any custom *out-of-process* extension that the test session has concluded. -Finally, both APIs take a `CancellationToken` which the extension is expected to honor. +Finally, the `ITestSessionContext` exposes a `CancellationToken` which the extension is expected to honor. If your extension requires intensive initialization and you need to use the async/await pattern, you can refer to the [`Async extension initialization and cleanup`](#asynchronous-initialization-and-cleanup-of-extensions). If you need to *share state* between extension points, you can refer to the [`CompositeExtensionFactory`](#the-compositeextensionfactoryt) section. @@ -257,7 +263,7 @@ Finally, both APIs take a `CancellationToken` which the extension is expected to ### The `IDataConsumer` extensions -The `IDataConsumer` is an *in-process* extension capable of subscribing to and receiving `IData` information that is pushed to the [IMessageBus](./microsoft-testing-platform-architecture-services.md#the-imessagebus-service) by the [testing framework](./microsoft-testing-platform-architecture-test-framework.md#test-framework-extension) and its extensions. +The `IDataConsumer` is an *in-process* extension capable of subscribing to and receiving `IData` information that is published to the [IMessageBus](./microsoft-testing-platform-architecture-services.md#the-imessagebus-service) by the [testing framework](./microsoft-testing-platform-architecture-test-framework.md#test-framework-extension) and its extensions. *This extension point is crucial as it enables developers to gather and process all the information generated during a test session.* @@ -280,7 +286,7 @@ The factory utilizes the [IServiceProvider](./microsoft-testing-platform-archite The `IDataConsumer` interface includes the following methods: ```csharp -public interface IDataConsumer : ITestHostExtension +public interface IDataConsumer : IExtension { Type[] DataTypesConsumed { get; } @@ -297,11 +303,14 @@ public interface IData } ``` -The `IDataConsumer` is a type of `ITestHostExtension`, which serves as a base for all *test host* extensions. Like all other extension points, it also inherits from [IExtension](./microsoft-testing-platform-architecture-test-framework.md#the-iextension-interface). Therefore, like any other extension, you can choose to enable or disable it using the `IExtension.IsEnabledAsync` API. +> [!IMPORTANT] +> In MTP 2.0.0, `IDataConsumer` moved to the `Microsoft.Testing.Platform.Extensions` namespace and now extends [`IExtension`](./microsoft-testing-platform-architecture-test-framework.md#the-iextension-interface) directly. In MTP 1.x, it extended `ITestHostExtension`. You still register it with `builder.TestHost.AddDataConsumer(...)`. For more information, see [Migrate from Microsoft.Testing.Platform (MTP) v1 to v2](microsoft-testing-platform-migration-from-v1-to-v2.md#api-signature-changes). + +The `IDataConsumer` inherits from [IExtension](./microsoft-testing-platform-architecture-test-framework.md#the-iextension-interface). Therefore, like any other extension, you can choose to enable or disable it using the `IExtension.IsEnabledAsync` API. `DataTypesConsumed`: This property returns a list of `Type` that this extension plans to consume. It corresponds to `IDataProducer.DataTypesProduced`. Notably, an `IDataConsumer` can subscribe to multiple types originating from different `IDataProducer` instances without any issues. -`ConsumeAsync`: This method is triggered whenever data of a type to which the current consumer is subscribed is pushed onto the [`IMessageBus`](./microsoft-testing-platform-architecture-services.md#the-imessagebus-service). It receives the `IDataProducer` to provide details about the data payload's producer, as well as the `IData` payload itself. As you can see, `IData` is a generic placeholder interface that contains general informative data. The ability to push different types of `IData` implies that the consumer needs to *switch* on the type itself to cast it to the correct type and access the specific information. +`ConsumeAsync`: This method is triggered whenever data of a type to which the current consumer is subscribed is published to the [`IMessageBus`](./microsoft-testing-platform-architecture-services.md#the-imessagebus-service). It receives the `IDataProducer` to provide details about the data payload's producer, as well as the `IData` payload itself. As you can see, `IData` is a generic placeholder interface that contains general informative data. The ability to publish different types of `IData` implies that the consumer needs to *switch* on the type itself to cast it to the correct type and access the specific information. A sample implementation of a consumer that wants to elaborate the [`TestNodeUpdateMessage`](./microsoft-testing-platform-architecture-test-framework.md#the-testnodeupdatemessage-data) produced by a [testing framework](./microsoft-testing-platform-architecture-test-framework.md#test-framework-extension) could be: @@ -351,15 +360,106 @@ internal class CustomDataConsumer : IDataConsumer, IOutputDeviceDataProducer Finally, the API takes a `CancellationToken` which the extension is expected to honor. > [!IMPORTANT] -> It's crucial to process the payload directly within the `ConsumeAsync` method. The [IMessageBus](./microsoft-testing-platform-architecture-services.md#the-imessagebus-service) can manage both synchronous and asynchronous processing, coordinating the execution with the [testing framework](./microsoft-testing-platform-architecture-test-framework.md#test-framework-extension). Although the consumption process is entirely asynchronous and doesn't block the [IMessageBus.Push](./microsoft-testing-platform-architecture-services.md#the-imessagebus-service) at the time of writing, this is an implementation detail that may change in the future due to future requirements. However, the platform ensures that this method is always called once, eliminating the need for complex synchronization, as well as managing the scalability of the consumers. +> Process the payload directly within the `ConsumeAsync` method. A regular `IDataConsumer` consumes data asynchronously: the [IMessageBus](./microsoft-testing-platform-architecture-services.md#the-imessagebus-service) queues each published payload and processes it on a background loop, so `IMessageBus.PublishAsync` doesn't block the producer, and there's no guarantee about when `ConsumeAsync` runs relative to the producer continuing its work. The platform serializes delivery so that only one payload is processed at a time per consumer, which eliminates the need for complex synchronization within a single consumer. + + + +> [!NOTE] +> For scenarios that need a guarantee that consumption happens before the producer proceeds (for example, before a test starts running), MTP 2.3.0 introduced the experimental `IBlockingDataConsumer` marker interface (requires the `TPEXP` diagnostic to be suppressed). A consumer that also implements `IBlockingDataConsumer` is invoked *inline* by the message bus: calls are serialized, `PublishAsync` blocks until `ConsumeAsync` completes, and any exception thrown by `ConsumeAsync` propagates back to the producer that published the data. The message bus skips delivering a producer's data back to that same producer (same UID), so publishing under your own UID is safe. However, a blocking consumer must not, from within `ConsumeAsync`, publish data that is routed back to itself under a *different* producer UID, because that reentrancy would deadlock. > [!WARNING] -> When using `IDataConsumer` in conjunction with [ITestHostProcessLifetimeHandler](#the-itestsessionlifetimehandler-extensions) within a [composite extension point](#the-compositeextensionfactoryt), **it's crucial to disregard any data received post the execution of [ITestSessionLifetimeHandler.OnTestSessionFinishingAsync](#the-itestsessionlifetimehandler-extensions)**. The `OnTestSessionFinishingAsync` is the final opportunity to process accumulated data and transmit new information to the [IMessageBus](./microsoft-testing-platform-architecture-services.md#the-imessagebus-service), hence, any data consumed beyond this point will not be *utilizable* by the extension. +> When using `IDataConsumer` in conjunction with [ITestSessionLifetimeHandler](#the-itestsessionlifetimehandler-extensions) within a [composite extension point](#the-compositeextensionfactoryt), **it's crucial to disregard any data received post the execution of [ITestSessionLifetimeHandler.OnTestSessionFinishingAsync](#the-itestsessionlifetimehandler-extensions)**. The `OnTestSessionFinishingAsync` is the final opportunity to process accumulated data and transmit new information to the [IMessageBus](./microsoft-testing-platform-architecture-services.md#the-imessagebus-service), hence, any data consumed beyond this point will not be *utilizable* by the extension. If your extension requires intensive initialization and you need to use the async/await pattern, you can refer to the [`Async extension initialization and cleanup`](#asynchronous-initialization-and-cleanup-of-extensions). If you need to *share state* between extension points, you can refer to the [`CompositeExtensionFactory`](#the-compositeextensionfactoryt) section. +### Message bus file artifacts + +A custom `IData` payload has no automatic UI or command-line output. The platform only surfaces data for which a matching consumer is registered, so if you publish your own `IData` type and nothing consumes it, nothing is printed or forwarded. To make files that your extension produces visible to users and tooling, publish one of the built-in file artifact messages, which the built-in *terminal* and *dotnet test* consumers already recognize. + +For run-level or session-level files, publish a `FileArtifact` or a `SessionFileArtifact`. Both were introduced in MTP 1.0.0 and live in the `Microsoft.Testing.Platform.Extensions.Messages` namespace: + +- `FileArtifact` is unscoped. Use it for a file that isn't tied to a specific test session. +- `SessionFileArtifact` is scoped to a run/session through its `SessionUid`. Use it for artifacts produced for the run as a whole, such as coverage results, reports, dumps, or recorded videos. + +The built-in terminal and `dotnet test` consumers pick up both types and print or forward the file paths, so the files become discoverable in the console output and across the `dotnet test` pipe. Because consumers control the ultimate presentation, keep each file on disk and available for as long as it might be consumed or forwarded; don't delete it inside the same `PublishAsync` call. + +The artifact object itself carries no producer identity. The message bus supplies the originating `IDataProducer` to each consumer through the `dataProducer` argument of `IDataConsumer.ConsumeAsync`, so consumers learn who produced a file from that argument, not from the artifact. The message bus also doesn't take ownership of, move, or delete the referenced file: the producer owns the file's lifetime, and consumers only receive, read, or forward its path. + +> [!NOTE] +> Third-party `IDataConsumer` registration is public only on `builder.TestHost` (the *in-process* test host). There's no public `builder.TestHostControllers.AddDataConsumer` API for *out-of-process* consumers. The first-party report extensions that display artifacts use internal platform integration that custom extensions can't rely on. To surface files from your own extension, publish the built-in artifact messages described here and let the built-in consumers present them. + +A producer that publishes a session artifact must implement `IDataProducer` and list the exact runtime message types it publishes in `DataTypesProduced`. The following example publishes a coverage report when the session finishes: + +```csharp +internal sealed class CoverageReportProducer(IMessageBus messageBus) + : IDataProducer, ITestSessionLifetimeHandler +{ + public string Uid => nameof(CoverageReportProducer); + public string Version => "1.0.0"; + public string DisplayName => "Coverage report producer"; + public string Description => "Publishes the coverage report as a session artifact."; + + // List the exact runtime message types this producer publishes. + public Type[] DataTypesProduced => new[] { typeof(SessionFileArtifact) }; + + public Task IsEnabledAsync() => Task.FromResult(true); + + public Task OnTestSessionStartingAsync(ITestSessionContext context) + => Task.CompletedTask; + + public Task OnTestSessionFinishingAsync(ITestSessionContext context) + { + var report = new FileInfo("coverage.cobertura.xml"); + return messageBus.PublishAsync( + this, + new SessionFileArtifact( + context.SessionUid, + report, + "Code coverage", + "Cobertura coverage report for the run.")); + } +} +``` + +To attach a file to a *specific test* so that the terminal, `dotnet test`, and IDEs associate and display it with that test, don't publish a stand-alone file artifact. Instead, add one or more `FileArtifactProperty` entries to the `TestNode` that your [testing framework](./microsoft-testing-platform-architecture-test-framework.md#test-framework-extension) reports through a [`TestNodeUpdateMessage`](./microsoft-testing-platform-architecture-test-framework.md#the-testnodeupdatemessage-data). `FileArtifactProperty` was introduced in MTP 1.7.0: + +```csharp +var testNode = new TestNode +{ + Uid = testUid, + DisplayName = testDisplayName, + Properties = new PropertyBag( + PassedTestNodeStateProperty.CachedInstance, + new FileArtifactProperty( + new FileInfo("screenshot.png"), + "Failure screenshot", + "Screenshot captured while the test ran.")), +}; + +await messageBus.PublishAsync( + dataProducer, + new TestNodeUpdateMessage(sessionUid, testNode)); +``` + +> [!IMPORTANT] +> `TestNodeFileArtifact` is obsolete and was removed in MTP 2.0.0. Use `FileArtifactProperty` on the `TestNode` to attach test-level files. For more information, see [Migrate from Microsoft.Testing.Platform (MTP) v1 to v2](microsoft-testing-platform-migration-from-v1-to-v2.md#removed-obsolete-types). + +> [!NOTE] +> MTP 2.4.0 (unreleased as of July 2026) adds an experimental `kind` constructor overload and `Kind` property to `FileArtifact` and `SessionFileArtifact` (requires the `TPEXP` diagnostic to be suppressed). `Kind` is a producer-asserted, reverse-DNS identifier of the artifact *format* (for example, `microsoft.testing.trx`, `microsoft.testing.junit`, `microsoft.testing.ctrf`, or `microsoft.testing.html`) that post-processing can use to group artifacts of the same format for consolidation. Leave it `null` or omit it when the producer doesn't declare a known kind. At this time, `Kind` is only a metadata contract; don't assume broad merge orchestration beyond that. +> +> ```csharp +> #pragma warning disable TPEXP // Experimental API. +> new SessionFileArtifact( +> context.SessionUid, +> trxFile, +> "TRX report", +> "Test results in TRX format.", +> kind: "microsoft.testing.trx"); +> #pragma warning restore TPEXP +> ``` + ### The `ITestHostEnvironmentVariableProvider` extensions The `ITestHostEnvironmentVariableProvider` is an *out-of-process* extension that enables you to establish custom environment variables for the test host. Utilizing this extension point ensures that the testing platform will initiate a new host with the appropriate environment variables, as detailed in the [architecture](./microsoft-testing-platform-architecture.md) section. @@ -687,3 +787,6 @@ The following combinations are possible: * For `ITestApplicationBuilder.TestHost`, you can combine `IDataConsumer` and `ITestSessionLifetimeHandler`. * For `ITestApplicationBuilder.TestHostControllers`, you can combine `ITestHostEnvironmentVariableProvider` and `ITestHostProcessLifetimeHandler`. + +> [!NOTE] +> `IDataConsumer` is an *in-process* extension, so custom consumers are registered only through `builder.TestHost` (including via `CompositeExtensionFactory`). There's no public API to register an `IDataConsumer` on `builder.TestHostControllers`. diff --git a/docs/core/testing/microsoft-testing-platform-architecture-test-framework.md b/docs/core/testing/microsoft-testing-platform-architecture-test-framework.md index db9e93809a210..6a9ffb12b45cf 100644 --- a/docs/core/testing/microsoft-testing-platform-architecture-test-framework.md +++ b/docs/core/testing/microsoft-testing-platform-architecture-test-framework.md @@ -3,7 +3,7 @@ title: Build a test framework for Microsoft.Testing.Platform (MTP) description: Learn how to create a custom test framework for Microsoft.Testing.Platform (MTP), including registration, lifecycle, and test node reporting. author: MarcoRossignoli ms.author: mrossignoli -ms.date: 02/24/2026 +ms.date: 07/17/2026 ai-usage: ai-assisted --- @@ -390,7 +390,12 @@ internal sealed class TestingFramework } ``` -If your test adapter requires the publication of *files* during execution, you can find the recognized properties in this source file: . As you can see, you can provide file assets in a general manner or associate them with a specific `TestNode`. Remember, if you intend to push a `SessionFileArtifact`, you must declare it to the platform in advance, as shown below: +If your test adapter produces *files* during execution, the platform recognizes two distinct mechanisms, and the right one depends on whether the file belongs to a specific test or to the run as a whole. For details, see [Message bus file artifacts](./microsoft-testing-platform-architecture-extensions.md#message-bus-file-artifacts). + +- Run-level or session-level files are standalone bus messages: publish a `FileArtifact` (unscoped) or a `SessionFileArtifact` (scoped to the run through its `SessionUid`). +- Test-specific attachments aren't standalone messages. Add them as `FileArtifactProperty` entries on `TestNode.Properties` inside a `TestNodeUpdateMessage`, so the terminal, `dotnet test`, and IDEs associate each file with its test. + +If you intend to publish a `SessionFileArtifact`, you must declare that runtime type to the platform in advance through `DataTypesProduced`, as shown below. Note that `FileArtifactProperty` isn't listed separately, because it travels inside a `TestNodeUpdateMessage` rather than as its own message type: ```csharp internal sealed class TestingFramework From 35f425cbe5ebcedaa3d20c12262908490b4e8052 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Fri, 17 Jul 2026 15:40:32 +0200 Subject: [PATCH 2/3] Use consistent 'standalone' spelling per review Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com> Copilot-Session: 77cb3512-ac1c-45cc-a0c8-3c583c919e8e --- .../microsoft-testing-platform-architecture-extensions.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/testing/microsoft-testing-platform-architecture-extensions.md b/docs/core/testing/microsoft-testing-platform-architecture-extensions.md index b952d347769ba..0d6d9f30aefa3 100644 --- a/docs/core/testing/microsoft-testing-platform-architecture-extensions.md +++ b/docs/core/testing/microsoft-testing-platform-architecture-extensions.md @@ -423,7 +423,7 @@ internal sealed class CoverageReportProducer(IMessageBus messageBus) } ``` -To attach a file to a *specific test* so that the terminal, `dotnet test`, and IDEs associate and display it with that test, don't publish a stand-alone file artifact. Instead, add one or more `FileArtifactProperty` entries to the `TestNode` that your [testing framework](./microsoft-testing-platform-architecture-test-framework.md#test-framework-extension) reports through a [`TestNodeUpdateMessage`](./microsoft-testing-platform-architecture-test-framework.md#the-testnodeupdatemessage-data). `FileArtifactProperty` was introduced in MTP 1.7.0: +To attach a file to a *specific test* so that the terminal, `dotnet test`, and IDEs associate and display it with that test, don't publish a standalone file artifact. Instead, add one or more `FileArtifactProperty` entries to the `TestNode` that your [testing framework](./microsoft-testing-platform-architecture-test-framework.md#test-framework-extension) reports through a [`TestNodeUpdateMessage`](./microsoft-testing-platform-architecture-test-framework.md#the-testnodeupdatemessage-data). `FileArtifactProperty` was introduced in MTP 1.7.0: ```csharp var testNode = new TestNode From 0e35982f3487295110c626e88303ec9290fee207 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Amaury=20Lev=C3=A9?= Date: Tue, 21 Jul 2026 09:41:19 +0200 Subject: [PATCH 3/3] Apply suggestions from code review Co-authored-by: Genevieve Warren <24882762+gewarren@users.noreply.github.com> --- .../microsoft-testing-platform-architecture-extensions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/core/testing/microsoft-testing-platform-architecture-extensions.md b/docs/core/testing/microsoft-testing-platform-architecture-extensions.md index 0d6d9f30aefa3..98ed287d3863b 100644 --- a/docs/core/testing/microsoft-testing-platform-architecture-extensions.md +++ b/docs/core/testing/microsoft-testing-platform-architecture-extensions.md @@ -152,7 +152,7 @@ Please note that the `ValidateCommandLineOptionsAsync` method provides the [`ICo The `ITestSessionLifetimeHandler` is an *in-process* extension that enables the execution of code *before* and *after* the test session. -To register a custom `ITestSessionLifetimeHandler`, utilize the following API: +To register a custom `ITestSessionLifetimeHandler`, use the following API: ```csharp var builder = await TestApplication.CreateBuilderAsync(args); @@ -206,7 +206,7 @@ Consider the following details for this API: `OnTestSessionFinishingAsync`: This method is invoked after the completion of the test session, ensuring that the [testing framework](./microsoft-testing-platform-architecture-test-framework.md#test-framework-extension) has finished executing all tests and has reported all relevant data to the platform. Typically, in this method, the extension employs the [`IMessageBus`](./microsoft-testing-platform-architecture-services.md#the-imessagebus-service) to transmit custom assets or data to the shared platform bus. This method can also signal to any custom *out-of-process* extension that the test session has concluded. -Finally, the `ITestSessionContext` exposes a `CancellationToken` which the extension is expected to honor. +Finally, the `ITestSessionContext` exposes a `CancellationToken` that the extension is expected to honor. If your extension requires intensive initialization and you need to use the async/await pattern, you can refer to the [`Async extension initialization and cleanup`](#asynchronous-initialization-and-cleanup-of-extensions). If you need to *share state* between extension points, you can refer to the [`CompositeExtensionFactory`](#the-compositeextensionfactoryt) section. @@ -444,7 +444,7 @@ await messageBus.PublishAsync( ``` > [!IMPORTANT] -> `TestNodeFileArtifact` is obsolete and was removed in MTP 2.0.0. Use `FileArtifactProperty` on the `TestNode` to attach test-level files. For more information, see [Migrate from Microsoft.Testing.Platform (MTP) v1 to v2](microsoft-testing-platform-migration-from-v1-to-v2.md#removed-obsolete-types). +> `TestNodeFileArtifact` is obsolete and was removed in MTP 2.0.0. To attach test-level files, use `FileArtifactProperty` on the `TestNode`. For more information, see [Migrate from Microsoft.Testing.Platform (MTP) v1 to v2](microsoft-testing-platform-migration-from-v1-to-v2.md#removed-obsolete-types). > [!NOTE] > MTP 2.4.0 (unreleased as of July 2026) adds an experimental `kind` constructor overload and `Kind` property to `FileArtifact` and `SessionFileArtifact` (requires the `TPEXP` diagnostic to be suppressed). `Kind` is a producer-asserted, reverse-DNS identifier of the artifact *format* (for example, `microsoft.testing.trx`, `microsoft.testing.junit`, `microsoft.testing.ctrf`, or `microsoft.testing.html`) that post-processing can use to group artifacts of the same format for consolidation. Leave it `null` or omit it when the producer doesn't declare a known kind. At this time, `Kind` is only a metadata contract; don't assume broad merge orchestration beyond that.