Skip to main content
The CI/CD Challenge: Running FlaUI Tests in GitHub Actions

The CI/CD Challenge: Running FlaUI Tests in GitHub Actions

A guide to setting up a robust CI/CD pipeline for WPF applications using FlaUI and GitHub Actions, focusing on handling headless environments and debugging failures.

  1. Posts/

The CI/CD Challenge: Running FlaUI Tests in GitHub Actions

·776 words·4 mins· loading
👤

Chris Malpass

Author

You’ve written your FlaUI tests. They pass locally. You push to GitHub. The build fails.

Welcome to the world of UI automation in CI/CD.

Running desktop UI tests in a cloud environment like GitHub Actions is notoriously difficult. Unlike unit tests, which just need a CPU and memory, UI tests need a Desktop Session.

In this post, we’ll tackle the challenges of running FlaUI tests on GitHub Actions windows-latest runners and how to make them reliable.

The Environment: What is windows-latest?
#

When you spin up a GitHub Action with runs-on: windows-latest, you are getting a Virtual Machine. Crucially, this VM does have a desktop session, but it is “headless” (no physical monitor connected).

The default resolution is typically 1024x768.

If your application requires a 1080p screen to render all buttons, your tests will fail because elements might be scrolled out of view or collapsed into a “hamburger” menu that your test doesn’t expect.

Step 1: The Workflow YAML
#

Here is a baseline workflow for running .NET tests.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
name: Desktop UI Tests

on: [push]

jobs:
  test:
    runs-on: windows-latest
    
    steps:
    - uses: actions/checkout@v4
    
    - name: Setup .NET
      uses: actions/setup-dotnet@v4
      with:
        dotnet-version: 8.0.x
        
    - name: Build
      run: dotnet build MyWpfApp.sln --configuration Release
      
    - name: Run FlaUI Tests
      run: dotnet test MyWpfApp.Tests/MyWpfApp.Tests.csproj --configuration Release --no-build --logger "trx;LogFileName=test_results.trx"

This might work if your app is simple. But when it fails, you’ll have no idea why.

Step 2: The “Black Box” Problem (Screenshots)
#

When a test fails locally, you watch it happen. In CI, you just get a stack trace saying ElementNotFoundException.

To fix this, you must configure your tests to take a screenshot on failure. FlaUI makes this easy, but you need to hook it into your test framework (e.g., NUnit TearDown).

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using System.IO;
using FlaUI.Core.Capturing;

[TearDown]
public void TearDown()
{
    if (TestContext.CurrentContext.Result.Outcome.Status == TestStatus.Failed)
    {
        var screenshotPath = Path.Combine(TestContext.CurrentContext.WorkDirectory, $"Fail_{TestContext.CurrentContext.Test.Name}.png");
        
        // Capture the full desktop
        var image = Capture.Screen();
        image.ToFile(screenshotPath);
        
        TestContext.AddTestAttachment(screenshotPath);
    }
    
    // Ensure the app is closed/killed so it doesn't block the next test
    _app?.Dispose();
    _automation?.Dispose();
}

Step 3: Uploading Artifacts
#

Now that you’re saving screenshots, you need to tell GitHub Actions to save them so you can download them after the run.

Update your YAML to include an upload-artifact step that runs even if tests fail.

1
2
3
4
5
6
7
8
9
    - name: Upload Test Screenshots
      if: failure()
      uses: actions/upload-artifact@v4
      with:
        name: test-failures
        path: |
          **/*.png
          **/*.trx
        if-no-files-found: ignore

Step 4: Handling Screen Resolution
#

If 1024x768 is breaking your app, you have two options:

  1. Design for Responsiveness: Make your app (and tests) handle small screens. This is the “correct” software engineering answer.
  2. Change the Resolution: You can try to use tools to change the VM resolution, but on GitHub hosted runners, this is often locked or unreliable.

A better hack is to ensure your Application.Launch code maximizes the window, or sets a specific size that fits within 1024x768.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
using FlaUI.Core;
using FlaUI.UIA3;

// In your Setup
var app = Application.Launch(appPath);
var automation = new UIA3Automation();
var window = app.GetMainWindow(automation);

// Force a known size that fits in the default runner resolution
window.Move(0, 0);
window.Resize(1024, 768);

Step 5: The “Active Session” Myth
#

You might read online that you need a “Self-Hosted Runner” with an active RDP session to run UI tests.

For FlaUI (UIA3), this is not strictly true. UIA3 works fine in the non-interactive session provided by GitHub Actions for standard controls.

However, if you use:

  • Mouse.Move() (Hardware cursor simulation)
  • Keyboard.Type() (Hardware input simulation)

These might fail or behave erratically if the session is locked.

Best Practice: Prefer Pattern methods over Input simulation.

  • Don’t use: btn.Click() (which moves mouse and clicks).
  • Use: btn.Invoke() (which uses the UIA InvokePattern).

Invoke() works programmatically and doesn’t care if the mouse cursor is actually working or if the screen is locked.

Summary
#

Running FlaUI on GitHub Actions is possible and powerful.

  1. Use windows-latest.
  2. Always capture screenshots on failure.
  3. Upload those screenshots as Artifacts.
  4. Prefer Invoke() patterns over physical mouse clicks to avoid session issues.

Now your CI pipeline isn’t just compiling code—it’s proving your app actually works.

Further Reading
#