3 hours ago
Reliable testing of UWP and WinUI 3 apps needs the app’s real UI dispatcher,
not just a single-threaded apartment (STA) thread, so that test setup, async
test code, and cleanup all run with UI-thread access. A WinUI 3 test still
looks this simple:
With MSTest
4.5 and Microsoft.Testing.Platform (MTP) 2.5,
you can use the same UI-thread testing pattern for UWP and WinUI 3 apps. The
supported models are classic and modern UWP, packaged or unpackaged WinUI 3,
and WinUI hosts that use AppContainer.
picks the launch path for each model. It starts unpackaged apps
directly and uses
to register and activate packaged apps by AUMID. For AppContainer hosts, it
also grants the exact package SID access to the controller and report pipes.
Packaged and sandboxed apps do not start as the initial test tool.
launches a normal full-trust sidecar controller that owns
test arguments, cancel requests, reports, retries, and the final exit code. It
then starts the app that hosts the tests. No,
, , or Visual Studio deployment
runtime is used.
For more background, read the
introduction to MSTest.Sdk
and the overview of
Microsoft.Testing.Platform support across .NET test frameworks.
Packaged activation needs machine setup
To register an unsigned build-output layout, you need Developer Mode or a similar sideloading policy. Therefore, run AppContainer tests non-elevated, and confirm the policy on your CI agent, not just your workstation.
Choose the model for UWP and WinUI 3 apps
Packaging and sandboxing are separate choices. Packaging adds MSIX
identity and AUMID activation; the trust level decides whether the process is
full trust or runs in AppContainer.
Application model
Identity and trust
MTP test-host path
Classic UWP ()
MSIX, AppContainer
Sidecar controller, UAP adapter/bootstrap assets, AUMID activation
Modern UWP ()
MSIX, AppContainer
Sidecar controller, Native AOT host, AUMID activation
Unpackaged WinUI 3
No package identity, full trust
Direct apphost launch
Packaged WinUI 3
MSIX, full trust
Sidecar controller, package registration, AUMID activation
WinUI 3 with AppContainer trust
MSIX, AppContainer
Sidecar controller and exact package-SID pipe authorization
For WinUI, start unpackaged unless the behavior under test needs package
identity, packaged activation contracts, or an exact match with installed app
behavior. In contrast, UWP is inherently packaged and sandboxed.
Select MSTest.Sdk and MTP for UWP and WinUI 3 apps
At the solution or repo root, use to pin MSTest.Sdk 4.5
and select Microsoft.Testing.Platform for the native .NET 10
experience:
Otherwise, .NET 10 uses VSTest for.
MSTest.Sdk 4.5 includes MTP 2.5, the app-model sidecar controller, UWP
adapter/bootstrap assets, and the packaged launcher.
Configure UWP and WinUI 3 apps
Modern UWP
For a modern UWP project, the test setup is small:
Keep the app’s XAML, manifest, architecture, and Native AOT settings. Then,
from, pass the activation string to the generated MTP helper:
Classic UWP
Classic projects keep their existing UWP project structure and
import alongside . MSTest 4.5 includes the
UAP-compatible adapter, generated bootstrap, packaged-app launcher, and TRX
client assets. The sidecar builds the layout,
installs its declared frameworks, and starts the app by AUMID.
Classic and modern UWP builds still need the Visual Studio MSBuild/UWP
toolchain. However, they no longer need its VSTest runtime or deployment
provider.
See the UWP and WinUI testing guide
for the complete classic project imports and package layout.
Therefore, run UWP tests from a Developer PowerShell for Visual Studio. Build
with the desktop MSBuild toolchain, then invoke the MTP target:
AppContainer-configured WinUI 3
A packaged WinUI 3 app is full trust by default. For example, to test a
supported AppContainer setup, keep the packaged WinUI host
shown below and set in the manifest. The
host still receives normal process arguments, while MTP grants only that
package SID access to its controller, cancellation, TRX, HangDump, and Retry
pipes. Do not grant.
Then, run this AppContainer shape from a non-elevated Developer PowerShell
through the MTP MSBuild target:
Build the self-hosted WinUI 3 test app
The following complete WinUI 3 test app supplies that dispatcher and runs
MSTest inside the app itself. Build this shared host once; the packaged and
unpackaged deltas come afterward. Three pieces make up that shared project:
Configure the shared project
You need Windows, the .NET 10 SDK, and the Windows App SDK tooling (installed
by Visual Studio’s “Windows application development” workload, or restored
from NuGet if you build from the CLI). Then, start from Visual Studio’s
“Blank App, Packaged (WinUI 3 in Desktop)” template, retain its
and package assets for the packaged form, and apply
this shared project setup.
Define the application entry point
The points to this minimal :
Host the test run from OnLaunched
The WinUI app owns its entry point through this.
detects that entry point, suppresses its own competing ,
and generates a reusable
helper. Finally, the app’s code-behind creates the window, publishes its
dispatcher, and calls that helper:
That single override plays out in this order every time the app
starts, whether launched directly or activated by AUMID:
Assigning is important: a WinUI-generated entry point
returns, so without it a failing test run can look successful to a
build or CI system. owns MSTest references, extension
registration, and the packaged-app launcher registration. The
WinUI testing guide
covers the generated helper and hosting model in more depth.
Verify the dispatcher in a UI test
The app and tests now form one host. For example, this async test
checks that, the test body, and keep access to
the UI dispatcher:
can provide an STA thread, but it does not create a WinUI
dispatcher. dispatches the full MSTest call for each test,
including its setup and cleanup.
Apply deployment models to UWP and WinUI 3 apps
For UWP and WinUI 3 apps, apply the delta that matches the deployment model
you chose in “Choose the model for UWP and WinUI 3 apps.” keeps
the runner setup shared between both models.
Unpackaged WinUI 3 (default choice)
Add these properties and do not include MSIX manifest or package asset items:
The resulting apphost is a standard executable. As a result,
Microsoft.Testing.Platform uses its normal launch path, and this route does
not use VSTest’s appx runtime provider.
Packaged full-trust WinUI 3 (when you need identity)
Remove the two unpackaged overrides; do not leave
in the project. Retain the
template’s normal and package assets. The output then
has MSIX identity, so Windows must register the package layout and activate the
test host by AUMID. It needs a Windows target framework moniker (TFM) at
or later and Developer Mode or a similar sideloading policy for
unsigned build output.
In addition, adds and registers the packaged-app launcher for
packaged WinUI projects. Leave
unset. Its default, , enables the
packaged launch path only when a matching describes the
app. Otherwise it keeps the normal, faster launch path with no controller
restart or deployment-copy overhead. The
WinUI testing guide
covers the and overrides for less common scenarios.
Validate on your CI agent first
Confirm Developer Mode (or your sideloading policy) and a clean, passing exit code on your own packaged app and CI agent, as covered in “Packaged activation needs machine setup” above, before wiring this into a required gate.
Run it
Use for either deployment model. It launches the generated
apphost, which hosts Microsoft.Testing.Platform inside the process that owns
the window and dispatcher:
The .NET 10 native MTP runner also supports for either
deployment model:
For an unpackaged app, you can also launch the generated apphost directly.
However, do not use, which puts in the middle and
can break WinUI resource loading.
A window flashes briefly while the test runs, and the console reports a
summary:
The packaged development layout can remain registered after the run. Remove
it when needed by using its manifest name:
Validate UWP and WinUI 3 apps on the target machine
For a packaged test host, a passing build is only the first step. Therefore,
check your actual CI agent image, not just a developer workstation, for a user
context that can register the package, the required Developer Mode or
sideloading policy, and the frameworks declared by the package. Otherwise, a
workstation that already has the UWP framework packages or Windows App SDK
runtime installed can hide a gap that only appears on a clean build agent.
Framework-dependent WinUI 3 apps need the matching Windows App SDK runtime on
the agent. A self-contained WinUI 3 build can remove that machine need, but test
it using the exact package model that will run in CI.
Start testing UWP and WinUI 3 apps
Use this checklist to start testing UWP and WinUI 3 apps:
Get the UWP and WinUI testing guide
One test platform for UWP and WinUI 3 apps
Keep, the generated MTP helper, and across the
Windows app models. Let the SDK choose direct startup for unpackaged
WinUI or the sidecar controller for package registration, AUMID activation,
and AppContainer isolation.
Three takeaways to carry forward:
For more detail, see the UWP and WinUI testing guide
and the MSTest documentation.
The post UWP and WinUI 3 apps: UI testing with MSTest appeared first on .NET Blog.
not just a single-threaded apartment (STA) thread, so that test setup, async
test code, and cleanup all run with UI-thread access. A WinUI 3 test still
looks this simple:
Code:
[code][UITestMethod]
public async Task GridCanBeCreatedOnTheUIThread()
{
await Task.Yield();
var grid = new Grid();
Assert.IsTrue(grid.DispatcherQueue.HasThreadAccess);
}[/code]With MSTest
4.5 and Microsoft.Testing.Platform (MTP) 2.5,
you can use the same UI-thread testing pattern for UWP and WinUI 3 apps. The
supported models are classic and modern UWP, packaged or unpackaged WinUI 3,
and WinUI hosts that use AppContainer.
Code:
MSTest.Sdkdirectly and uses
Code:
Microsoft.Testing.Extensions.PackagedAppto register and activate packaged apps by AUMID. For AppContainer hosts, it
also grants the exact package SID access to the controller and report pipes.
Packaged and sandboxed apps do not start as the initial test tool.
Code:
MSTest.Sdktest arguments, cancel requests, reports, retries, and the final exit code. It
then starts the app that hosts the tests. No
Code:
Microsoft.NET.Test.SdkCode:
vstest.consoleCode:
UwpTestHostRuntimeProviderruntime is used.
For more background, read the
introduction to MSTest.Sdk
and the overview of
Microsoft.Testing.Platform support across .NET test frameworks.
Packaged activation needs machine setup
To register an unsigned build-output layout, you need Developer Mode or a similar sideloading policy. Therefore, run AppContainer tests non-elevated, and confirm the policy on your CI agent, not just your workstation.
Choose the model for UWP and WinUI 3 apps
Packaging and sandboxing are separate choices. Packaging adds MSIX
identity and AUMID activation; the trust level decides whether the process is
full trust or runs in AppContainer.
Application model
Identity and trust
MTP test-host path
Classic UWP (
Code:
uap10.0MSIX, AppContainer
Sidecar controller, UAP adapter/bootstrap assets, AUMID activation
Modern UWP (
Code:
UseUwpMSIX, AppContainer
Sidecar controller, Native AOT host, AUMID activation
Unpackaged WinUI 3
No package identity, full trust
Direct apphost launch
Packaged WinUI 3
MSIX, full trust
Sidecar controller, package registration, AUMID activation
WinUI 3
Code:
packagedClassicAppMSIX, AppContainer
Sidecar controller and exact package-SID pipe authorization
For WinUI, start unpackaged unless the behavior under test needs package
identity, packaged activation contracts, or an exact match with installed app
behavior. In contrast, UWP is inherently packaged and sandboxed.
Select MSTest.Sdk and MTP for UWP and WinUI 3 apps
At the solution or repo root, use
Code:
global.jsonand select Microsoft.Testing.Platform for the native .NET 10
Code:
dotnet testCode:
[code]{
"test": {
"runner": "Microsoft.Testing.Platform"
},
"msbuild-sdks": {
"MSTest.Sdk": "4.5.0"
}
}[/code]Otherwise, .NET 10 uses VSTest for
Code:
dotnet testMSTest.Sdk 4.5 includes MTP 2.5, the app-model sidecar controller, UWP
adapter/bootstrap assets, and the packaged launcher.
Configure UWP and WinUI 3 apps
Modern UWP
For a modern UWP project, the test setup is small:
Code:
[code]<Project Sdk="MSTest.Sdk">
<PropertyGroup>
<TargetFramework>net10.0-windows10.0.26100.0</TargetFramework>
<UseUwp>true</UseUwp>
<PublishAot>true</PublishAot>
</PropertyGroup>
</Project>[/code]Keep the app’s XAML, manifest, architecture, and Native AOT settings. Then,
from
Code:
OnLaunchedCode:
[code]using Microsoft.Testing.Extensions;
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
Window.Current.Activate();
string[] testArguments =
PackagedAppExtensions.GetTestApplicationArguments(args.Arguments);
Environment.ExitCode =
await MicrosoftTestingPlatformApplication.RunAsync(testArguments);
Exit();
}[/code]Classic UWP
Classic
Code:
uap10.0import
Code:
MSTest.SdkCode:
MSBuild.Sdk.ExtrasUAP-compatible adapter, generated bootstrap, packaged-app launcher, and TRX
client assets. The sidecar builds the
Code:
.build.appxrecipeinstalls its declared frameworks, and starts the app by AUMID.
Classic and modern UWP builds still need the Visual Studio MSBuild/UWP
toolchain. However, they no longer need its VSTest runtime or deployment
provider.
See the UWP and WinUI testing guide
for the complete classic project imports and package layout.
Therefore, run UWP tests from a Developer PowerShell for Visual Studio. Build
with the desktop MSBuild toolchain, then invoke the MTP target:
Code:
[code]msbuild .\MyUwpTests.sln /restore /p:Configuration=Release /p:Platform=x64
msbuild .\MyUwpTests.csproj /t:InvokeTestingPlatform /p:Configuration=Release /p:Platform=x64[/code]AppContainer-configured WinUI 3
A packaged WinUI 3 app is full trust by default. For example, to test a
supported
Code:
packagedClassicAppshown below and set
Code:
uap10:TrustLevel="appContainer"host still receives normal process arguments, while MTP grants only that
package SID access to its controller, cancellation, TRX, HangDump, and Retry
pipes. Do not grant
Code:
ALL APPLICATION PACKAGESThen, run this AppContainer shape from a non-elevated Developer PowerShell
through the MTP MSBuild target:
Code:
[code]dotnet build .\MyAppContainerTests.csproj -c Release -p:Platform=x64
dotnet msbuild .\MyAppContainerTests.csproj /t:InvokeTestingPlatform /p:Configuration=Release /p:Platform=x64[/code]Build the self-hosted WinUI 3 test app
The following complete WinUI 3 test app supplies that dispatcher and runs
MSTest inside the app itself. Build this shared host once; the packaged and
unpackaged deltas come afterward. Three pieces make up that shared project:
Configure the shared project
You need Windows, the .NET 10 SDK, and the Windows App SDK tooling (installed
by Visual Studio’s “Windows application development” workload, or restored
from NuGet if you build from the CLI). Then, start from Visual Studio’s
“Blank App, Packaged (WinUI 3 in Desktop)” template, retain its
Code:
Package.appxmanifestthis shared project setup.
Code:
[code]<Project Sdk="MSTest.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0-windows10.0.19041.0</TargetFramework>
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion>
<UseWinUI>true</UseWinUI>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<Page Remove="UnitTestApp.xaml" />
<ApplicationDefinition Include="UnitTestApp.xaml" />
<ProjectCapability Include="TestContainer" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Microsoft.WindowsAppSDK"
Version="1.8.251106002" />
</ItemGroup>
</Project>[/code]Define the application entry point
The
Code:
ApplicationDefinitionCode:
UnitTestApp.xamlCode:
[code]<Application
x:Class="MyWinUiTests.UnitTestApp"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources />
</Application>[/code]Host the test run from OnLaunched
The WinUI app owns its entry point through this
Code:
ApplicationDefinitionCode:
MSTest.SdkCode:
Mainand generates a reusable
Code:
MicrosoftTestingPlatformApplication.RunAsynchelper. Finally, the app’s code-behind creates the window, publishes its
dispatcher, and calls that helper:
Code:
[code]using Microsoft.UI.Xaml;
using Microsoft.VisualStudio.TestTools.UnitTesting.AppContainer;
namespace MyWinUiTests;
public partial class UnitTestApp : Application
{
private Window? _window;
public UnitTestApp() => InitializeComponent();
protected override async void OnLaunched(LaunchActivatedEventArgs args)
{
_window = new Window();
_window.Activate();
UITestMethodAttribute.DispatcherQueue = _window.DispatcherQueue;
try
{
Environment.ExitCode = await MicrosoftTestingPlatformApplication.RunAsync(Environment.GetCommandLineArgs()[1..]);
}
finally
{
_window.Close();
Exit();
}
}
}[/code]That single
Code:
OnLaunchedstarts, whether launched directly or activated by AUMID:
Code:
[code]sequenceDiagram
participant OS as Windows
participant App as UnitTestApp.OnLaunched
participant MTP as Microsoft.Testing.Platform
participant Tests as MSTest UITestMethod tests
OS->>App: Launch (apphost or AUMID activation)
App->>App: Create and activate the Window
App->>Tests: Publish UITestMethodAttribute.DispatcherQueue
App->>MTP: MicrosoftTestingPlatformApplication.RunAsync
MTP->>Tests: Run tests on the published dispatcher
MTP-->>App: Test-run result
App->>App: Environment.ExitCode = result
App->>OS: Window.Close + Exit[/code]Assigning
Code:
Environment.ExitCodereturns
Code:
voidbuild or CI system.
Code:
MSTest.Sdkregistration, and the packaged-app launcher registration. The
WinUI testing guide
covers the generated helper and hosting model in more depth.
Verify the dispatcher in a UI test
The app and tests now form one host. For example, this async test
checks that
Code:
TestInitializeCode:
TestCleanupthe UI dispatcher:
Code:
[code]using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Controls;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MyWinUiTests;
[TestClass]
public sealed class ViewTests
{
private bool _initializedOnUiThread;
private bool _verifyCleanupOnUiThread;
[TestInitialize]
public async Task InitializeAsync()
{
await Task.Yield();
_initializedOnUiThread =
DispatcherQueue.GetForCurrentThread()?.HasThreadAccess == true;
}
[TestCleanup]
public async Task CleanupAsync()
{
await Task.Yield();
if (_verifyCleanupOnUiThread)
{
Assert.IsTrue(
DispatcherQueue.GetForCurrentThread()?.HasThreadAccess == true);
}
}
[UITestMethod]
public async Task ControlCanBeCreatedAfterAsyncInitialization()
{
_verifyCleanupOnUiThread = true;
await Task.Yield();
var grid = new Grid();
Assert.IsTrue(_initializedOnUiThread);
Assert.IsTrue(grid.DispatcherQueue.HasThreadAccess);
}
}[/code]Code:
[STATestMethod]dispatcher.
Code:
[UITestMethod]including its setup and cleanup.
Apply deployment models to UWP and WinUI 3 apps
For UWP and WinUI 3 apps, apply the delta that matches the deployment model
you chose in “Choose the model for UWP and WinUI 3 apps.”
Code:
MSTest.Sdkthe runner setup shared between both models.
Unpackaged WinUI 3 (default choice)
Add these properties and do not include MSIX manifest or package asset items:
Code:
[code]<PropertyGroup>
<WindowsPackageType>None</WindowsPackageType>
<EnableMsixTooling>false</EnableMsixTooling>
</PropertyGroup>[/code]The resulting apphost is a standard executable. As a result,
Microsoft.Testing.Platform uses its normal launch path, and this route does
not use VSTest’s appx runtime provider.
Packaged full-trust WinUI 3 (when you need identity)
Remove the two unpackaged overrides; do not leave
Code:
<WindowsPackageType>None</WindowsPackageType>template’s normal
Code:
Package.appxmanifesthas MSIX identity, so Windows must register the package layout and activate the
test host by AUMID. It needs a Windows target framework moniker (TFM) at
Code:
10.0.19041.0unsigned build output.
In addition,
Code:
MSTest.Sdkpackaged WinUI projects. Leave
Code:
TESTINGPLATFORM_PACKAGEDAPP_LAUNCHERCode:
autopackaged launch path only when a matching
Code:
AppxManifest.xmlapp. Otherwise it keeps the normal, faster launch path with no controller
restart or deployment-copy overhead. The
WinUI testing guide
covers the
Code:
alwaysCode:
neverValidate on your CI agent first
Confirm Developer Mode (or your sideloading policy) and a clean, passing exit code on your own packaged app and CI agent, as covered in “Packaged activation needs machine setup” above, before wiring this into a required gate.
Run it
Use
Code:
dotnet runapphost, which hosts Microsoft.Testing.Platform inside the process that owns
the window and dispatcher:
Code:
[code]dotnet run[/code]The .NET 10 native MTP runner also supports
Code:
dotnet testdeployment model:
Code:
[code]dotnet test --project .\MyWinUiTests.csproj -c Release -a x64[/code]For an unpackaged app, you can also launch the generated apphost directly.
However, do not use
Code:
dotnet execCode:
dotnet.execan break WinUI resource loading.
A window flashes briefly while the test runs, and the console reports a
summary:
Code:
[code]Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1, Duration: 63ms - MyWinUiTests.dll (net10.0-windows10.0.19041.0)[/code]The packaged development layout can remain registered after the run. Remove
it when needed by using its manifest
Code:
IdentityCode:
[code]Get-AppxPackage -Name '<package identity name>' |
Remove-AppxPackage -PreserveApplicationData[/code]Validate UWP and WinUI 3 apps on the target machine
For a packaged test host, a passing build is only the first step. Therefore,
check your actual CI agent image, not just a developer workstation, for a user
context that can register the package, the required Developer Mode or
sideloading policy, and the frameworks declared by the package. Otherwise, a
workstation that already has the UWP framework packages or Windows App SDK
runtime installed can hide a gap that only appears on a clean build agent.
Framework-dependent WinUI 3 apps need the matching Windows App SDK runtime on
the agent. A self-contained WinUI 3 build can remove that machine need, but test
it using the exact package model that will run in CI.
Start testing UWP and WinUI 3 apps
Use this checklist to start testing UWP and WinUI 3 apps:
- Select Microsoft.Testing.Platform in and useCode:
global.json
4.5.Code:MSTest.Sdk
- For modern UWP, set andCode:
UseUwp. For classic UWP, importCode:PublishAot
into the existing project. For WinUI 3, setCode:MSTest.SdkandCode:UseWinUI
choose packaged or unpackaged deployment.
- Call the generated helperCode:
MicrosoftTestingPlatformApplication.RunAsync
from. Modern UWP restoresCode:OnLaunchedthroughCode:args.Arguments
; WinUI uses processCode:PackagedAppExtensions.GetTestApplicationArguments
arguments. Publish the UI dispatcher where required.
- Run full-trust WinUI with orCode:
dotnet run. Run UWP andCode:dotnet test
AppContainer WinUI through theMSBuild target fromCode:InvokeTestingPlatform
a non-elevated Developer PowerShell.
- For packaged and AppContainer models, confirm Developer Mode (or your
sideloading policy) and run non-elevated on the test agent.
Get the UWP and WinUI testing guide
One test platform for UWP and WinUI 3 apps
Keep
Code:
MSTest.SdkCode:
[UITestMethod]Windows app models. Let the SDK choose direct startup for unpackaged
WinUI or the sidecar controller for package registration, AUMID activation,
and AppContainer isolation.
Three takeaways to carry forward:
- Reuse the same MSTest lifecycle and UI-dispatcher tests across UWP and
WinUI 3.
- Use direct apphost startup for unpackaged WinUI; let MTP register and
AUMID-activate packaged or AppContainer hosts.
- Validate the exact package model, required frameworks, trust level, and
machine policy that your CI agents will run.
For more detail, see the UWP and WinUI testing guide
and the MSTest documentation.
The post UWP and WinUI 3 apps: UI testing with MSTest appeared first on .NET Blog.

