Skip to main content

Headless platform

The headless backend replaces native windows with an in-memory implementation. Avalonia still creates controls, evaluates bindings and styles, performs layout, and processes input. Install an adapter for test-managed setup, or use ProTest.Avalonia.Headless for a manual session.

Simulate platform input​

Import Avalonia.Headless to use extension methods on a headless Window or other TopLevel.

OperationMethods
Text compositionKeyTextInput(string)
Keyboard keysKeyPress, KeyRelease, KeyPressQwerty, KeyReleaseQwerty
MouseMouseDown, MouseUp, MouseMove, MouseWheel
TouchTouchBegin, TouchMove, TouchEnd
Drag/dropDragDrop, using Avalonia 12's IDataTransfer
DPISetRenderScaling

Keyboard key events and text composition are separate. Focus the target control before sending text:

editor.Focus();
window.KeyTextInput("ProTest");
window.KeyPressQwerty(PhysicalKey.Enter, RawInputModifiers.None);
window.KeyReleaseQwerty(PhysicalKey.Enter, RawInputModifiers.None);

Mouse coordinates are relative to the top level. For a 100 × 100 window containing a stretched button:

window.MouseMove(new Point(50, 50));
window.MouseDown(new Point(50, 50), MouseButton.Left);
window.MouseUp(new Point(50, 50), MouseButton.Left);

These methods exercise input routing and hit testing. Calling RaiseEvent on a button is useful for testing an event subscriber, but does not substitute for the button's input and command behavior.

The richer input helpers target individual controls and build multi-step interactions.

Bindings, focus, and real views​

Instantiate your actual view and attach its view model before showing the window. Keep references to controls, use a namescope lookup for XAML names, or query the visual and logical trees.

var editor = new TextBox();
var window = new Window { Content = editor };
window.Show();
try
{
editor.Focus();
Assert.True(editor.IsFocused);
window.KeyTextInput("Changed");
Assert.Equal("Changed", editor.Text);
}
finally
{
window.Close();
}

For binding assertions, use the application's actual binding mode and property-change notifications. A TwoWay binding must write back through a writable source property; changing an unbound control proves only the control behavior.

Dispatcher and render work​

After a state change that schedules work, flush the dispatcher before reading the outcome:

window.Width = 320;
window.Height = 200;
Dispatcher.UIThread.RunJobs();
Assert.Equal(new Size(320, 200), window.ClientSize);

AvaloniaHeadlessPlatform.ForceRenderTimerTick() advances the headless render timer. Window input helpers and frame capture perform their own internal queue processing. Prefer an explicit state condition over arbitrary delays.

Manual sessions​

Outside the xUnit adapter, use a session to own the dispatcher and application lifetime. TestAppBuilder is the same builder shown in the quick start.

await using var session = HeadlessUnitTestSession.StartNew(typeof(TestAppBuilder));
await session.Dispatch(() =>
{
var editor = new TextBox();
var window = new Window { Content = editor };
window.Show();
try
{
editor.Focus();
window.KeyTextInput("Manual session");
if (editor.Text != "Manual session")
throw new InvalidOperationException("Text input did not reach the editor.");
}
finally
{
window.Close();
}
}, CancellationToken.None);

Create, mutate, and inspect controls within dispatched work. Do not combine manual Avalonia initialization with the adapter-managed lifecycle in the same test.

See rendering for pixel capture and all platform options. Topic reference: Avalonia headless platform guide.