Select Page
Desktop Automation Testing

Reqnroll Tutorial: Build a Desktop Automation Framework with FlaUI & NUnit (C#)

Reqnroll tutorial: build a Windows desktop automation framework with FlaUI and NUnit, step by step, with code and screenshots.

Mohammed Yasin

Team Lead

Posted on

12/08/2026

Reqnroll Tutorial Build A Desktop Automation Framework With Flaui & Nunit (c#) (2)

In this Reqnroll tutorial, you’ll build a Windows Desktop Automation framework using FlaUI, a modern, open-source .NET library for automating Windows desktop applications. It is built on top of Microsoft’s native UI Automation (UIA) framework and acts as a lightweight wrapper it simplifies day-to-day interaction with UI elements, while still giving you access to the underlying UI Automation APIs when you need advanced functionality.

FlaUI supports a wide range of Windows application technologies, including:

  • Win32
  • Windows Forms (WinForms)
  • Windows Presentation Foundation (WPF)
  • Universal Windows Platform (UWP)
  • Windows Store applications

Looking to automate a real enterprise desktop application instead of Notepad? See how our desktop app automation testing services can help.

Quick answer: This Reqnroll tutorial shows how to automate a Windows desktop application (Notepad) using FlaUI for UI Automation, Reqnroll for Gherkin-based BDD scenarios, and NUnit as the test runner from project setup through running tests via the command line and viewing an HTML report.

What Is FlaUI?

FlaUI is a free, open-source .NET library for automating Windows desktop applications (Win32, WinForms, WPF, and UWP) by wrapping Microsoft’s UI Automation API in a clean C# interface.

Why Choose FlaUI?

FlaUI stands out for its clean, modern API, its active community support, and its seamless integration with the .NET ecosystem. It works naturally with popular testing frameworks such as NUnit, Reqnroll, xUnit, and MSTest, which means teams can build scalable automation frameworks and plug them straight into CI/CD pipelines.

Unlike older desktop automation tools that rely on additional background services or complicated configuration, FlaUI talks directly to Microsoft’s UI Automation framework. The result is faster execution, better stability, and easier long-term maintenance.

What’s the Difference Between UIA2 and UIA3?

UIA2 is FlaUI’s managed .NET backend for older Win32/WinForms apps, while UIA3 is the newer COM-based backend with better support for WPF and UWP. Use UIA3 by default unless you’re automating a legacy Win32 application.

A unique advantage of FlaUI is that it supports both UIA2 and UIA3, so you can pick whichever automation backend best fits your target application:

  • UIA2 (UI Automation Version 2) the managed .NET implementation of Microsoft’s UI Automation API. It offers strong compatibility with traditional Win32 and WinForms applications.
  • UIA3 (UI Automation Version 3) the newer, COM-based implementation. It provides enhanced support for WPF, UWP, and other modern Windows applications, along with better compatibility with newer controls.

If you’ve automated web applications with Selenium or Playwright, you already understand the value of BDD (Behavior Driven Development) and Page Object Models. But desktop applications Notepad, calculators, WPF/WinForms line-of-business tools, legacy Win32 apps don’t have a DOM, and Selenium can’t touch them.

That’s where FlaUI comes in. Combined with Reqnroll (the actively maintained successor to SpecFlow) and NUnit, you get a production-grade framework for automating Windows desktop applications using plain-English Gherkin scenarios.

By the end of this article, you’ll have:

  • A working Reqnroll + NUnit + FlaUI solution built from a blank Visual Studio project
  • A feature file written in Gherkin
  • A Page Object Model class wrapping Notepad
  • Step definitions that map Gherkin steps to C# code
  • Hooks that log every step and capture a screenshot on failure
  • HTML test reports generated automatically
  • The ability to run everything from the command line using the NUnit console runner
  • The skills to troubleshoot failures and extend the framework confidently

We’ll use Notepad as the target application throughout this tutorial. It ships with every Windows machine, requires no installation, and is perfect for learning the mechanics of desktop automation without fighting with a complex UI.

Why Reqnroll, NUnit, and FlaUI Work Well Together

Sno Component Role Why it’s used
1 Reqnroll BDD framework Lets you write test scenarios in plain English (Gherkin) that stakeholders can read; actively maintained fork of SpecFlow
2 NUnit Test runner / assertion framework Executes the generated test methods and reports pass/fail
3 FlaUI UI automation library Wraps Microsoft’s UI Automation API in a clean, fluent C# API to find and interact with desktop controls

1. Prerequisites

Install the following before you start:

  • Visual Studio Community Edition 2026 (or 2022 instructions are nearly identical) free from visualstudio.microsoft.com
  • The .NET SDK (latest supported LTS version) verify with the command below
  • NUnit Console Runner used later to execute tests from the command line. Install via NuGet or download from the NUnit documentation and releases page
  • FlaUInspect a free inspection tool (similar to Selenium’s “Inspect Element”) that lets you see the AutomationId, Name, ControlType, and ClassName of every control in a desktop application. Download it from the FlaUI GitHub repository releases

dotnet --version

Tip for beginners: Open FlaUInspect, then open Notepad side by side. Click on Notepad’s text area or “File” menu inside FlaUInspect and note the AutomationId values. You’ll need these in Step 4 of this guide.

2. Install the Reqnroll Visual Studio Extension

The Reqnroll extension gives Visual Studio the ability to understand .feature files, provide syntax highlighting, and auto-generate step definition skeletons.

Steps:

  • Open Visual Studio → Extensions menu → Manage Extensions
  • In the search box, type “Reqnroll for Visual Studio 2022 & 2026”
  • Select it from the results and click Install
  • Restart Visual Studio when prompted to complete installation

Once installed, .feature files will render with proper Gherkin syntax highlighting, and right-clicking a scenario will give you options like “Generate Step Definitions.”

Reqnroll for Visual Studio 2022 and 2026 extension shown as installed in the Extension Manager

3. Create a Reqnroll NUnit Project

3.1 Create the project

  • File → New → Project
  • In the project template search box, type “Reqnroll”
  • Select Reqnroll Project (NUnit) this scaffolds a project pre-wired for NUnit rather than MSTest or xUnit

Visual Studio Create a new project dialog with the Reqnroll Project template selected

3.2 Name your project

Give it a meaningful, lowercase-hyphenated or PascalCase name that reflects its purpose. For this tutorial we’ll use:


qa-test-flaui

3.3 Note the new solution format

Visual Studio 2026 creates solutions using the newer .slnx format (an XML-based replacement for the legacy .sln format). You’ll see:


qa-test-flaui.slnx

This is functionally equivalent to a .sln file all the same commands (dotnet build, dotnet test) work identically. You don’t need to change anything about your workflow.

3.4 Install the required NuGet packages

Open Tools → NuGet Package Manager → Manage NuGet Packages for Solution, or use the Package Manager Console / dotnet add package commands below.

i. Reqnroll packages (BDD framework + NUnit integration)


dotnet add package Reqnroll
dotnet add package Reqnroll.NUnit

ii. NUnit packages


dotnet add package NUnit
dotnet add package NUnit3TestAdapter
dotnet add package Microsoft.NET.Test.Sdk

  • NUnit the core testing/assertion framework
  • NUnit3TestAdapter required for Visual Studio’s Test Explorer to discover and run your tests
  • Microsoft.NET.Test.Sdk the general .NET test SDK required by any test project

iii. FlaUI packages


dotnet add package FlaUI.Core
dotnet add package FlaUI.UIA3

  • FlaUI.Core the core desktop automation library (application launching, waits, element trees)
  • FlaUI.UIA3 the modern UI Automation v3 implementation (use this by default)
  • FlaUI.UIA2 (optional) only needed if you’re automating older Win32/legacy applications that don’t expose UIA3 properties correctly:

dotnet add package FlaUI.UIA2

After installation, your .csproj should contain a <PackageReference> entry for each package above. Build the project once (Ctrl+Shift+B) to confirm everything restores cleanly before moving on.

4. Folder Structure

Keeping things simple and beginner-friendly, here’s the minimal Reqnroll + FlaUI project structure we’ll build:


qa-test-flaui/
|
+-- Features/
|   +-- Notepad.feature              # Gherkin scenarios
|
+-- StepDefinitions/
|   +-- NotepadSteps.cs              # Glue code between Gherkin and Page Objects
|
+-- Pages/
|   +-- NotepadWindow.cs             # Page Object Model for Notepad
|
+-- Hooks/
|   +-- Hooks.cs                     # Logging + screenshot capture
|
+-- Screenshots/                     # Auto-created at runtime for failure screenshots
|
+-- reqnroll.json                    # Reqnroll configuration (HTML report generation)
+-- qa-test-flaui.csproj

This mirrors the same separation of concerns you’d use in a Selenium framework: Features (what), StepDefinitions (glue), Pages (how), Hooks (cross-cutting concerns).

4.1 The Feature File Features/Notepad.feature

Feature files are written in Gherkin: plain English structured into Feature, Scenario, and Given/When/Then steps. Anyone on your team QA, developers, product owners can read this without knowing C#.

Feature: Notepad Text Editing
    As a user,
    I want to type text and access the File menu

Scenario: Type text into Notepad and verify it appears
    Given I launch Notepad
    When I type "Hello from Reqnroll and FlaUI!" into the editor
    Then I click Page Setup option under File menu

Right-click inside the feature file and choose Generate Step Definitions Reqnroll will scan the steps and offer to scaffold matching method signatures for you.

4.2 The Page Object Model Pages/NotepadWindow.cs

This class is the only place in the entire framework that knows how to interact with Notepad’s UI. If Notepad’s layout changes, or you swap the target app, you only edit this file step definitions stay untouched. Keeping this layer isolated is also what keeps test automation maintenance costs down as your framework grows.

using FlaUI.Core;
using FlaUI.Core.AutomationElements;
using FlaUI.Core.Definitions;
using FlaUI.Core.Tools;
using FlaUI.UIA3;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using Application = FlaUI.Core.Application;

namespace Qa_Test_Flaui.Objects.Windows
{
    public class NotepadWindow : IDisposable
    {
        private readonly Application _app;
        private readonly UIA3Automation _automation;
        private readonly Window _mainWindow;

        public NotepadWindow()
        {
            Process.Start("notepad.exe");
            _automation = new UIA3Automation();
            _mainWindow = Retry.WhileNull(
                () => _automation.GetDesktop()
                    .FindFirstDescendant(cf => cf.ByName("Untitled - Notepad"))
                    ?.AsWindow(),
                TimeSpan.FromSeconds(10))
                .Result;
            Thread.Sleep(5000);
        }

        private const string FileTab = "File";
        private const string PageSetupSubTab = "Page setup";

        private AutomationElement GetEditorElement()
        {
            Console.WriteLine("---> Get Editor Main Element: " + _mainWindow.Title);
            return _mainWindow.FindFirstDescendant(cf => cf.ByName("Text editor"));
        }

        public void TypeText(string text)
        {
            var editor = GetEditorElement();
            editor.Focus();
            editor.AsTextBox().Enter(text);
        }

        public void OpenPageSetUp()
        {
            _mainWindow.FindFirstDescendant(
                 cf => cf.ByName(FileTab)).Click();
            Thread.Sleep(2000);
            _mainWindow.FindFirstDescendant(
                 cf => cf.ByName(PageSetupSubTab)).Click();
        }
    }
}

4.3 Step Definitions StepDefinitions/NotepadSteps.cs

Step definitions are the glue layer. They parse the Gherkin text, call methods on the Page Object, and make assertions.

using NUnit.Framework;
using Qa_Test_Flaui.Objects.Windows;
using Reqnroll;

namespace qa_test_flaui.StepDefinitions
{
    [Binding]
    public class NotepadSteps
    {
        private readonly ScenarioContext _scenarioContext;
        private NotepadWindow _notepad;

        public NotepadSteps(ScenarioContext scenarioContext)
        {
            _scenarioContext = scenarioContext;
        }

        [Given(@"I launch Notepad")]
        public void GivenILaunchNotepad()
        {
            _notepad = new NotepadWindow();
            // Store in ScenarioContext so Hooks can access it (e.g., to close it after the scenario)
            _scenarioContext["NotepadWindow"] = _notepad;
        }

        [When(@"I type ""(.*)"" into the editor")]
        public void WhenITypeIntoTheEditor(string text)
        {
            _notepad.TypeText(text);
        }

        [Then("I click Page Setup option under File menu")]
        public void ThenIClickPageSetupOptionUnderFileMenu()
        {
            _notepad.OpenPageSetUp();
        }
    }
}

Key things to notice for beginners:

  • [Binding] tells Reqnroll “this class contains step definitions.”
  • The regular expressions in [Given], [When], [Then] attributes match the Gherkin text, and (.*) captures the string in quotes as a method parameter.
  • We store the NotepadWindow instance in ScenarioContext a dictionary-like object that Reqnroll shares across step definitions and hooks within the same scenario. This is how Hooks will later access it to close Notepad automatically.

4.4 Hooks Hooks/Hooks.cs

Hooks handle cross-cutting concerns that shouldn’t clutter your step definitions: logging every step, capturing screenshots on failure, and cleaning up resources.

using FlaUI.Core.Capturing;
using NUnit.Framework;
using Qa_Test_Flaui.Objects.Windows;
using Reqnroll;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Qa_Test_Flaui.Objects.Hooks
{
    [Binding]
    public class Hooks
    {
        private readonly ScenarioContext _scenarioContext;
        // ThreadLocal prevents log mixing when running tests in parallel
        private static readonly ThreadLocal<IReqnrollOutputHelper> _outputHelperContainer = new();

        public Hooks(ScenarioContext scenarioContext)
        {
            _scenarioContext = scenarioContext;
        }

        [BeforeScenario]
        public void BeforeScenario(IReqnrollOutputHelper outputHelper)
        {
            // Store the current scenario's output helper in the thread container
            _outputHelperContainer.Value = outputHelper;
        }

        [AfterScenario]
        public void AfterScenario(IReqnrollOutputHelper outputHelper)
        {
            // Clear the value after the scenario finishes to prevent memory leaks
            _outputHelperContainer.Value = null;
            String strPath = TakeScreenshot();
            outputHelper.AddAttachment(strPath);
        }

        /// <summary>
        /// Globally accessible method to write logs to the Reqnroll test output.
        /// </summary>
        public static void AttachStepLog(string message)
        {
            Console.WriteLine("---> AttachStepLog in...: ");
            if (_outputHelperContainer.Value != null)
            {
                Console.WriteLine("---> AttachStepLog IF in...: " + message);
                _outputHelperContainer.Value.WriteLine(message);
            }
        }

        public static string TakeScreenshot()
        {
           string fullPath = "";
            try
            {
                string projectRoot = AppContext.BaseDirectory.Split(
             new[] { $"{Path.DirectorySeparatorChar}bin{Path.DirectorySeparatorChar}" },
             StringSplitOptions.None)[0];
                string reportFolderLocation = Path.Combine(projectRoot, "Screenshots");
                if (!Directory.Exists(reportFolderLocation))
                    Directory.CreateDirectory(reportFolderLocation);
                string fileName = "img-" + DateTime.Now.Ticks + ".png";
                fullPath = Path.Combine(reportFolderLocation, fileName);
                var bitmap = Capture.Screen();
                bitmap.ToFile(fullPath);
            }
            catch (Exception ex)
            {
                Console.WriteLine("******* Hooks - Screenshot Exception >>>" + ex.Message);
            }
            return fullPath;
        }
    }
}

What’s happening here, step by step:

  • [BeforeTestRun] runs once before any scenario we use it to ensure the Screenshots folder exists.
  • [BeforeScenario] and [BeforeStep] log progress to the console (and therefore to the NUnit test output), so when you’re troubleshooting a failure you can see exactly which step the test reached.
  • [AfterStep] checks _scenarioContext.TestError if a step threw an exception (an assertion failure or an exception from FlaUI), we immediately capture a full-screen screenshot using FlaUI.Core.Capturing.Capture.
  • TestContext.AddTestAttachment links that screenshot file directly into the NUnit test result, so it shows up when you view results in Test Explorer or in the generated HTML report.
  • [AfterScenario] disposes of the Notepad process so it doesn’t linger in the background between test runs a common source of “flaky” desktop test suites is leftover processes from previous failed runs.

5. reqnroll.json Generating HTML Test Results

Reqnroll uses a reqnroll.json file at the project root to control runtime behavior, including generating a Living Documentation-style HTML report after test execution.

Create reqnroll.json in the project root:

{
  "$schema": "https://schemas.reqnroll.net/reqnroll-config-latest.json",
  "bindingAssemblies": [
  ],
  "formatters": {
    "html": {
      "outputFilePath": "report/reqnroll_report.html"
    }
  }
}

6. Execute Your Script Using the NUnit Command

You have two common ways to run your tests: through Visual Studio’s Test Explorer (great during development) and through the NUnit Console Runner (essential for CI/CD pipelines and command-line execution).

Build the project first

This command compiles the automation framework and generates the test assembly in the build output folder.


dotnet build

Terminal output showing a successful dotnet build of the qa-test-flaui project

Run tests with the NUnit Console Runner

After the build completes successfully, navigate to the following directory:


Goto to this folder path: ./bin/Debug/net8.0-windows/
nunit3-console.exe qa-test-flaui.dll

The NUnit Console Runner will:

  • Discover all Reqnroll scenarios.
  • Execute the automation test suite.
  • Display the execution progress in the console.

Terminal output of nunit3-console.exe running the qa-test-flaui.dll test suite with a passed result

After the test execution completes successfully, the framework automatically generates a Reqnroll HTML Report, providing a detailed overview of the execution.

Need a Production-Ready Desktop Automation Framework?

Talk to Our QA Experts

7. Analyze the Test Result


qa-test-flaui/
└── bin/Debug/net8.0-windows/report/
                └── reqnroll_report.html

Open the following file in any web browser to view the execution dashboard:

Reqnroll HTML report dashboard showing 100% passed for the Notepad feature test

Troubleshooting checklist

These are the most common issues teams hit when running this Reqnroll and FlaUI test suite for the first time.

Symptom Likely Cause Fix
ElementNotAvailableException Locator (AutomationId/ControlType) is wrong for your Notepad version Re-inspect with FlaUInspect; Windows 11 Notepad’s control tree differs from older versions
Test hangs indefinitely FlaUI is waiting for a window that never appeared Add explicit Retry.WhileNull(…) waits around GetMainWindow; check the app actually launched
Notepad processes pile up after failed runs AfterScenario hook wasn’t reached due to an unhandled exception before NotepadWindow was stored in ScenarioContext Wrap window launch in a try/catch, or add a BeforeScenario step that kills any lingering notepad.exe processes first
Screenshot file not found in report Path mismatch between TestContext.WorkDirectory and the actual output folder Print ScreenshotDirectory to console at runtime to confirm the exact resolved path
Tests pass locally but fail in CI CI agent runs “headless” / no interactive desktop session Desktop UI Automation requires an interactive session configure your CI agent to run as an interactive service or use a self-hosted agent with a real desktop session

8. Inspecting UI Elements Using FlaUInspect

FlaUInspect is a free Windows UI Automation inspector. Use it to find the AutomationId, Name, ControlType, and ClassName of any element before writing a locator.

8.1 Launching FlaUInspect

Download the latest release from the FlaUInspect GitHub releases page and extract it (no installation required).

Run as Administrator and open FlaUInspect.exe. It opens a window with a tree view on the left and a properties panel on the right.

Step to Use Hover Mode:

  • Click the Hover Mode button in the inspection tool’s toolbar to activate it.
  • Move your mouse cursor over the application window you want to inspect.
  • Press and hold the Ctrl key on your keyboard while keeping the mouse hovered over the specific UI element.

FlaUInspect Mode menu with Hover Mode (use Ctrl) selected

Note: Inspect elements like the Window Title or File Menu button as shown below.

FlaUInspect showing AutomationId, Name, and ControlType details for the Notepad window title

FlaUInspect showing the AutomationId and ClassName details for Notepad's File menu item

Conclusion

This Reqnroll tutorial walked you through building a complete Windows desktop automation framework with FlaUI and NUnit from installing the Reqnroll Visual Studio extension and scaffolding the project, through building a Page Object Model, step definitions, and hooks for Notepad, to running your suite from the command line and reading the generated HTML report. With FlaUInspect in your toolkit for locating elements, you now have everything needed to extend this same pattern to a real, production desktop application.

Frequently Asked Questions

  • What is FlaUI used for?

    FlaUI is an open-source .NET library for automating Windows desktop applications Win32, WinForms, WPF, and UWP apps by wrapping Microsoft's native UI Automation (UIA) framework in a cleaner C# API.

  • Can Selenium automate desktop applications like FlaUI does?

    No. Selenium automates browser-based (DOM) applications; it can't interact with native Windows desktop apps. FlaUI fills that gap by talking directly to Microsoft's UI Automation API instead of a browser DOM.

  • What's the difference between UIA2 and UIA3 in FlaUI?

    UIA2 is the managed .NET implementation, best for older Win32/WinForms apps. UIA3 is the newer COM-based implementation with stronger support for WPF, UWP, and modern controls and is FlaUI's recommended default.

  • How do I find element locators for a desktop app before writing FlaUI code?

    Use FlaUInspect, a free inspection tool from the FlaUI project. Hover over any control while holding Ctrl to see its AutomationId, Name, ControlType, and ClassName the values you'll use in your FlaUI locators.

  • Does FlaUI work with testing frameworks other than Reqnroll and NUnit?

    Yes. FlaUI integrates cleanly with xUnit and MSTest as well, so teams can slot it into whatever test runner and CI/CD pipeline they already use.


Comments(0)

Submit a Comment

Your email address will not be published. Required fields are marked *

Top Picks For you

Talk to our Experts

Amazing clients who
trust us


poloatto
ABB
polaris
ooredo
stryker
mobility