Skip to content
End To End Tester
Platformsadvanced

Testing Avalonia Applications

Avalonia's headless test platform runs real UI tests in CI in milliseconds with no display server — the most testable desktop stack in .NET.

2 min read · updated 19 September 2026

Avalonia is a cross-platform .NET UI framework, and its testing story is the most interesting in desktop software: a headless platform that runs the real UI stack with no windowing system at all.

Layout runs. Styles apply. Bindings evaluate. Input is dispatched. It takes milliseconds and needs no display server, which means real UI tests run in an ordinary Linux CI container.

Nothing else covered in desktop testing offers this.

#Setting it up

xml
<PackageReference Include="Avalonia.Headless.XUnit" Version="11.*" />
<!-- Add this too if you want screenshots: -->
<PackageReference Include="Avalonia.Skia" Version="11.*" />
csharp
// TestAppBuilder.cs — one per test assembly
using Avalonia;
using Avalonia.Headless;

[assembly: AvaloniaTestApplication(typeof(TestAppBuilder))]

public static class TestAppBuilder
{
    public static AppBuilder BuildAvaloniaApp() => AppBuilder
        .Configure<App>()
        .UseHeadless(new AvaloniaHeadlessPlatformOptions
        {
            UseHeadlessDrawing = false   // false => Skia renders, screenshots work
        });
}

#A real UI test

csharp
public class CheckoutViewTests
{
    [AvaloniaFact]
    public void Entering_a_card_enables_the_pay_button()
    {
        var window = new Window { Content = new CheckoutView { DataContext = new CheckoutViewModel() } };
        window.Show();

        var pay = window.FindControl<Button>("PayButton")!;
        var card = window.FindControl<TextBox>("CardNumber")!;

        Assert.False(pay.IsEnabled);

        card.Text = "4242424242424242";
        Dispatcher.UIThread.RunJobs();      // let bindings and layout settle

        Assert.True(pay.IsEnabled);
    }

    [AvaloniaFact]
    public void Clicking_pay_shows_the_confirmation()
    {
        var window = new Window { Content = new CheckoutView { DataContext = PaidViewModel() } };
        window.Show();

        // Real input, dispatched through the real input pipeline.
        window.KeyPressQwerty(PhysicalKey.Tab, RawInputModifiers.None);
        window.MouseDown(new Point(120, 400), MouseButton.Left);
        window.MouseUp(new Point(120, 400), MouseButton.Left);
        Dispatcher.UIThread.RunJobs();

        var confirmation = window.FindControl<TextBlock>("Confirmation")!;
        Assert.Equal("Order confirmed", confirmation.Text);
    }
}

Dispatcher.UIThread.RunJobs() is the Avalonia equivalent of Angular's detectChanges or Vue's nextTick: it drains the queued layout and binding work so the assertion sees settled state. Forgetting it is the most common reason an Avalonia headless test fails confusingly.

#Layout assertions

Because layout genuinely runs, you can assert on it — which is something neither jsdom nor most desktop tooling allows:

csharp
[AvaloniaFact]
public void The_total_stays_visible_at_a_narrow_width()
{
    var window = new Window { Width = 320, Height = 640, Content = new CheckoutView() };
    window.Show();
    Dispatcher.UIThread.RunJobs();

    var total = window.FindControl<TextBlock>("Total")!;
    var bounds = total.Bounds;

    Assert.True(bounds.Width > 0 && bounds.Height > 0);
    Assert.True(bounds.Right <= window.Width);      // not clipped off the edge
}

#Screenshots in CI

csharp
[AvaloniaFact]
public void Checkout_matches_the_approved_appearance()
{
    var window = new Window { Width = 800, Height = 600, Content = new CheckoutView() };
    window.Show();
    Dispatcher.UIThread.RunJobs();

    using var frame = window.CaptureRenderedFrame()!;
    frame.Save("artifacts/checkout.png");

    // Compare against a committed baseline with your image-diff library of
    // choice, and publish the diff as a CI artifact on failure.
}

Visual regression for a desktop application, on a Linux runner, with no display server. See screenshots for the general approach and its pitfalls.

#The pyramid still applies

The headless platform is fast enough that it is tempting to test everything through it. Resist a little: a view model is still cheaper to test directly, and most business logic belongs there.

csharp
// No UI at all. Microseconds. This is where most of the tests should be.
[Fact]
public void Pay_is_disabled_until_the_card_is_valid()
{
    var vm = new CheckoutViewModel(new StubPaymentService());

    Assert.False(vm.CanPay);
    vm.CardNumber = "4242424242424242";
    Assert.True(vm.CanPay);
}

MVVM makes this natural, which is the underlying point: an Avalonia application written with a proper view-model layer has most of its logic in plain classes, and the headless tests then cover the part that genuinely needs a UI — bindings, styles, layout, input.

#What headless cannot cover

  • Native dialogs — file pickers, print, system message boxes
  • Multi-window and window-manager behaviour
  • Platform integration: tray icons, notifications, drag and drop from the OS
  • Real GPU rendering differences
  • Actual per-platform font rendering

Those need a real session on each target platform, and they are the handful of end-to-end tests an Avalonia application should have. Driven by Appium or FlaUI on Windows — see desktop testing.

#Why this matters beyond Avalonia

The interesting thing here is not the framework; it is the demonstration that a UI toolkit can be built so that its full stack runs headlessly and deterministically. Most desktop frameworks cannot, and that single fact accounts for most of the difficulty in desktop testing.

If you are choosing a .NET desktop stack and test automation is a requirement rather than an aspiration, this is a real point in Avalonia's favour.

Common questions

What is Avalonia's headless test platform?
A rendering backend that runs the full Avalonia UI stack — layout, styling, input, data binding — with no windowing system. Tests create real controls, dispatch real input and read real layout results, in milliseconds, on a Linux CI runner with no display.
Do I still need end-to-end tests for an Avalonia app?
A few, for things the headless platform cannot cover — native dialogs, file pickers, multi-window behaviour, platform integration. But the headless tests cover far more than UI tests usually can, so the end-to-end layer stays genuinely small.
Can headless tests take screenshots?
Yes, with the Skia rendering option enabled. That makes visual regression testing possible in CI with no display server, which is unusual for a desktop framework.

Runnable samples for this page

last test results ↗

Working tests, not fragments — they run in CI on every push to 8exgh/endtoendtester-samples.

Was this page useful?