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, asynctest code, and cleanup all run with UI-thread access. A WinUI 3 test stilllooks this simple:[UITestMethod]public async Task GridCanBeCreatedOnTheUIThread(){ await Task.Yield(); var grid = new Grid(); Assert.IsTrue(grid.DispatcherQueue.HasThreadAccess);}With MSTest4.5 and Microsoft.Testing.Platform (MTP) 2.5,you can use the same UI-thread testing pattern for UWP and WinUI 3 apps. Thesupported models are classic and modern UWP, packaged or unpackaged WinUI 3,and WinUI hosts that use AppContainer.MSTest.Sdk picks the launch path for each model. It starts unpackaged appsdirectly and usesMicrosoft.Testing.Extensions.PackagedAppto register and activate packaged apps by AUMID. For AppContainer hosts, italso grants the exact package SID access to the controller and report pipes.Packaged and sandboxed apps do not start as the initial test tool.MSTest.Sdk launches a normal full-trust sidecar controller that ownstest arguments, cancel requests, reports, retries, and the final exit code. Itthen starts the app that hosts the tests. No Microsoft.NET.Test.Sdk,vstest.console, UwpTestHostRuntimeProvider, or Visual Studio deploymentruntime is used.For more background, read theintroduction to MSTest.Sdkand the overview ofMicrosoft.Testing.Platform support across .NET test frameworks.Packaged activation needs machine setupTo 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 appsPackaging and sandboxing are separate choices. Packaging adds MSIXidentity and AUMID activation; the trust level decides whether the process isfull trust or runs in AppContainer.Application modelIdentity and trustMTP test-host pathClassic UWP (uap10.0)MSIX, AppContainerSidecar controller, UAP adapter/bootstrap assets, AUMID activationModern UWP (UseUwp)MSIX, AppContainerSidecar controller, Native AOT host, AUMID activationUnpackaged WinUI 3No package identity, full trustDirect apphost launchPackaged WinUI 3MSIX, full trustSidecar controller, package registration, AUMID activationWinUI 3 packagedClassicApp with AppContainer trustMSIX, AppContainerSidecar controller and exact package-SID pipe authorizationFor WinUI, start unpackaged unless the behavior under test needs packageidentity, packaged activation contracts, or an exact match with installed appbehavior. In contrast, UWP is inherently packaged and sandboxed.Select MSTest.Sdk and MTP for UWP and WinUI 3 appsAt the solution or repo root, use global.json to pin MSTest.Sdk 4.5and select Microsoft.Testing.Platform for the native .NET 10dotnet test experience:{ "test": { "runner": "Microsoft.Testing.Platform" }, "msbuild-sdks": { "MSTest.Sdk": "4.5.0" }}Otherwise, .NET 10 uses VSTest for dotnet test.MSTest.Sdk 4.5 includes MTP 2.5, the app-model sidecar controller, UWPadapter/bootstrap assets, and the packaged launcher.Configure UWP and WinUI 3 appsModern UWPFor a modern UWP project, the test setup is small: net10.0-windows10.0.26100.0 true true Keep the app’s XAML, manifest, architecture, and Native AOT settings. Then,from OnLaunched, pass the activation string to the generated MTP helper: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();}Classic UWPClassic uap10.0 projects keep their existing UWP project structure andimport MSTest.Sdk alongside MSBuild.Sdk.Extras. MSTest 4.5 includes theUAP-compatible adapter, generated bootstrap, packaged-app launcher, and TRXclient assets. The sidecar builds the .build.appxrecipe layout,installs its declared frameworks, and starts the app by AUMID.Classic and modern UWP builds still need the Visual Studio MSBuild/UWPtoolchain. However, they no longer need its VSTest runtime or deploymentprovider.See the UWP and WinUI testing guidefor the complete classic project imports and package layout.Therefore, run UWP tests from a Developer PowerShell for Visual Studio. Buildwith the desktop MSBuild toolchain, then invoke the MTP target:msbuild .\MyUwpTests.sln /restore /p:Configuration=Release /p:Platform=x64msbuild .\MyUwpTests.csproj /t:InvokeTestingPlatform /p:Configuration=Release /p:Platform=x64AppContainer-configured WinUI 3A packaged WinUI 3 app is full trust by default. For example, to test asupported packagedClassicApp AppContainer setup, keep the packaged WinUI hostshown below and set uap10:TrustLevel="appContainer" in the manifest. Thehost still receives normal process arguments, while MTP grants only thatpackage SID access to its controller, cancellation, TRX, HangDump, and Retrypipes. Do not grant ALL APPLICATION PACKAGES.Then, run this AppContainer shape from a non-elevated Developer PowerShellthrough the MTP MSBuild target:dotnet build .\MyAppContainerTests.csproj -c Release -p:Platform=x64dotnet msbuild .\MyAppContainerTests.csproj /t:InvokeTestingPlatform /p:Configuration=Release /p:Platform=x64Build the self-hosted WinUI 3 test appThe following complete WinUI 3 test app supplies that dispatcher and runsMSTest inside the app itself. Build this shared host once; the packaged andunpackaged deltas come afterward. Three pieces make up that shared project:Configure the shared projectYou need Windows, the .NET 10 SDK, and the Windows App SDK tooling (installedby Visual Studio’s “Windows application development” workload, or restoredfrom NuGet if you build from the CLI). Then, start from Visual Studio’s“Blank App, Packaged (WinUI 3 in Desktop)” template, retain itsPackage.appxmanifest and package assets for the packaged form, and applythis shared project setup. Exe net10.0-windows10.0.19041.0 10.0.17763.0 true enable enable Define the application entry pointThe ApplicationDefinition points to this minimal UnitTestApp.xaml: Host the test run from OnLaunchedThe WinUI app owns its entry point through this ApplicationDefinition.MSTest.Sdk detects that entry point, suppresses its own competing Main,and generates a reusable MicrosoftTestingPlatformApplication.RunAsynchelper. Finally, the app’s code-behind creates the window, publishes itsdispatcher, and calls that helper: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(); } }}That single OnLaunched override plays out in this order every time the appstarts, whether launched directly or activated by AUMID: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 + ExitAssigning Environment.ExitCode is important: a WinUI-generated entry pointreturns void, so without it a failing test run can look successful to abuild or CI system. MSTest.Sdk owns MSTest references, extensionregistration, and the packaged-app launcher registration. TheWinUI testing guidecovers the generated helper and hosting model in more depth.Verify the dispatcher in a UI testThe app and tests now form one host. For example, this async testchecks that TestInitialize, the test body, and TestCleanup keep access tothe UI dispatcher: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); }}[STATestMethod] can provide an STA thread, but it does not create a WinUIdispatcher. [UITestMethod] dispatches the full MSTest call for each test,including its setup and cleanup.Apply deployment models to UWP and WinUI 3 appsFor UWP and WinUI 3 apps, apply the delta that matches the deployment modelyou chose in “Choose the model for UWP and WinUI 3 apps.” MSTest.Sdk keepsthe runner setup shared between both models.Unpackaged WinUI 3 (default choice)Add these properties and do not include MSIX manifest or package asset items: None falseThe resulting apphost is a standard executable. As a result,Microsoft.Testing.Platform uses its normal launch path, and this route doesnot use VSTest’s appx runtime provider.Packaged full-trust WinUI 3 (when you need identity)Remove the two unpackaged overrides; do not leaveNone in the project. Retain thetemplate’s normal Package.appxmanifest and package assets. The output thenhas MSIX identity, so Windows must register the package layout and activate thetest host by AUMID. It needs a Windows target framework moniker (TFM) at10.0.19041.0 or later and Developer Mode or a similar sideloading policy forunsigned build output.In addition, MSTest.Sdk adds and registers the packaged-app launcher forpackaged WinUI projects. LeaveTESTINGPLATFORM_PACKAGEDAPP_LAUNCHER unset. Its default, auto, enables thepackaged launch path only when a matching AppxManifest.xml describes theapp. Otherwise it keeps the normal, faster launch path with no controllerrestart or deployment-copy overhead. TheWinUI testing guidecovers the always and never overrides for less common scenarios.Validate on your CI agent firstConfirm 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 itUse dotnet run for either deployment model. It launches the generatedapphost, which hosts Microsoft.Testing.Platform inside the process that ownsthe window and dispatcher:dotnet runThe .NET 10 native MTP runner also supports dotnet test for eitherdeployment model:dotnet test --project .\MyWinUiTests.csproj -c Release -a x64For an unpackaged app, you can also launch the generated apphost directly.However, do not use dotnet exec, which puts dotnet.exe in the middle andcan break WinUI resource loading.A window flashes briefly while the test runs, and the console reports asummary:Passed! - Failed: 0, Passed: 1, Skipped: 0, Total: 1, Duration: 63ms - MyWinUiTests.dll (net10.0-windows10.0.19041.0)The packaged development layout can remain registered after the run. Removeit when needed by using its manifest Identity name:Get-AppxPackage -Name '' | Remove-AppxPackage -PreserveApplicationDataValidate UWP and WinUI 3 apps on the target machineFor 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 usercontext that can register the package, the required Developer Mode orsideloading policy, and the frameworks declared by the package. Otherwise, aworkstation that already has the UWP framework packages or Windows App SDKruntime 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 onthe agent. A self-contained WinUI 3 build can remove that machine need, but testit using the exact package model that will run in CI.Start testing UWP and WinUI 3 appsUse this checklist to start testing UWP and WinUI 3 apps:Select Microsoft.Testing.Platform in global.json and useMSTest.Sdk 4.5.For modern UWP, set UseUwp and PublishAot. For classic UWP, importMSTest.Sdk into the existing project. For WinUI 3, set UseWinUI andchoose packaged or unpackaged deployment.Call the generated MicrosoftTestingPlatformApplication.RunAsync helperfrom OnLaunched. Modern UWP restores args.Arguments throughPackagedAppExtensions.GetTestApplicationArguments; WinUI uses processarguments. Publish the UI dispatcher where required.Run full-trust WinUI with dotnet run or dotnet test. Run UWP andAppContainer WinUI through the InvokeTestingPlatform MSBuild target froma non-elevated Developer PowerShell.For packaged and AppContainer models, confirm Developer Mode (or yoursideloading policy) and run non-elevated on the test agent.Get the UWP and WinUI testing guideOne test platform for UWP and WinUI 3 appsKeep MSTest.Sdk, the generated MTP helper, and [UITestMethod] across theWindows app models. Let the SDK choose direct startup for unpackagedWinUI 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 andWinUI 3.Use direct apphost startup for unpackaged WinUI; let MTP register andAUMID-activate packaged or AppContainer hosts.Validate the exact package model, required frameworks, trust level, andmachine policy that your CI agents will run.For more detail, see the UWP and WinUI testing guideand the MSTest documentation.The post UWP and WinUI 3 apps: UI testing with MSTest appeared first on .NET Blog.