Desktop automation quality is capped by how the application was built — not by your test framework.
Web automation tools assume a DOM: a structured, inspectable tree of elements with IDs
and classes you can reliably select. A mature WPF application doesn't give you that for
free. Instead, you're working with the Windows UI Automation framework, and whether an
element is reliably identifiable depends entirely on whether the original developers
added proper AutomationId and Name properties — which, on an
app built over years before automation was a priority, they often didn't.
A framework can't invent a stable selector for a button that has no name and no automation ID. A meaningful chunk of "automation work" on a legacy desktop app is actually application work.
Choosing a framework: why FlaUI
For a WPF app on Windows, the realistic options are Microsoft's own UI Automation libraries directly, or a wrapper library that makes them less painful to work with. We chose FlaUI (specifically FlaUI.Core + FlaUI.UIA3) over writing directly against Microsoft UI Automation or using an older tool like Coded UI, which Microsoft has deprecated. FlaUI gives a much more usable C# API on top of UIA3, active maintenance, and it plays well with NUnit.
The resulting stack: C#, Visual Studio, NUnit as the test runner, FlaUI.Core and FlaUI.UIA3 for driving the app, and Microsoft UI Automation underneath all of it.
The three rules that actually matter for maintainability
Before writing a single test, we set three hard rules, because it's easy to end up with an automation suite that's more fragile than doing the testing manually.
- No coordinate-based or image-matching automation, ever, unless there's genuinely no other option. Clicking at a fixed pixel or matching a screenshot works until a window resizes, a theme changes, or the app runs on a different monitor DPI — then it silently breaks.
-
No
Thread.Sleep. A legacy desktop app's timing is unpredictable: database queries, UI grid rendering, and the underlying environment (a Citrix or VM-hosted deployment, for example) all introduce variable delays. Condition-based waits — "wait until this element exists and is enabled," polled with a timeout — are the only approach that holds up. -
Page Object / Screen Object structure from day one. Tests should read
like a business flow ("open the order screen, enter a customer, submit"), not a
script full of raw selectors. Selectors and waits live in a
Pageslayer; the test files stay readable by anyone on the QA team.
The unglamorous part: fixing the app's UI, not just the tests
The single biggest realisation was that automation architecture includes going into the
app's XAML and adding proper AutomationProperties.AutomationId and
AutomationProperties.Name to controls that don't have them. Third-party UI
component libraries are a common offender — many of their interactive controls render
with an empty Name and AutomationId out of the box, which
makes them technically clickable but not reliably identifiable once there's more than
one on screen.
Where UI automation should hand off to database checks
Not everything should be verified through the UI, even in a UI automation suite. If a screen shows "Saved successfully," that confirms the UI displayed a success message — it doesn't confirm the record actually landed correctly in the database, with the right values, in the right related tables. For a desktop app backed by SQL Server, pairing UI actions with direct database assertions catches a category of bug that UI-only checks miss entirely, and it's often faster than adding more UI navigation just to verify a value on screen.
Where this leaves the project
Progress here has been deliberately incremental: confirm the app launches and the framework can find the main window, build a small utility to recursively dump every element's name, automation ID, control type and class, and build outward from there — one screen, one Page Object, one real business flow at a time. Desktop automation on a legacy app is a slower start than web automation with a clean modern frontend, but the payoff — a suite that catches real regressions on a business-critical app that almost nobody else knows how to automate — is worth the setup cost.