Next gen automation testing for WPF apps.
PM > Install-Package WpfPilotWPF Pilot is a NuGet Package for writing WPF automation tests. It works out of box with no special setup or configuration. WPF Pilot launches your app and injects a DLL to set up a communication channel between your test suite and app. It is built around the same technology as Snoop and does not use the UIA in any way. In addition to automating WPF apps, WPF Pilot supports recording videos and taking screenshots of the app under test.
WPF Pilot's core is oriented around the Visual Tree and works incredibly well with Snoop, which can be used to inspect the Visual Tree and find elements of interest to write tests around. Snoop is not affiliated with WPF Pilot, but is an amazing application you should consider supporting.
WPF Pilot works with any test suite: NUnit, MSTest, XUnit, etc.
Guiding principles of WPF Pilot include:
- Just works. No special configuration or setup should be necessary. Install the package and get to hacking.
- Minimal yet expressive API. Everything you need and nothing more.
WPF Pilot works on Windows and requires .NET Framework 4.5.2+, .NET Core 3.1+, or .NET 5+.
There are only 4 core classes to learn, with a small handful of methods on each class. WPF Pilot strives to keep the API surface minimal, and encourages you to write your own helper methods for your specific domain. For brevity, not every method or class is documented below.
See the docs for detailed usage, tutorials, and comprehensive info.
AppDriver
The AppDriver is responsible for launching or attaching to an app and finding elements to interact with.
AppDriver.LaunchorAppDriver.AttachToAppDriver.GetElement(s)AppDriver.ScreenshotAppDriver.RecordAppDriver.Keyboard(property)
Element
An Element represents a WPF UIElement in the app. It can be interacted with in all the expected ways.
Element.Click,Element.DoubleClick, …Element.FocusElement.SelectElement.Check/Element.UncheckElement.Expand/Element.CollapseElement.TypeElement.Screenshot
[…]
Most Element methods are syntactic sugar around 3 core methods,
Element.InvokeElement.RaiseEventElement.SetProperty
Which allow arbitrary code execution on the underlying element.
Primitive
Each property on an Element returns a Primitive. A Primitive represents any object, but is typically a string, number, DateTime, or the like. A Primitive comes with many support methods to work as expected. A primitive can be cast to its underlying type using To<T>, but this is generally unnecessary.
var fullHealth = healthBarElement["Width"] / healthBarElement["FullWidth"];
Assert.AreEqual(0.8, fullHealth);
// Hide the health bar.
healthBarElement["Visibility"] = Visibility.Hidden.ToPrimitive();
// Check player name.
Assert.True(playerHUD["PlayerName"].StartsWith("Erik"));Keyboard
Keyboard allows direct input to the app using a virtual keyboard.
Keyboard.PressKeyboard.Type
See this github action for an example of how to run your UI tests as part of a CI/CD pipeline. Feel free to copy and modify it for your project.
- Low code recorder tool. Generate tests automatically by clicking through the app.
- Streamlined CEF and WebView2 support.
- Support for Linux and Mac.
- Low level input mocking.
- Minor API improvements.
using WpfPilot;
using NUnit.Framework;
[TestFixture]
public class UserProfileTests
{
[Test, Retry(1)]
public void TestNewUserWorkflow()
{
using var appDriver = AppDriver.Launch("../bin/Debug/MyCoolApp.exe");
// Start a screen recording if this is a retry run.
using var recording = TestContext.CurrentContext.CurrentRepeatCount > 0 ?
AppDriver.Record($"./TestRecordings/{nameof(TestNewUserWorkflow)}.mp4") : null;
// Create a user.
appDriver.GetElement(x => x["Name"] == "UsernameInput")
.Type("CoolUser123")
.Assert(x => x["Border"] == "Green");
appDriver.GetElement(x => x["Name"] == "PasswordInput")
.Type("myp@ssword1")
.Assert(x => x["Border"] == "Green")
appDriver.GetElement(x => x["Name"] == "SubmitForm").Click();
// Accept cookies.
var cookieBanner = appDriver.GetElement(x => x["Name"] == "CookieBanner");
appDriver.GetElement(x => x["Name"] == "AcceptCookiesButton")
.Click()
.Assert(_ => cookieBanner["Visibility"].To<Visibility>() == Visibility.Hidden);
// Check some interests.
appDriver.GetElements(x => x["Name"].StartsWith("InterestOption"))
.ForEach(x => x.Check());
appDriver.GetElement(x => x["Text"] == "Finished").Click();
// Verify we're on the news feed.
appDriver.GetElement(x => x["Name"] == "InterestSection")
.Assert(x => x.Child.Count != 0);
}
}The WPF Pilot split license is viewable here. For most consumers this is the Apache License, Version 2.0. For larger corporations, an enterprise license is required. This can be obtained by donating $100 to a charity of your choice, once per year. Full details here.
- Debug logs are stored at
%TEMP%/WpfPilot - If you cloned the project, and built in debug locally, and
AppDriverPayloadis injected into an exe, you can attach Visual Studio to the process and walk through theAppDriverPayloadcode like usual. WpfPilotuses DLL injection to setup a communication bridge. If you have an aggressive anti-virus, you may need to disable it. I have never personally encountered this, but it is worth mentioning.- If the app is launched in Administrator mode, and the test suite is not, it may not have permissions to inject.
- The test suite dotnet version or framework must match the application dotnet version or framework.
