Flawright 0.5.73

dotnet add package Flawright --version 0.5.73
                    
NuGet\Install-Package Flawright -Version 0.5.73
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Flawright" Version="0.5.73" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Flawright" Version="0.5.73" />
                    
Directory.Packages.props
<PackageReference Include="Flawright" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Flawright --version 0.5.73
                    
#r "nuget: Flawright, 0.5.73"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Flawright@0.5.73
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Flawright&version=0.5.73
                    
Install as a Cake Addin
#tool nuget:?package=Flawright&version=0.5.73
                    
Install as a Cake Tool

Flawright

A Playwright-flavored API for FlaUI: write Windows desktop UI tests that read like web tests.

NuGet Downloads CI CodeQL codecov License: MIT .NET 10.0 Documentation

Why Flawright?

Raw FlaUI gets the job done, but every test is a wall of boilerplate:

  • Raw FlaUI requires manually building ConditionFactory, calling FindFirstDescendant, casting to the right pattern, and guarding every call against null — before you've clicked a single button.
  • Playwright proved that a fluent locator API with auto-waiting produces tests that are shorter, more readable, and less flaky. Flawright brings that model to Windows desktop automation.
  • Selector strings keep tests decoupled from the UI tree. page.Locator("name:Save") reads at a glance; _cf.ByName("Save", PropertyConditionFlags.None) does not.
  • Async throughout. Every action returns a Task, composing naturally with async/await test frameworks.
  • Auto-waiting. Locator operations retry with a configurable polling interval until the element appears or the timeout expires — no manual Task.Delay loops needed.
  • No plumbing to own. Flawright.LaunchAsync(options) — one call and you are writing assertions, not scaffolding.

Install

dotnet add package Flawright

Prerequisites

  • Windows 10 or later (UI Automation requires a desktop session)
  • .NET 10.0
  • The target application must be accessible via UI Automation (UIA3). Use Accessibility Insights or the built-in inspect.exe to verify.

Quickstart

Launch Notepad, type some text, and assert it landed. The correct selector depends on which Notepad your system ships — pick the block that matches:

Windows 11 Notepad (WinUI3 packaged app — default on Win11):

using Flawright;
using Flawright.CloseBehaviors;

// Configure how the app closes — Notepad shows a save-changes dialog,
// so opt into dialog-dismissing close behavior.
await using var fw = await Flawright.LaunchAsync(
    new LaunchOptions { ApplicationPath = "notepad.exe" },
    new FlawrightOptions
    {
        CloseBehavior = new DismissDialogCloseBehavior() // defaults handle Win10 + Win11 Notepad
    });

var page = await fw.Browser.NewPageAsync();

// Win11: editor AutomationId is "RichEditBox"
await page.FillAsync("#RichEditBox", "Hello from Flawright!");
await page.Locator("#RichEditBox").Expect().ToBeVisibleAsync();

byte[] png = await page.ScreenshotAsync(@"C:\temp\notepad.png");

// Runs the configured behavior — transparent, no hidden magic
await fw.Browser.CloseAsync();

Classic Windows 10 Notepad (Win32 — also present on some Win11 installs):

using Flawright;
using Flawright.CloseBehaviors;

await using var fw = await Flawright.LaunchAsync(
    new LaunchOptions { ApplicationPath = "notepad.exe" },
    new FlawrightOptions
    {
        CloseBehavior = new DismissDialogCloseBehavior() // defaults handle Win10 + Win11 Notepad
    });

var page = await fw.Browser.NewPageAsync();

// Win10 classic: Win32 ClassName is "Edit" — use class: selector
await page.FillAsync("class:Edit", "Hello from Flawright!");
await page.Locator("class:Edit").Expect().ToBeVisibleAsync();

byte[] png = await page.ScreenshotAsync(@"C:\temp\notepad.png");

await fw.Browser.CloseAsync();

Windows 10 vs Windows 11 Notepad

Different Windows versions ship different Notepad implementations:

  • Windows 11 Notepad (WinUI3, packaged app): editor AutomationId is RichEditBox — use #RichEditBox.
  • Classic Windows 10 Notepad (Win32): editor Win32 ClassName is Edit — use class:Edit. Note that UIA promotes this multi-line edit to ControlType.Document, so controltype:Edit does not match it; class:Edit (Win32 class) or controltype:Document (UIA type) both work.

When in doubt, inspect the live UI tree with Accessibility Insights for Windows or FlaUI Inspect to find the right selector for your system.

App Execution Alias detection (Windows 11)

On Windows 11, several inbox apps (notepad.exe, calc.exe, mspaint.exe) are packaged WinUI 3 applications whose command-line entries are App Execution Alias stubs — 0-byte reparse points under %LOCALAPPDATA%\Microsoft\WindowsApps\. When launched via Process.Start, the stub exits immediately after activating the real packaged app, so FlaUI would track a dead PID.

Flawright detects this automatically. When ApplicationPath resolves to a known alias stub (or the alias stub is absent but the corresponding package is installed), Flawright internally calls Application.LaunchStoreApp(aumid) — binding FlaUI to the real packaged app process — instead of Application.Launch. No change to your test code is required; ApplicationPath = "notepad.exe" simply works on both Windows 10 and Windows 11.

The detection uses two tiers:

  1. File check — if %LOCALAPPDATA%\Microsoft\WindowsApps\<name>.exe exists, the alias stub is present.
  2. Registry probe — if the stub is absent (some Win11 builds ship calc.exe as a System32 redirect), the per-user package registry (HKCU\...\AppModel\Repository\Packages) is checked; if the package is installed, the AUMID is used.

To override the resolver (e.g. to inject a fake in unit tests, or to add an app not in the built-in table), set LaunchOptions.AumidResolver:

var fake = new FakeAumidResolver();
fake.RegisterAumid("myapp.exe", "Contoso.MyApp_abc123!App");
var opts = new LaunchOptions { ApplicationPath = "myapp.exe", AumidResolver = fake };

To attach to an already-running process instead of launching a new one:

await using var fw = await Flawright.AttachAsync(new AttachOptions
{
    ProcessId = 12345
});

Selector Syntax

Selectors are strings with an optional prefix: followed by a value. Without a prefix, the string is treated as a name: match against the element's UIA Name property.

Prefix Matches Example
(no prefix) UIA Name (smart fallback) "Save"
text: UIA Name property "text:Save"
name: UIA Name property (alias for text:) "name:Save"
# AutomationId (CSS shorthand) "#btn_save"
automationid: AutomationId (explicit form) "automationid:btn_save"
class: or [class=...] ClassName "class:Button"
role: or [role=...] UIA ControlType "role:Button"
controltype: UIA ControlType (alias for role:) "controltype:Button"
[name=...] UIA Name (attribute syntax) "[name=OK]"

Supported control type values for controltype: / role:: button, checkbox, combobox, dropdown, edit, textbox, input, list, listitem, menu, menubar, menuitem, radiobutton, tab, tabitem, text, label, window, group, image, link, hyperlink, progressbar, scrollbar, slider, spinner, statusbar, table, toolbar, tooltip, tree, treeitem, separator, pane, document, header, headeritem.

Any unrecognized value throws ArgumentException with a message listing the valid options.

API Overview

Flawright — entry point

Call Flawright.LaunchAsync (static, one step) to launch an application. The returned Flawright instance owns the browser and disposes it on DisposeAsync.

await using var fw = await Flawright.LaunchAsync(new LaunchOptions
{
    ApplicationPath = "notepad.exe"
});

To customize timeouts and screenshot output:

await using var fw = await Flawright.LaunchAsync(
    new LaunchOptions { ApplicationPath = "notepad.exe" },
    new FlawrightOptions
    {
        DefaultTimeout      = TimeSpan.FromSeconds(10),
        DefaultRetryInterval = TimeSpan.FromMilliseconds(50),
        ScreenshotDirectory  = @"C:\TestOutput"
    });

IFlawrightBrowser — the application

Wraps a running process. Access via fw.Browser. Call NewPageAsync to get the main window, GetAllPagesAsync to enumerate all top-level windows, or WaitForPageAsync to wait for a window by title.

var page = await fw.Browser.NewPageAsync();

// All top-level windows
var pages = await fw.Browser.GetAllPagesAsync();

// Wait for a specific window to appear
var dialog = await fw.Browser.WaitForPageAsync("Save As", timeout: TimeSpan.FromSeconds(10));

IFlawrightPage — a window

Corresponds to a top-level window. The primary surface for interacting with the application.

Examples below use controltype:Edit as a stand-in for "an editable text control". For a real app, pick the selector that matches your target — see the Quickstart above for the Win10/Win11 Notepad differences.

// Click a button by name
await page.ClickAsync("name:OK");

// Fill a text box via ValuePattern (fast, single shot)
await page.FillAsync("controltype:Edit", "some text");

// Type character-by-character (realistic key events for reactive controls)
await page.TypeAsync("controltype:Edit", "hello");

// Press a key or chord
await page.PressAsync("controltype:Edit", "Ctrl+S");

// Check / uncheck a toggle
await page.CheckAsync("controltype:CheckBox");
await page.UncheckAsync("controltype:CheckBox");

// Select a combo box option by value
await page.SelectOptionAsync("controltype:ComboBox", "Option A");

// Wait for an element to appear and return it
var el = await page.WaitForSelectorAsync("name:Loading Complete");

// Create a locator for chaining
var locator = page.Locator("#username");

// Window title
var title = await page.TitleAsync();

IFlawrightLocator — a lazy element query

A locator is a reusable description of how to find an element. It does not execute until you call an action on it. This lets you define locators once and assert them multiple times. All resolution is auto-waited.

using Flawright.Locator; // for LocatorFilterOptions

var saveButton = page.Locator("name:Save");

// Resolves the element (auto-waited) and clicks it
await saveButton.ClickAsync();

// Get the first match (sync — returns a new locator narrowed to the first element)
var firstLocator = saveButton.First;

// Count matching elements (no wait — returns current count)
var count = await page.Locator("controltype:Button").CountAsync();

// Get the nth match (0-indexed), sync — returns a new narrowed locator
var second = page.Locator("controltype:ListItem").Nth(1);

// Get all matching elements (auto-waits for at least one)
var all = await page.Locator("controltype:ListItem").AllAsync();

// Filter locator results by text content
var filtered = page.Locator("controltype:ListItem")
    .Filter(new LocatorFilterOptions { HasText = "Save" });

// Enter the assertion chain
await saveButton.Expect().ToBeEnabledAsync();

IFlawrightElement — a resolved element

Returned by AllAsync (or via ElementHandleAsync for advanced use). Exposes actions on the concrete UIA element.

// Click
await element.ClickAsync();

// Double-click
await element.DoubleClickAsync();

// Fill (ValuePattern — fast value set)
await element.FillAsync("new value");

// Read text (ValuePattern → TextPattern → Name fallback)
var text = await element.TextAsync();

// State checks
bool visible = await element.IsVisibleAsync();
bool enabled = await element.IsEnabledAsync();
bool checked_ = await element.IsCheckedAsync();

// Mouse / focus
await element.HoverAsync();
await element.FocusAsync();
await element.ScrollIntoViewIfNeededAsync();

// Read a UIA attribute by name
var id = await element.GetAttributeAsync("AutomationId");

IFlawrightAssertions — expect chain

Returned by locator.Expect(). All methods auto-wait and throw FlawrightTimeoutException on timeout.

// Visibility
await page.Locator("name:Submit").Expect().ToBeVisibleAsync();
await page.Locator("name:Hidden").Expect().ToBeHiddenAsync();

// Enabled state
await page.Locator("controltype:Button").Expect().ToBeEnabledAsync();
await page.Locator("controltype:Button").Expect().ToBeDisabledAsync();

// Text content
await page.Locator("name:Result").Expect().ToHaveTextAsync("42");

// ValuePattern value (edit controls)
await page.Locator("controltype:Edit").Expect().ToHaveValueAsync("hello");

// Toggle / checkbox state
await page.Locator("controltype:CheckBox").Expect().ToBeCheckedAsync();

// Count
await page.Locator("controltype:ListItem").Expect().ToHaveCountAsync(5);

// Negation — each positive assertion has a .Not counterpart
await page.Locator("name:Spinner").Expect().Not.ToBeVisibleAsync();
await page.Locator("controltype:Button").Expect().Not.ToBeDisabledAsync();

Screenshots

// Returns PNG bytes only
byte[] png = await page.ScreenshotAsync();

// Saves to a file and returns the bytes
byte[] png = await page.ScreenshotAsync(@"C:\temp\screenshot.png");

// Auto-saves to FlawrightOptions.ScreenshotDirectory when no path is given
// and ScreenshotDirectory is configured

Multi-window

// Main window
var page = await fw.Browser.NewPageAsync();

// All current top-level windows
var pages = await fw.Browser.GetAllPagesAsync();

// Wait for a dialog/window to appear by title substring
var saveDialog = await fw.Browser.WaitForPageAsync("Save As");

App close behavior

IFlawrightBrowser.CloseAsync() delegates to an ICloseBehavior configured on FlawrightOptions. Four built-in behaviors ship out of the box:

Behavior What it does
WindowMessageCloseBehavior Sends WM_CLOSE and waits for exit. Default.
DismissDialogCloseBehavior Sends WM_CLOSE, then polls for a named button (e.g. "Don't Save") and clicks it.
KillCloseBehavior Force-kills the process tree immediately.
CompositeCloseBehavior Runs behaviors in sequence; stops at the first that succeeds.

Configure at launch time:

using Flawright.CloseBehaviors;

// Notepad shows a save-changes dialog — use the dialog-dismissing behavior.
var options = new FlawrightOptions
{
    CloseBehavior = new DismissDialogCloseBehavior() // handles Win10 + Win11 Notepad by default
};

await using var fw = await Flawright.LaunchAsync(
    new LaunchOptions { ApplicationPath = "notepad.exe" },
    options);

// ... test work ...

await fw.Browser.CloseAsync(); // transparent: runs the configured behavior

Implement ICloseBehavior for any app-specific logic:

// Example: close via File > Quit menu rather than WM_CLOSE
public sealed class QuitMenuCloseBehavior : ICloseBehavior
{
    public async Task<bool> CloseAsync(ICloseContext context)
    {
        var page = await context.Browser.NewPageAsync();
        await page.ClickAsync("name:File");
        await page.ClickAsync("name:Quit");
        return await context.WaitForExitAsync(context.Timeout);
    }
}

Use CompositeCloseBehavior to chain behaviors — for example, try graceful close first, fall back to kill:

var options = new FlawrightOptions
{
    CloseBehavior = new CompositeCloseBehavior(
        new DismissDialogCloseBehavior(),
        new KillCloseBehavior())
};

Input mode

IFlawrightLocator and IFlawrightElement actions (clicks, typing, key presses, hover, drag) can operate in two modes configured on FlawrightOptions:

Mode Focus-steal Cursor Concurrent tests Unsupported actions
RealInputMode Yes Moves No None
VirtualInputMode No Stationary Yes Hover, drag, double-click, key chords

RealInputMode (default) sends real OS-level mouse and keyboard input via Win32 SendInput. Matches a user driving the application manually. Required for any action that depends on OS-level focus or cursor position.

VirtualInputMode drives the application via UIA patterns (InvokePattern, ValuePattern, etc.) — no focus-steal, no cursor movement. Recommended for CI runs and bulk test suites. Actions without a UIA equivalent throw NotSupportedException with an actionable message.

using Flawright.InputModes;

var options = new FlawrightOptions
{
    InputMode = new VirtualInputMode() // no focus-steal, supports concurrent tests
};

await using var fw = await Flawright.LaunchAsync(
    new LaunchOptions { ApplicationPath = "myapp.exe" },
    options);

Implement IInputMode to define custom input dispatch — for example, routing actions through accessibility APIs specific to your app framework.

Differences from Playwright (web)

Concept Playwright (web) Flawright (desktop)
Browser Chromium / Firefox / WebKit A Windows process (EXE)
Page Browser tab or window Top-level Window (UIA Window)
Locator CSS / XPath / ARIA roles UIA properties (Name, AutomationId, ControlType)
Selector syntax #id, .class, role=button #id, name:, controltype:, text:, class:, role:
Headless mode Supported Not applicable — requires a display
Platform Cross-platform Windows only
Networking Intercept, mock, HAR Not applicable
JavaScript page.evaluate() Not applicable

Comparison to Raw FlaUI

The same task — clicking the "OK" button in a dialog — raw FlaUI vs. Flawright:

Raw FlaUI

using FlaUI.Core;
using FlaUI.Core.AutomationElements;
using FlaUI.Core.Conditions;
using FlaUI.UIA3;

var app = Application.Launch("myapp.exe");
using var automation = new UIA3Automation();
var window = app.GetMainWindow(automation);

var cf = new ConditionFactory(automation.PropertyLibrary);
var button = window.FindFirstDescendant(cf.ByName("OK"));
if (button == null)
    throw new Exception("OK button not found");

button.AsButton().Invoke();

Flawright

using Flawright;

await using var fw = await Flawright.LaunchAsync(new LaunchOptions { ApplicationPath = "myapp.exe" });
var page = await fw.Browser.NewPageAsync();

await page.ClickAsync("name:OK");

Behavior-driven testing with Reqnroll

Flawright ships a companion package Flawright.Reqnroll for writing Gherkin/BDD tests:

dotnet add package Flawright.Reqnroll
@launch:notepad.exe
Scenario: Type and verify text in Notepad (Windows 11)
    Given I have the application in focus
    When I fill "[name=\"Text editor\"]" with "Hello from Flawright!"
    Then "[name=\"Text editor\"]" should contain "Hello"

The name:"Text editor" selector matches the Win11 packaged Notepad, where the UIA Name property of the editor is "Text editor". On classic Win10 Notepad the textarea has no UIA Name — use class:Edit instead (e.g. When I fill "class:Edit" with "Hello from Flawright!").

See docs/bdd.md for the full step reference, tag forms, and DI patterns.

Documentation

Full documentation site: jerrettdavis.github.io/Flawright

Project Layout

Flawright/
├── src/
│   ├── Flawright/          # Core library
│   │   ├── Flawright.cs                  # Entry point (LaunchAsync / AttachAsync)
│   │   ├── FlawrightBrowser.cs           # Application wrapper (IFlawrightBrowser)
│   │   ├── FlawrightPage.cs              # Window wrapper (IFlawrightPage)
│   │   ├── FlawrightLocator.cs           # Lazy element query (IFlawrightLocator)
│   │   ├── FlawrightElement.cs           # Resolved element (IFlawrightElement)
│   │   ├── FlawrightAssertions.cs        # Assertion chain (IFlawrightAssertions)
│   │   ├── FlawrightOptions.cs           # Global options (timeout, retry, screenshot dir)
│   │   ├── FlawrightTimeoutException.cs  # Timeout exception
│   │   ├── Interfaces.cs                 # All public interfaces
│   │   ├── AutoWait.cs                   # Internal polling loop
│   │   ├── Selectors/SelectorParser.cs   # Selector string → FlaUI condition
│   │   └── Input/KeyParser.cs            # Key/chord string → FlaUI keyboard input
│   └── Flawright.Reqnroll/  # BDD companion package
│       ├── FlawrightReqnrollHooks.cs     # BeforeScenario / AfterScenario lifecycle
│       ├── FlawrightReqnrollOptions.cs   # Global BDD options (default app path, timeout)
│       ├── FlawrightSteps.cs             # 25 built-in step bindings
│       └── TagParser.cs                  # @launch / @aumid / @attach tag parsing
├── samples/
│   ├── Flawright.Reqnroll.NotepadDemo/   # Gherkin-driven Notepad tests
│   └── Flawright.Reqnroll.CalculatorDemo/# Gherkin-driven Calculator tests
├── tests/
│   ├── Flawright.UnitTests/ # Unit tests (SelectorParser, KeyParser, AutoWait)
│   └── Flawright.E2ETests/  # E2E tests (Notepad, Calculator)
└── docs/                                 # Extended documentation

Contributing

See CONTRIBUTING.md.

License

MIT

Product Compatible and additional computed target framework versions.
.NET net10.0-windows7.0 is compatible. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (1)

Showing the top 1 NuGet packages that depend on Flawright:

Package Downloads
Flawright.Reqnroll

Reqnroll (BDD/Gherkin) step bindings and DI wiring for Flawright Windows desktop UI automation.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
0.5.73 76 5/14/2026
0.5.72 72 5/14/2026
0.5.66 68 5/14/2026
0.5.64 185 5/12/2026
0.5.63 85 5/12/2026
0.5.62 91 5/12/2026
0.5.61 81 5/12/2026
0.5.60 81 5/12/2026
0.5.59 88 5/12/2026
0.5.58 86 5/12/2026
0.5.57 89 5/12/2026
0.5.56 84 5/12/2026
0.5.55 95 5/12/2026
0.5.54 94 5/12/2026
0.5.53 82 5/12/2026
0.5.52 93 5/12/2026
0.5.51 90 5/12/2026
0.5.50 94 5/12/2026
0.5.49 78 5/12/2026
0.5.48 76 5/12/2026
Loading failed