After a WPF-to-Uno Platform migration, your test strategy splits into three tiers: pure logic tests you keep almost unchanged (xUnit, NUnit, MSTest), platform-runtime tests that validate real Uno.UI controls on every target using Uno.UI.RuntimeTests, and cross-platform UI automation tests written with Uno.UITest. Your existing WPF-only automation (FlaUI, WinAppDriver, UI Automation) does not port directly. The fastest path forward is to scaffold fresh test heads with dotnet new unoapp -tests ui -tests unit and rebuild coverage from the three tiers up.
Who this is for: WPF developers and QA leads with an existing FlaUI, WinAppDriver, or UI Automation test suite and a freshly migrated Uno Platform solution targeting Windows, iOS, Android, and WebAssembly.
Why Your Old WPF Test Suite Shrinks After Migration
The uncomfortable truth is that a meaningful fraction of your existing test suite is WPF-coupled in ways that will not survive the port. FlaUI, for example, works by wrapping Microsoft's UIA2 and UIA3 automation stacks. Those stacks are Windows-host technologies. The moment your migrated app runs in a browser through WebAssembly, on iOS, or on Android, FlaUI and WinAppDriver have no surface to drive.
Meanwhile, your pure logic tests (calculators, validators, domain services, repositories that do not touch System.Windows.* types) port with zero changes. If your WPF codebase was disciplined about keeping logic out of the code-behind, most of your tests fall into this bucket and migrate untouched.
The question is not "how do I port my WPF tests." It is "which tier does each existing test belong to, and what do I rebuild from scratch at the UI layer."
The Three Test Tiers for a Migrated Uno Platform App
| Tier | WPF Equivalent | Replacement | Platforms Covered |
|---|---|---|---|
| 1. Pure logic | xUnit / NUnit / MSTest | Unchanged | All .NET targets |
| 2. Platform-runtime | (no equivalent) | Uno.UI.RuntimeTests | Windows, iOS, Android, WASM, Skia |
| 3. Cross-platform UI | FlaUI, WinAppDriver | Uno.UITest | WASM, Android, iOS |
Tier 1: Pure Logic (xUnit / NUnit / MSTest)
This tier is unchanged by migration. Keep your existing xUnit or NUnit projects, retarget them to the same .NET version your migrated Uno Platform app uses, and add a project reference to the shared head. Run with dotnet test.
If your ViewModels were WPF-free (no System.Windows.Input.ICommand from WPF, no Dispatcher references), they belong in this tier too. If they were not, you will have to decouple the WPF types before the tests port cleanly; that is migration work, not test-strategy work.
Tier 2: Platform-Runtime (Uno.UI.RuntimeTests)
This is the tier that did not exist in your WPF world. These are in-process unit tests run on the actual target platform, using the real Uno.UI assemblies. They live inside the SamplesApp and are executed by the Unit Tests Runner on each head.
This is where you verify things that are cheap to set up programmatically but still need real rendering: a binding resolves, a control measures to the expected size, a ListView virtualizes a certain number of items, a Style applies.
[TestClass]and[TestMethod]attributes (MSTest-style)[RunsOnUIThread]on a method or class to marshal execution to the UI threadWindowHelper.WindowContentto inject aFrameworkElementinto the live visual treeWindowHelper.WaitForLoaded,WaitForIdle, andWaitFor(Func<bool>)to synchronize with the async UI pipelineFindFirstChild<T>andFindFirstParent<T>extension methods for visual tree traversal
using Private.Infrastructure.TestServices;
[TestClass]
public class Given_CustomerCard
{
[TestMethod]
[RunsOnUIThread]
public async Task When_Bound_To_Customer_Then_Name_Rendered()
{
var vm = new CustomerViewModel { Name = "Ada Lovelace" };
var sut = new CustomerCard { DataContext = vm };
WindowHelper.WindowContent = sut;
await WindowHelper.WaitForLoaded(sut);
var nameText = sut.FindFirstChild<TextBlock>(
tb => tb.Name == "NameText");
Assert.IsNotNull(nameText);
Assert.AreEqual("Ada Lovelace", nameText.Text);
}
}This test will execute on every head the SamplesApp is built for, which is precisely what you could not get from a WPF-only FlaUI test.
Tier 3: Cross-Platform UI (Uno.UITest)
This is the replacement for your FlaUI, WinAppDriver, or UI Automation suite, but only for the platforms it supports. Uno.UITest is a framework for unified UI testing of Uno Platform apps, using NUnit 3.x. The API is modeled on Xamarin.UITest so that teams porting from mobile test suites can reuse patterns.
| Platform | Supported | Via |
|---|---|---|
| WebAssembly | Yes | Selenium driving Chrome |
| Android | Yes | Xamarin.UITest + emulator |
| iOS | Yes | Xamarin.UITest + simulator |
| Windows | Not yet | |
| Skia (GTK, WPF-head) | Not yet |
Porting a WPF FlaUI Test to Uno.UITest
The honest answer: the intent carries, the code does not.
var app = FlaUI.Core.Application.Launch("MyWpfApp.exe");
using var automation = new UIA3Automation();
var window = app.GetMainWindow(automation);
var button = window.FindFirstDescendant(
cf => cf.ByAutomationId("submitButton")).AsButton();
button.Invoke();[Test]
[AutoRetry]
[ActivePlatforms(Platform.Browser, Platform.Android, Platform.iOS)]
public void When_User_Taps_Submit_Then_Confirmation_Shown()
{
Run("SubmitFormSample");
var submit = _app.Marked("submitButton");
_app.WaitForElement(submit);
_app.FastTap(submit);
_app.WaitForText("confirmationLabel", "Thanks");
TakeScreenshot("after-submit");
}Three things to flag: You cannot drive a WPF-targeted window from Uno.UITest because Windows is not in its supported target list. Your x:Name values need to be stable and meaningful for Marked(...); if the WPF original leaned on AutomationId set via styles, port those into x:Name. Screenshot diffing across Chrome on Linux, an Android emulator, and an iOS simulator will differ at the pixel level; threshold-based diffing is the norm.
Testing MVUX Feeds Directly
If you are using MVUX and want to test a reactive feed directly (not the rendered control), there is a separate package: Uno.Extensions.Reactive.Testing. It provides a FeedTests base class and a .Record() extension for asserting on message sequences.
[TestClass]
public class When_TestingAsyncFeed : FeedTests
{
[TestMethod]
public async Task When_ProviderReturnsValue_Then_GetSome()
{
var sut = Feed.Async(async ct =>
{
await Task.Delay(500, ct);
return 42;
});
using var result = await sut.Record();
result.Should().Be(r => r
.Message(Changed.Progress, Data.Undefined,
Error.No, Progress.Transient)
.Message(Changed.Data, 42,
Error.No, Progress.Final)
);
}
}Each Message(...) asserts one axis of the feed contract (Data, Error, Progress) plus which axes changed. When building a new feed, validate all three axes so regressions in progress or error propagation do not slip through.
CI Setup for Runtime and UI Tests
A defensible CI shape for a migrated app:
- PR-time: all Tier 1 logic tests on
ubuntu-latestviadotnet test. Fast. - PR-time:
Uno.UI.RuntimeTestson the platforms you can afford in PR (typically WebAssembly and one of Android or Windows), triggered through the Unit Tests Runner in the SamplesApp. - Nightly or pre-release: the full
Uno.UITestmatrix across WebAssembly, Android, and iOS.
The Uno Platform docs point at sample CI scripts for Azure DevOps hosted agents covering Android (Linux), WebAssembly (Linux), and iOS (macOS). That is the authoritative starting point; treat anything beyond those script samples as adaptation work for your pipeline of choice.
What You Give Up, What You Gain
Give Up
- Single-host Windows automation. FlaUI, WinAppDriver, and WPF UI Automation tests do not run against WebAssembly, iOS, or Android.
- Pixel-exact screenshot comparisons across platforms. The headless Chrome render, the Android emulator render, and the iOS simulator render of the same page will differ.
Gain
- Tier 2 coverage (
Uno.UI.RuntimeTests) that runs the real Uno.UI binaries on every platform, catching regressions a WPF suite never could. - Tier 3 coverage (
Uno.UITest) that validates real user flows on mobile and WebAssembly in CI. - Standard .NET test tooling. NUnit-based authoring (Uno.UITest) and MSTest-style attributes (Uno.UI.RuntimeTests) fit inside existing CI pipelines.
The net effect: your test suite gets smaller at the surface level but broader in the platforms it covers. That is the migration trade.
FAQ
Can I keep my existing xUnit tests as-is after migration?
If those tests reference only .NET types and your domain code, yes. If any test references WPF-specific types (System.Windows.*), you will need to decouple first.
Does Uno.UITest run on WebAssembly?
Yes. WebAssembly support works via Selenium driving Chrome, with a Constants.CurrentPlatform = Platform.Browser switch and a Constants.WebAssemblyDefaultUri pointing at the deployed app.
Can I test MVUX feeds and states directly?
Yes. The Uno.Extensions.Reactive.Testing package provides a FeedTests base class and .Record() extension method for asserting on feed message sequences.
Does Uno.UITest support the Windows head?
Not as of the documentation reviewed. Windows, Skia backends (GTK, WPF-head, Tizen), and Xamarin.macOS are listed as "not yet supported." For those heads, rely on Tier 2 (Uno.UI.RuntimeTests) for CI coverage.
How does screenshot diffing work cross-platform?
TakeScreenshot() attaches captures as CI artifacts. Cross-platform pixel-exact diffing is not a supported default; treat screenshots as evidence for human review or as inputs to a threshold-based diff tool you configure yourself.
Scaffold a UI test head into your Uno Platform solution with dotnet new unoapp -tests ui -tests unit and wire it to the SamplesApp or your running WASM build. The Uno Platform getting-started pages document the wizard-driven setup that produces an NUnit project pre-configured for UI testing.
Subscribe to Our Blog
Subscribe via RSS
Back to Top