Headless Mode
PlusUi Headless allows you to run your UI application without a display. This is ideal for automated testing, screenshot generation, CI/CD pipelines, and server-side rendering.
Installation
dotnet add package PlusUi.core
dotnet add package PlusUi.headless
Project Setup
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net10.0</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>preview</LangVersion>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="PlusUi.core" Version="*" />
<PackageReference Include="PlusUi.headless" Version="*" />
</ItemGroup>
</Project>
Basic Usage
Create a headless instance using your existing App class:
using PlusUi.Headless;
using PlusUi.Headless.Enumerations;
// Create headless instance with PNG output format
using var headless = PlusUiHeadless.Create(new App(), ImageFormat.Png);
// Capture a screenshot of the current frame
var frame = await headless.GetCurrentFrameAsync();
await File.WriteAllBytesAsync("screenshot.png", frame);
Simulating User Input
Headless mode supports simulating mouse and keyboard interactions:
Mouse Input
// Move mouse to position (x, y)
headless.MouseMove(100, 200);
// Click (move, press, release)
headless.MouseDown();
headless.MouseUp();
// Combined: move and click
headless.MouseMove(100, 200);
headless.MouseDown();
headless.MouseUp();
Keyboard Input
// Type text character by character
foreach (char c in "Hello World")
{
headless.CharInput(c);
}
Complete Example
This example demonstrates a full workflow: capturing screenshots after each interaction.
using PlusUi.Headless;
using PlusUi.Headless.Enumerations;
// Create headless instance
using var headless = PlusUiHeadless.Create(new App(loadImagesSynchronously: true), ImageFormat.Png);
// 1. Capture initial state
var frame = await headless.GetCurrentFrameAsync();
await File.WriteAllBytesAsync("screenshot_01_initial.png", frame);
// 2. Click on a text entry and type
headless.MouseMove(65, 437);
headless.MouseDown();
headless.MouseUp();
foreach (char c in "Hello World")
{
headless.CharInput(c);
}
frame = await headless.GetCurrentFrameAsync();
await File.WriteAllBytesAsync("screenshot_02_text_entered.png", frame);
// 3. Click a checkbox
headless.MouseMove(117, 533);
headless.MouseDown();
headless.MouseUp();
frame = await headless.GetCurrentFrameAsync();
await File.WriteAllBytesAsync("screenshot_03_checkbox_clicked.png", frame);
// 4. Click a button
headless.MouseMove(51, 535);
headless.MouseDown();
headless.MouseUp();
frame = await headless.GetCurrentFrameAsync();
await File.WriteAllBytesAsync("screenshot_04_button_clicked.png", frame);
Image Formats
The ImageFormat enum supports the following output formats:
| Format | Description |
|---|---|
ImageFormat.Png |
PNG format (lossless, with transparency) |
ImageFormat.Jpeg |
JPEG format (lossy, smaller file size) |
ImageFormat.Webp |
WebP format (modern, efficient compression) |
// PNG (default, best for UI screenshots)
using var headless = PlusUiHeadless.Create(new App(), ImageFormat.Png);
// JPEG (smaller files, no transparency)
using var headless = PlusUiHeadless.Create(new App(), ImageFormat.Jpeg);
// WebP (modern format, good compression)
using var headless = PlusUiHeadless.Create(new App(), ImageFormat.Webp);
Use Cases
Automated UI Testing
using var headless = PlusUiHeadless.Create(new App(), ImageFormat.Png);
// Navigate to a specific state
headless.MouseMove(buttonX, buttonY);
headless.MouseDown();
headless.MouseUp();
// Capture and compare with baseline
var screenshot = await headless.GetCurrentFrameAsync();
await File.WriteAllBytesAsync("test_result.png", screenshot);
// Compare with expected baseline image
Assert.IsTrue(CompareImages("baseline.png", "test_result.png"));
CI/CD Visual Regression Testing
Run headless tests in your CI pipeline to detect visual regressions:
# Example GitHub Actions step
- name: Run Visual Tests
run: dotnet run --project MyApp.HeadlessTests
Screenshot Generation for Documentation
// Generate documentation screenshots automatically
var pages = new[] { "home", "settings", "profile" };
foreach (var page in pages)
{
// Navigate to page
NavigateToPage(headless, page);
// Capture screenshot
var frame = await headless.GetCurrentFrameAsync();
await File.WriteAllBytesAsync($"docs/images/{page}.png", frame);
}
Server-Side Rendering
Generate images on the server without requiring a display:
public async Task<byte[]> GeneratePreviewImage(UserData data)
{
using var headless = PlusUiHeadless.Create(new PreviewApp(data), ImageFormat.Png);
return await headless.GetCurrentFrameAsync();
}
Tips
Synchronous Image Loading - When generating screenshots, pass
loadImagesSynchronously: trueto your App constructor to ensure all images are loaded before capturing.
Dispose Properly - Always use
usingor callDispose()on the headless instance to free resources.
No User Input - Headless mode doesn’t have actual user input. All interactions must be simulated programmatically.
Next Steps
- Project Setup - App configuration and DI
- Best Practices - Write better PlusUi code