A powerful toolchain for recording user interactions in Avalonia desktop applications and automatically generating executable C# headless tests.
This project consists of four main components:
- Avalonia.TestRecorder - NuGet library for recording user interactions in desktop applications
- Avalonia.HeadlessTestKit - NuGet library providing DSL helpers for writing headless tests
- SampleApp - Demo Avalonia desktop application with recorder integration
- SampleApp.Tests.Headless - Reference test project demonstrating generated tests
- π― Record user interactions - Capture clicks, text input, scrolling, and keyboard actions
- π§ͺ Generate test code - Automatically create C# test files compatible with Avalonia.Headless
- π Stable selectors - Prioritizes AutomationId-based element identification with intelligent fallbacks
- β Assertion capture - Record UI state validations with Ctrl+Shift+A
- π Clean DSL - Fluent, readable test code using the
Uihelper class - β‘ Headless execution - Run tests without visible windows in CI/CD pipelines
- π¨ Theme support - Overlay panel adapts to light/dark themes
- π Real-time feedback - Status bar shows generated code and validation results
Add the TestRecorder to your Avalonia desktop app:
dotnet add package Kibnet.Avalonia.TestRecorderIntegrate the recorder in your App.axaml.cs:
using Avalonia.TestRecorder;
public override void OnFrameworkInitializationCompleted()
{
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
{
var mainWindow = new MainWindow { DataContext = new MainWindowViewModel() };
desktop.MainWindow = mainWindow;
#if DEBUG
// Attach recorder when AV_RECORDER=1 environment variable is set
if (Environment.GetEnvironmentVariable("AV_RECORDER") == "1")
{
var session = TestRecorder.Attach(mainWindow, new RecorderOptions
{
OutputDirectory = "./RecordedTests",
ScenarioName = "MyScenario"
});
session.Start();
}
#endif
}
base.OnFrameworkInitializationCompleted();
}Run your app with recording enabled:
set AV_RECORDER=1
dotnet runOverlay Panel:
When the recorder is attached, a compact overlay panel appears at the top-right of your window with:
- π₯ Record/Pause button - Unified button to start/stop recording (red when recording, green when paused)
- Step counter - Number of recorded steps
- ποΈ Clear button - Reset recorded steps
- πΎ Save button - Save test to file (prompts for file path via dialog)
- Status bar - Shows real-time code preview and validation feedback
Hotkeys:
Ctrl+Shift+R- Start/Stop/Pause recording (unified action)Ctrl+Shift+S- Save test to file (prompts for file path)Ctrl+Shift+A- Capture assertion from element under mouse cursor (or focused element as fallback)Ctrl+Shift+T- Capture text content from element under cursor
Steps:
- Launch your app with recording enabled
- The overlay panel appears at the top of the window
- Perform your test scenario (click buttons, enter text, etc.)
- Hover your mouse over the element you want to assert, then press
Ctrl+Shift+Ato capture its state - Press
Ctrl+Shift+Rto pause recording if needed - Click the πΎ button or press
Ctrl+Shift+Sto save - enter the full file path in the prompt dialog - The status bar shows real-time code preview and validation results for each step
Create a new test project for your Avalonia application. For detailed step-by-step instructions, see the Test Project Setup Guide.
Quick setup:
dotnet new xunit -n YourApp.Tests.Headless
cd YourApp.Tests.HeadlessAdd required packages:
dotnet add package Avalonia.Headless.XUnit
dotnet add package Kibnet.Avalonia.HeadlessTestKitAdd reference to your application:
dotnet add reference ../YourApp/YourApp.csprojYour .csproj should look like this:
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Avalonia.Headless.XUnit" Version="11.3.6" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.5.3" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.3" />
</ItemGroup>
<ItemGroup>
<Using Include="Xunit" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="Avalonia.HeadlessTestKit" Version="1.0.0" />
<ProjectReference Include="..\YourApp\YourApp.csproj" />
</ItemGroup>
</Project>Create AvaloniaTestApplication.cs to configure headless mode:
using Avalonia;
using Avalonia.Headless;
[assembly: AvaloniaTestApplication(typeof(YourApp.Tests.Headless.TestAppBuilder))]
namespace YourApp.Tests.Headless;
public class TestAppBuilder
{
public static AppBuilder BuildAvaloniaApp() => AppBuilder.Configure<YourApp.App>()
.UsePlatformDetect()
.UseHeadless(new AvaloniaHeadlessPlatformOptions
{
UseHeadlessDrawing = false
})
.LogToTrace();
}Important Notes:
- Replace
YourAppwith your actual application namespace - The
[assembly: AvaloniaTestApplication]attribute is required for xUnit discovery UseHeadlessDrawing = falseis recommended for better test stability- Tests will run without visible windows - perfect for CI/CD
Add the HeadlessTestKit to your test project (if not already added):
dotnet add package Kibnet.Avalonia.HeadlessTestKitExample test using the generated code:
using Avalonia.Headless.XUnit;
using Avalonia.HeadlessTestKit;
using Xunit;
public class LoginTests
{
[AvaloniaFact]
public void LoginScenario_ValidCredentials_ShowsSuccess()
{
// Arrange - Create and show the window
var window = new MainWindow
{
DataContext = new MainWindowViewModel()
};
window.Show();
var ui = new Ui(window);
// Act - Perform user interactions
ui.Click("usernameField");
ui.TypeText("usernameField", "testuser");
ui.Click("passwordField");
ui.TypeText("passwordField", "password123");
ui.Click("loginButton");
// Assert - Verify expected results
ui.AssertText("statusLabel", "Login successful");
}
}Important: Always call window.Show() before creating the Ui instance. This ensures the window is properly initialized in headless mode.
Run your tests:
dotnet testFor stable test recording, always set AutomationId on interactive controls:
<TextBox AutomationProperties.AutomationId="usernameField" />
<Button AutomationProperties.AutomationId="loginButton" />
<TextBlock AutomationProperties.AutomationId="statusLabel" />Naming Convention:
- Use descriptive, semantic names:
loginButton,usernameField,statusLabel - Be consistent:
fieldName_TypeorcamelCase - Avoid generic names:
button1,textBox1
The Ui class provides fluent methods for interacting with your application:
Interactions:
Click(id)- Click elementRightClick(id)- Right-click elementDoubleClick(id)- Double-click elementTypeText(id, text)- Enter text into elementKeyPress(keyName)- Press specific key (e.g., "Enter", "Tab")SelectItem(id, itemText)- Select item in ComboBox or ListBoxScroll(id, deltaX, deltaY)- Scroll elementHover(id)- Move mouse over element
Assertions:
AssertText(id, expected)- Verify text contentAssertChecked(id, expected)- Verify toggle stateAssertVisible(id)- Verify element is visibleAssertEnabled(id)- Verify element is enabled
Synchronization:
WaitFor(id, condition, timeout)- Wait for custom conditionWaitForText(id, expected, timeout)- Wait for text valueWaitForVisible(id, timeout)- Wait for element to appearWaitForEnabled(id, timeout)- Wait for element to be enabled
git clone <repository-url>
cd Kibnet.Avalonia.TestRecorder
dotnet build
dotnet test/
βββ src/
β βββ Avalonia.TestRecorder/ # Recorder library
β βββ Avalonia.HeadlessTestKit/ # Test helper DSL
βββ samples/
β βββ SampleApp/ # Demo application
β βββ SampleApp.Tests.Headless/ # Demo tests
βββ docs/ # Documentation
- Test Project Setup Guide - Complete guide for setting up a headless test project
- AutomationId Conventions - Best practices for naming AutomationIds
- Overlay Panel Guide - Recorder UI reference
- Troubleshooting - Common issues and solutions
- GitHub Actions Publishing - CI/CD setup for NuGet packages
MIT
Contributions are welcome! Please open an issue or submit a pull request.