TonyHallett.WPFExtras.TestTools.WPFPilot
1.0.0
dotnet add package TonyHallett.WPFExtras.TestTools.WPFPilot --version 1.0.0
NuGet\Install-Package TonyHallett.WPFExtras.TestTools.WPFPilot -Version 1.0.0
<PackageReference Include="TonyHallett.WPFExtras.TestTools.WPFPilot" Version="1.0.0" />
<PackageVersion Include="TonyHallett.WPFExtras.TestTools.WPFPilot" Version="1.0.0" />
<PackageReference Include="TonyHallett.WPFExtras.TestTools.WPFPilot" />
paket add TonyHallett.WPFExtras.TestTools.WPFPilot --version 1.0.0
#r "nuget: TonyHallett.WPFExtras.TestTools.WPFPilot, 1.0.0"
#:package TonyHallett.WPFExtras.TestTools.WPFPilot@1.0.0
#addin nuget:?package=TonyHallett.WPFExtras.TestTools.WPFPilot&version=1.0.0
#tool nuget:?package=TonyHallett.WPFExtras.TestTools.WPFPilot&version=1.0.0
Helpers for when ui testing with WPFPilot.
This project depends on WPF Pilot, licensed under the WPF Pilot Split License. See the WPF Pilot license for applicable licensing terms.
AppDriver.Launch helpers
AppDriverWindowLauncher launches the exe with --window=FullyQualifiedNameOfWindow.
SlnConventionAppDriverWindowLauncher
public static AppDriver LaunchWindow<TWindow>(Assembly testAssembly)
extends above so the exe path is not necessary.
e.g given testAssembly
"\TonyHallett.WPFExtras\TonyHallett.WPFExtras.UITests\bin\Debug\net10.0-windows\TonyHallett.WPFExtras.UITests.dll"
and TWindow in assembly TonyHallett.WPFExtras.TestHost
will replace the directory, TonyHallett.WPFExtras.UITests, in the path above with TonyHallett.WPFExtras.TestHost and have the exe file name as TonyHallett.WPFExtras.TestHost.exe
For NUnit - TonyHallett.WPFExtras.TestTools.WPFPilot.NUnit this is incorporate into a base test class that launches the TWindow app.
Strongly typed AppDriver.GetElement
Not using the indexer in the GetElement matcher.
public static Element GetElementByName(this AppDriver appDriver, string name, int timeoutMs = 30000)
Not specifying TypeName string in the GetElement matcher.
public static Element GetElementByType<TWpf>(this AppDriver appDriver, int timeoutMs = 30000) where TWpf : Visual public static IReadOnlyList<Element> GetElementsByType<TWpf>(this AppDriver appDriver, int timeoutMs = 30000) where TWpf : Visual
There is also a custom element versions of these.
Custom elements
WPFPilot has the concept of custom elements. These are derived Element whose purpose is to provide helpers for a specific control type.
They are obtained from the AppDriver GetElement<TElement>.
The only downside is that custom elements have to have a specific constructor instantiated by WPFPilot and do not receive the AppDriver for future usage.
As such the strongly typed "Get" AppDriver extension methods
public static TElement GetElementByName<TElement>(this AppDriver appDriver, string name, int timeoutMs = 30000) where TElement : Element
public static TElement GetElementByType<TElement, TWpf>(this AppDriver appDriver, int timeoutMs = 30000)
where TElement : Element
where TWpf : Visual
public static IReadOnlyList<TElement> GetElementsByType<TElement, TWpf>(this AppDriver appDriver, int timeoutMs = 30000)
where TElement : Element
where TWpf : Visual
that will set the AppDriver if the Element derivation is ICustomElementWithAppDriver
public interface ICustomElementWithAppDriver
{
void SetAppDriver(AppDriver appDriver);
}
A base implementation of this interface is provided Note the ICustomElement.
public abstract class CustomElement(Element element) :
Element<CustomElement>(element), ICustomElementWithAppDriver, ICustomElement
{
private AppDriver? appDriver;
public Element Element { get; private set; } = element;
public void SetAppDriver(AppDriver appDriver) => this.appDriver = appDriver;
protected AppDriver AppDriver => appDriver ?? throw new InvalidOperationException("AppDriver has not been set. Ensure that SetAppDriver is called before using this element.");
}
public interface ICustomElement
{
Element Element { get; }
}
There are a couple of marker interfaces that extend ICustomElement ( that are reliant upon the Element property ) with extension methods applied.
ISelectableElement has IsSelected()
IContentControlElement has Content() and StringContent()
these just avoid the magic strings in the indexer.
For containers there is
public interface IHaveSelectableItemsElement<TItemElement> where TItemElement : ISelectableElement
{
IReadOnlyList<TItemElement> GetItemElements();
}
with extension methods to select an ISelectableElement from GetItemElements()
public static IHaveSelectableItemsElement<TItem> SelectItem<TItem>(this IHaveSelectableItemsElement<TItem> containerElement, int index)
where TItem : ISelectableElement
public static IHaveSelectableItemsElement<TItem> SelectLast<TItem>(this IHaveSelectableItemsElement<TItem> containerElement)
where TItem : ISelectableElement
public static IHaveSelectableItemsElement<TItem> SelectFirst<TItem>(this IHaveSelectableItemsElement<TItem> containerElement)
where TItem : ISelectableElement
implementations of IHaveSelectableItemsElement / ISelectableElement are
ListBoxElement and ListBoxItemElement
DataGridElement and DataGridRowElement
ListBoxElement and DataGridElement also implement IHaveSelectedItemsElement nad received via extension method
TSelectedItem[] GetSelectedItems<TSelectedItem>() - this uses reflection on "SelectedItems" property of type IEnumerable.
ListBoxItemElement is a IContentControlElement whereas DataGridRowElement has the generic Item method.
public class DataGridRowElement(Element element) : CustomElement(element), ISelectableElement
{
public TItem Item<TItem>() => Invoke<DataGridRow, TItem>(dgr => (TItem)dgr.Item);
}
WPFPilot serialization applies
Invoke issues an InvokeCommand on the target element and is one of the most flexible and useful methods in WPF Pilot. Invoke expects a LambdaExpression, which will typically look like a plain old function call, as seen in the example. There are many flavors of Invoke depending on if we care about the result, if we need to specify a custom element, or if we need to call an async method. The LambdaExpression is serialized into a JSON representation and sent over the NamedPipe. The app then compiles the LambdaExpression and invokes it on the element. The result is then serialized and returned over the NamedPipe back to the client. Most standard classes will be serializable by Newtonsoft.Json, but not all are, in which case null is returned.
Additional AppDriver extensions
There are a couple of utility methods
public static Element GetElementByNameAndClick(this AppDriver appDriver, string name, int timeoutMs = 30000)
public static string? GetTextByName(this AppDriver appDriver, string name, int timeoutMs = 30000)
=> appDriver.GetElementByName(name, timeoutMs)["Text"];
public static void AssertTextByName(
this AppDriver appDriver,
string name,
string expectedText,
int findTimeoutMs = 30000,
int assertTimeoutMs = 10000)
Element InvokeDataContext extensions
This makes the element.Invoke code succinct
Instead of
element.Invoke<MyControl,string>(c => ((MyDataContext)c.DataContext).MyString);
Use
element.InvokeDataContext<MyDataContext,string>(dc => dc.MyString);
When not returning a value
element.InvokeDataContext<MyDataContext>(dc => dc.DoSomething());
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net10.0-windows7.0 is compatible. |
-
net10.0-windows7.0
- TonyHallett.WPFExtras.TestTools (>= 1.0.0)
- WpfPilot (>= 3.2.1)
NuGet packages (1)
Showing the top 1 NuGet packages that depend on TonyHallett.WPFExtras.TestTools.WPFPilot:
| Package | Downloads |
|---|---|
|
TonyHallett.WPFExtras.TestTools.WPFPilot.NUnit
Base test classes for WPFPilot testing in NUnit |
GitHub repositories
This package is not used by any popular GitHub repositories.
| Version | Downloads | Last Updated |
|---|---|---|
| 1.0.0 | 108 | 9/7/2026 |