AppDriver
The AppDriver
is responsible for:
- Launching the application being tested.
- Finding
Elements
in the visual tree. - Providing
Keyboard
access. - Taking pictures of the application.
- Recording screencaps.
- Executing code within the running app.
Methods
void Dispose()
Closes the application under test. Dispose
is typically not called directly.
Usage
// Calls Dispose implicitly.
using var appDriver = AppDriver.Launch("./MyApp.exe");
// Calls Dispose directly.
appDriver.Dispose();
static AppDriver Launch(string exePath, string? args = null)
Launches the application using the given exePath
. Passes the args
through to the application as standard command-line args. Enviroment variables such as %temp%
are properly expanded.
Usage
using var appDriver = AppDriver.Launch(@"..\..\bin\Debug\MyApp.exe", @"--debug --verbose");
using var appDriver = AppDriver.Launch(@"%ProgramFiles%\CoolApp\ACoolApp.exe");
static AppDriver Launch(ProcessStartInfo processStartInfo)
Launches the application using the given processStartInfo
. This is useful for more complicated scenarios that require setting environment variables and the like.
Usage
var p = new ProcessStartInfo(@"C:\Path\To\MyApp.exe");
p.Environment.Add("TEST_MODE", "true");
p.WorkingDirectory = @"C:\TestEnvironment";
using var appDriver = AppDriver.Launch(p);
static AppDriver AttachTo(int processId)
Attaches the AppDriver
to an existing process using the given processId
. This is useful for scenarios where you want to handle the application launch process yourself.
Usage
var p = Process.Start(new ProcessStartInfo(@"C:\Path\To\MyApp.exe"));
using var appDriver = AppDriver.AttachTo(p.Id);
static AppDriver AttachTo(string processName)
Attaches the AppDriver
to an existing process using the given processName
. This is useful for scenarios where you want to handle the application launch process yourself.
Usage
using var appDriver = AppDriver.AttachTo("MyCoolAppName");
Element GetElement(Func<Element, bool?> matcher, int timeoutMs = 30_000)
Returns the first element using the given matcher
function with a time limit of timeoutMs
. The default timeout is 30 seconds. The application is queried for its visual tree and each element is checked against the given matcher
function. If none of the elements meet the criteria, the application is given time to update before being queried again.
A TimeoutException
is thrown if no element matching the criteria is found within the given time limit.
Usage
// Find the root element.
var root = appDriver.GetElement(x => x.Parent is null);
// Find an input box by text.
var input = appDriver.GetElement(x => x["Text"] == "Enter an email.");
// Find a button by width and height.
var button = appDriver.GetElement(x => x["Width"] == 40 && x["Height"] == 40);
// Find a text box by name.
var textBox = appDriver.GetElement(x => x["Name"] == "AccountStatus");
T GetElement<T>(Func<Element, bool?> matcher, int timeoutMs = 30_000)
Returns the first element using the given matcher
function with a time limit of timeoutMs
. The default timeout is 30 seconds. The application is queried for its visual tree and each element is checked against the given matcher
function. If none of the elements meet the criteria, the application is given time to update before being queried again.
T
allows plugging in a custom Element
class, eg GetElement<MyCustomElement>
.
A TimeoutException
is thrown if no element matching the criteria is found within the given time limit.
Usage
// Find the root element.
var root = appDriver.GetElement<MyApp>(x => x.Parent is null);
// Find an input box by text.
var input = appDriver.GetElement<CustomInput>(x => x["Text"] == "Enter an email.");
IReadOnlyList<Element> GetElements(Func<Element, bool?> matcher, int timeoutMs = 30_000)
Returns all elements using the given matcher
function with a time limit of timeoutMs
. The default timeout is 30 seconds. The application is queried for its visual tree and each element is checked against the given matcher
function. If none of the elements meet the criteria, the application is given time to update before being queried again. If even a single element matches the given criteria, the result is returned.
An empty list is returned if no elements matching the criteria are found within the given time limit.
Usage
var elements = appDriver.GetElements(x => x["Name"].StartsWith("ListItem"));
IReadOnlyList<T> GetElements<T>(Func<Element, bool?> matcher, int timeoutMs = 30_000)
Returns all elements using the given matcher
function with a time limit of timeoutMs
. The default timeout is 30 seconds. The application is queried for its visual tree and each element is checked against the given matcher
function. If none of the elements meet the criteria, the application is given time to update before being queried again. If even a single element matches the given criteria, the result is returned.
T
allows plugging in a custom Element
class, eg GetElements<MyCustomElement>
.
An empty list is returned if no elements matching the criteria are found within the given time limit.
Usage
var elements = appDriver.GetElements<ListItem>(x => x["Name"].StartsWith("ListItem"));
void Screenshot(string fileOutputPath)
Takes a screenshot of the app and saves it to the given path. The AppDriver
will briefly attempt to wait for the app to be idle before taking the screenshot.
Usage
appDriver.Screenshot(@"C:\pics\app-snap.png");
appDriver.Screenshot(@"%TEMP%\app-snap.png");
byte[] Screenshot(ImageFormat format = ImageFormat.Jpeg)
Takes a screenshot of the app. Returns the bytes of the image. The AppDriver
will briefly attempt to wait for the app to be idle before taking the screenshot.
Usage
var bytes = appDriver.Screenshot();
File.WriteAllBytes(@"C:\test-pic.jpg", bytes);
static IDisposable Record(string fileOutputPath, string? windowTitle = null)
Records the window with the given title, or the fullscreen if null
is passed. Returns an IDisposable that stops recording when disposed.
The recording must be stopped (disposed), or the recording will be corrupt. Only one recording at a time is allowed; multiple calls will end the original recording.
Usage
using var recordWindow = AppDriver.Record(@"C:\videos\test-vid.mp4", appDriver.Process.MainWindowTitle);
using var recordFullscreen = AppDriver.Record(@"C:\videos\test-vid.mp4");
T? RunCode<T>(Expression<Func<Application, T>> code)
Runs the given expression within the context of the application. Returns the result of the expression if it is serializable, otherwise returns null
.
The Application.Current
is passed as the first parameter to the expression.
Usage
// Get the main window name.
var windowName = appDriver.RunCode(app => app.MainWindow.Name);
// Execute a private method on a global singleton.
// `Invoke` is a WPF Pilot convenience extension method for executing private methods.
var appWarningCount = appDriver.RunCode(_ => MyCustomContext.Invoke<int>("GetWarningCount"));
// Query a private property on a global singleton.
// `Property` is a WPF Pilot convenience extension method for querying private properties.
var appStartTime = appDriver.RunCode(_ => MyCustomContext.Property<DateTime>("StartTime"));
// Query a private field on a global singleton.
// `Field` is a WPF Pilot convenience extension method for querying private fields.
var userId = appDriver.RunCode(_ => GlobalLogger.Field<string>("m_currentUserId"));
void RunCode(Expression<Action<Application>> code)
Runs the given expression within the context of the application.
The Application.Current
is passed as the first parameter to the expression.
Usage
appDriver.RunCode(app => app.MainWindow.Focus());
appDriver.RunCode(_ => ScreenManager.SwitchToDarkMode());
appDriver.RunCode(_ => GlobalCache.Clear());
T? RunCodeAsync<T>(Expression<Func<Application, Task<T>>> code)
Runs the given async expression within the context of the application. Returns the awaited result of the expression if it is serializable, otherwise returns null
.
The Application.Current
is passed as the first parameter to the expression.
It is not possible to call await
within an expression, it will be handled by the AppDriver
for you.
Usage
var userProfile = appDriver.RunCodeAsync(_ => UserManager.GetUserProfileAsync());
var flushResult = appDriver.RunCodeAsync(_ => DiskManager.FlushCacheAsync());
void RunCodeAsync<T>(Expression<Func<Application, Task>> code)
Runs the given async expression within the context of the application.
The Application.Current
is passed as the first parameter to the expression.
It is not possible to call await
within an expression, it will be handled by the AppDriver
for you.
Usage
appDriver.RunCodeAsync(_ => AppStartup.DoStepsAsync());
Keyboard Keyboard
Returns the Keyboard
instance associated with the appDriver
.
Usage
appDriver.Keyboard.Type("Hello world");
Process Process
Returns the Process
associated with the appDriver
.
Usage
var processId = appDriver.Process.Id;