{"id":49697274,"url":"https://github.com/jerrettdavis/flawright","last_synced_at":"2026-05-12T02:03:30.572Z","repository":{"id":356374324,"uuid":"1231156289","full_name":"JerrettDavis/Flawright","owner":"JerrettDavis","description":"A Playwright-flavored API for FlaUI: write fluent Windows desktop UI tests that feel natural and familiar.","archived":false,"fork":false,"pushed_at":"2026-05-08T02:07:49.000Z","size":6961,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-08T02:46:08.329Z","etag":null,"topics":["e2e","e2e-testing","test","test-automation","testing","testing-tools","tests","windows"],"latest_commit_sha":null,"homepage":"https://jerrettdavis.github.io/Flawright/","language":"C#","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/JerrettDavis.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-06T17:26:18.000Z","updated_at":"2026-05-08T02:06:13.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/JerrettDavis/Flawright","commit_stats":null,"previous_names":["jerrettdavis/flawright"],"tags_count":12,"template":false,"template_full_name":null,"purl":"pkg:github/JerrettDavis/Flawright","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JerrettDavis%2FFlawright","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JerrettDavis%2FFlawright/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JerrettDavis%2FFlawright/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JerrettDavis%2FFlawright/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/JerrettDavis","download_url":"https://codeload.github.com/JerrettDavis/Flawright/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/JerrettDavis%2FFlawright/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32920399,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-11T17:09:15.040Z","status":"online","status_checked_at":"2026-05-12T02:00:06.338Z","response_time":102,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["e2e","e2e-testing","test","test-automation","testing","testing-tools","tests","windows"],"created_at":"2026-05-08T02:32:59.030Z","updated_at":"2026-05-12T02:03:30.566Z","avatar_url":"https://github.com/JerrettDavis.png","language":"C#","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Flawright\r\n\r\nA Playwright-flavored API for FlaUI: write Windows desktop UI tests that read like web tests.\r\n\r\n[![CI](https://github.com/JerrettDavis/Flawright/actions/workflows/ci.yml/badge.svg)](https://github.com/JerrettDavis/Flawright/actions/workflows/ci.yml)\r\n[![NuGet](https://img.shields.io/nuget/v/Flawright.svg)](https://www.nuget.org/packages/Flawright)\r\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)\r\n[![.NET](https://img.shields.io/badge/.NET-10.0-512bd4)](https://dotnet.microsoft.com)\r\n\r\n## Why Flawright?\r\n\r\nRaw FlaUI gets the job done, but every test is a wall of boilerplate:\r\n\r\n- **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.\r\n- **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.\r\n- **Selector strings** keep tests decoupled from the UI tree. `page.Locator(\"name:Save\")` reads at a glance; `_cf.ByName(\"Save\", PropertyConditionFlags.None)` does not.\r\n- **Async throughout.** Every action returns a `Task`, composing naturally with `async`/`await` test frameworks.\r\n- **Auto-waiting.** Locator operations retry with a configurable polling interval until the element appears or the timeout expires — no manual `Task.Delay` loops needed.\r\n- **No plumbing to own.** `Flawright.LaunchAsync(options)` — one call and you are writing assertions, not scaffolding.\r\n\r\n## Install\r\n\r\n```bash\r\ndotnet add package Flawright\r\n```\r\n\r\n**Prerequisites**\r\n\r\n- Windows 10 or later (UI Automation requires a desktop session)\r\n- .NET 10.0\r\n- The target application must be accessible via UI Automation (UIA3). Use [Accessibility Insights](https://accessibilityinsights.io/) or the built-in `inspect.exe` to verify.\r\n\r\n## Quickstart\r\n\r\nLaunch Notepad, type some text, and assert it landed:\r\n\r\n```csharp\r\nusing JerrettDavis.Flawright;\r\n\r\nawait using var fw = await Flawright.LaunchAsync(new LaunchOptions\r\n{\r\n    ApplicationPath = \"notepad.exe\"   // auto-resolves to AUMID on Windows 11\r\n});\r\n\r\nvar page = await fw.Browser.NewPageAsync();\r\n\r\n// Fill the editor — use the AutomationId of the editor control\r\nawait page.FillAsync(\"#RichEditBox\", \"Hello from Flawright!\");\r\n\r\n// Assert the text is present\r\nawait page.Locator(\"#RichEditBox\").Expect().ToBeVisibleAsync();\r\n\r\n// Take a screenshot — returns PNG bytes; also saves to file if path is given\r\nbyte[] png = await page.ScreenshotAsync(@\"C:\\temp\\notepad.png\");\r\n```\r\n\r\n\u003e **Windows 10 vs Windows 11 Notepad**\r\n\u003e\r\n\u003e Different Windows versions ship different Notepad implementations:\r\n\u003e\r\n\u003e - **Windows 11 Notepad** (WinUI3, packaged app): editor `AutomationId` is `RichEditBox` — use `#RichEditBox`.\r\n\u003e - **Classic Windows 10 Notepad** (Win32): editor `ControlType` is `Edit` — use `controltype:Edit`.\r\n\u003e\r\n\u003e When in doubt, inspect the live UI tree with [Accessibility Insights for Windows](https://accessibilityinsights.io/) or [FlaUI Inspect](https://github.com/FlaUI/FlaUI/wiki/Tools#flauiinspect) to find the right selector for your system.\r\n\u003e\r\n\u003e On Windows 11, `ApplicationPath = \"notepad.exe\"` is automatically redirected to\r\n\u003e `Application.LaunchStoreApp(\"Microsoft.WindowsNotepad_8wekyb3d8bbwe!App\")` so FlaUI\r\n\u003e binds to the real packaged app instead of the short-lived alias stub.\r\n\r\nTo attach to an already-running process instead of launching a new one:\r\n\r\n```csharp\r\nawait using var fw = await Flawright.AttachAsync(new AttachOptions\r\n{\r\n    ProcessId = 12345\r\n});\r\n```\r\n\r\n## Selector Syntax\r\n\r\nSelectors 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.\r\n\r\n| Prefix | Matches | Example |\r\n|---|---|---|\r\n| *(no prefix)* | UIA Name (smart fallback) | `\"Save\"` |\r\n| `text:` | UIA Name property | `\"text:Save\"` |\r\n| `name:` | UIA Name property (alias for `text:`) | `\"name:Save\"` |\r\n| `#` | AutomationId (CSS shorthand) | `\"#btn_save\"` |\r\n| `automationid:` | AutomationId (explicit form) | `\"automationid:btn_save\"` |\r\n| `class:` or `[class=...]` | ClassName | `\"class:Button\"` |\r\n| `role:` or `[role=...]` | UIA ControlType | `\"role:Button\"` |\r\n| `controltype:` | UIA ControlType (alias for `role:`) | `\"controltype:Edit\"` |\r\n| `[name=...]` | UIA Name (attribute syntax) | `\"[name=OK]\"` |\r\n\r\n**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`.\r\n\r\nAny unrecognized value maps to `ControlType.Custom`.\r\n\r\n## API Overview\r\n\r\n### `Flawright` — entry point\r\n\r\nCall `Flawright.LaunchAsync` (static, one step) to launch an application. The returned `Flawright` instance owns the browser and disposes it on `DisposeAsync`.\r\n\r\n```csharp\r\nawait using var fw = await Flawright.LaunchAsync(new LaunchOptions\r\n{\r\n    ApplicationPath = \"notepad.exe\"\r\n});\r\n```\r\n\r\nTo customize timeouts and screenshot output:\r\n\r\n```csharp\r\nawait using var fw = await Flawright.LaunchAsync(\r\n    new LaunchOptions { ApplicationPath = \"notepad.exe\" },\r\n    new FlawrightOptions\r\n    {\r\n        DefaultTimeout      = TimeSpan.FromSeconds(10),\r\n        DefaultRetryInterval = TimeSpan.FromMilliseconds(50),\r\n        ScreenshotDirectory  = @\"C:\\TestOutput\"\r\n    });\r\n```\r\n\r\n### `IFlawrightBrowser` — the application\r\n\r\nWraps 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.\r\n\r\n```csharp\r\nvar page = await fw.Browser.NewPageAsync();\r\n\r\n// All top-level windows\r\nvar pages = await fw.Browser.GetAllPagesAsync();\r\n\r\n// Wait for a specific window to appear\r\nvar dialog = await fw.Browser.WaitForPageAsync(\"Save As\", timeout: TimeSpan.FromSeconds(10));\r\n```\r\n\r\n### `IFlawrightPage` — a window\r\n\r\nCorresponds to a top-level window. The primary surface for interacting with the application.\r\n\r\n```csharp\r\n// Click a button by name\r\nawait page.ClickAsync(\"name:OK\");\r\n\r\n// Fill a text box via ValuePattern (fast, single shot)\r\nawait page.FillAsync(\"controltype:Edit\", \"some text\");\r\n\r\n// Type character-by-character (realistic key events for reactive controls)\r\nawait page.TypeAsync(\"controltype:Edit\", \"hello\");\r\n\r\n// Press a key or chord\r\nawait page.PressAsync(\"controltype:Edit\", \"Ctrl+S\");\r\n\r\n// Check / uncheck a toggle\r\nawait page.CheckAsync(\"controltype:CheckBox\");\r\nawait page.UncheckAsync(\"controltype:CheckBox\");\r\n\r\n// Select a combo box option by value\r\nawait page.SelectOptionAsync(\"controltype:ComboBox\", \"Option A\");\r\n\r\n// Wait for an element to appear and return it\r\nvar el = await page.WaitForSelectorAsync(\"name:Loading Complete\");\r\n\r\n// Create a locator for chaining\r\nvar locator = page.Locator(\"#username\");\r\n\r\n// Window title\r\nvar title = await page.TitleAsync();\r\n```\r\n\r\n### `IFlawrightLocator` — a lazy element query\r\n\r\nA 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.\r\n\r\n```csharp\r\nusing JerrettDavis.Flawright.Locator; // for LocatorFilterOptions\r\n\r\nvar saveButton = page.Locator(\"name:Save\");\r\n\r\n// Resolves the element (auto-waited) and clicks it\r\nawait saveButton.ClickAsync();\r\n\r\n// Get the first match (sync — returns a new locator narrowed to the first element)\r\nvar firstLocator = saveButton.First;\r\n\r\n// Count matching elements (no wait — returns current count)\r\nvar count = await page.Locator(\"controltype:Button\").CountAsync();\r\n\r\n// Get the nth match (0-indexed), sync — returns a new narrowed locator\r\nvar second = page.Locator(\"controltype:ListItem\").Nth(1);\r\n\r\n// Get all matching elements (auto-waits for at least one)\r\nvar all = await page.Locator(\"controltype:ListItem\").AllAsync();\r\n\r\n// Filter locator results by text content\r\nvar filtered = page.Locator(\"controltype:ListItem\")\r\n    .Filter(new LocatorFilterOptions { HasText = \"Save\" });\r\n\r\n// Enter the assertion chain\r\nawait saveButton.Expect().ToBeEnabledAsync();\r\n```\r\n\r\n### `IFlawrightElement` — a resolved element\r\n\r\nReturned by `AllAsync` (or via `ElementHandleAsync` for advanced use). Exposes actions on the concrete UIA element.\r\n\r\n```csharp\r\n// Click\r\nawait element.ClickAsync();\r\n\r\n// Double-click\r\nawait element.DoubleClickAsync();\r\n\r\n// Fill (ValuePattern — fast value set)\r\nawait element.FillAsync(\"new value\");\r\n\r\n// Read text (ValuePattern → TextPattern → Name fallback)\r\nvar text = await element.TextAsync();\r\n\r\n// State checks\r\nbool visible = await element.IsVisibleAsync();\r\nbool enabled = await element.IsEnabledAsync();\r\nbool checked_ = await element.IsCheckedAsync();\r\n\r\n// Mouse / focus\r\nawait element.HoverAsync();\r\nawait element.FocusAsync();\r\nawait element.ScrollIntoViewIfNeededAsync();\r\n\r\n// Read a UIA attribute by name\r\nvar id = await element.GetAttributeAsync(\"AutomationId\");\r\n```\r\n\r\n### `IFlawrightAssertions` — expect chain\r\n\r\nReturned by `locator.Expect()`. All methods auto-wait and throw `FlawrightTimeoutException` on timeout.\r\n\r\n```csharp\r\n// Visibility\r\nawait page.Locator(\"name:Submit\").Expect().ToBeVisibleAsync();\r\nawait page.Locator(\"name:Hidden\").Expect().ToBeHiddenAsync();\r\n\r\n// Enabled state\r\nawait page.Locator(\"controltype:Button\").Expect().ToBeEnabledAsync();\r\nawait page.Locator(\"controltype:Button\").Expect().ToBeDisabledAsync();\r\n\r\n// Text content\r\nawait page.Locator(\"name:Result\").Expect().ToHaveTextAsync(\"42\");\r\n\r\n// ValuePattern value (edit controls)\r\nawait page.Locator(\"controltype:Edit\").Expect().ToHaveValueAsync(\"hello\");\r\n\r\n// Toggle / checkbox state\r\nawait page.Locator(\"controltype:CheckBox\").Expect().ToBeCheckedAsync();\r\n\r\n// Count\r\nawait page.Locator(\"controltype:ListItem\").Expect().ToHaveCountAsync(5);\r\n\r\n// Negation — each positive assertion has a .Not counterpart\r\nawait page.Locator(\"name:Spinner\").Expect().Not.ToBeVisibleAsync();\r\nawait page.Locator(\"controltype:Button\").Expect().Not.ToBeDisabledAsync();\r\n```\r\n\r\n### Screenshots\r\n\r\n```csharp\r\n// Returns PNG bytes only\r\nbyte[] png = await page.ScreenshotAsync();\r\n\r\n// Saves to a file and returns the bytes\r\nbyte[] png = await page.ScreenshotAsync(@\"C:\\temp\\screenshot.png\");\r\n\r\n// Auto-saves to FlawrightOptions.ScreenshotDirectory when no path is given\r\n// and ScreenshotDirectory is configured\r\n```\r\n\r\n### Multi-window\r\n\r\n```csharp\r\n// Main window\r\nvar page = await fw.Browser.NewPageAsync();\r\n\r\n// All current top-level windows\r\nvar pages = await fw.Browser.GetAllPagesAsync();\r\n\r\n// Wait for a dialog/window to appear by title substring\r\nvar saveDialog = await fw.Browser.WaitForPageAsync(\"Save As\");\r\n```\r\n\r\n## Differences from Playwright (web)\r\n\r\n| Concept | Playwright (web) | Flawright (desktop) |\r\n|---|---|---|\r\n| Browser | Chromium / Firefox / WebKit | A Windows process (EXE) |\r\n| Page | Browser tab or window | Top-level Window (UIA `Window`) |\r\n| Locator | CSS / XPath / ARIA roles | UIA properties (Name, AutomationId, ControlType) |\r\n| Selector syntax | `#id`, `.class`, `role=button` | `#id`, `name:`, `controltype:`, `text:`, `class:`, `role:` |\r\n| Headless mode | Supported | Not applicable — requires a display |\r\n| Platform | Cross-platform | Windows only |\r\n| Networking | Intercept, mock, HAR | Not applicable |\r\n| JavaScript | `page.evaluate()` | Not applicable |\r\n\r\n## Comparison to Raw FlaUI\r\n\r\nThe same task — clicking the \"OK\" button in a dialog — raw FlaUI vs. Flawright:\r\n\r\n**Raw FlaUI**\r\n\r\n```csharp\r\nusing FlaUI.Core;\r\nusing FlaUI.Core.AutomationElements;\r\nusing FlaUI.Core.Conditions;\r\nusing FlaUI.UIA3;\r\n\r\nvar app = Application.Launch(\"myapp.exe\");\r\nusing var automation = new UIA3Automation();\r\nvar window = app.GetMainWindow(automation);\r\n\r\nvar cf = new ConditionFactory(automation.PropertyLibrary);\r\nvar button = window.FindFirstDescendant(cf.ByName(\"OK\"));\r\nif (button == null)\r\n    throw new Exception(\"OK button not found\");\r\n\r\nbutton.AsButton().Invoke();\r\n```\r\n\r\n**Flawright**\r\n\r\n```csharp\r\nusing JerrettDavis.Flawright;\r\n\r\nawait using var fw = await Flawright.LaunchAsync(new LaunchOptions { ApplicationPath = \"myapp.exe\" });\r\nvar page = await fw.Browser.NewPageAsync();\r\n\r\nawait page.ClickAsync(\"name:OK\");\r\n```\r\n\r\n## Project Layout\r\n\r\n```\r\nFlawright/\r\n├── src/\r\n│   └── JerrettDavis.Flawright/          # Library source\r\n│       ├── Flawright.cs                  # Entry point (LaunchAsync / AttachAsync)\r\n│       ├── FlawrightBrowser.cs           # Application wrapper (IFlawrightBrowser)\r\n│       ├── FlawrightPage.cs              # Window wrapper (IFlawrightPage)\r\n│       ├── FlawrightLocator.cs           # Lazy element query (IFlawrightLocator)\r\n│       ├── FlawrightElement.cs           # Resolved element (IFlawrightElement)\r\n│       ├── FlawrightAssertions.cs        # Assertion chain (IFlawrightAssertions)\r\n│       ├── FlawrightOptions.cs           # Global options (timeout, retry, screenshot dir)\r\n│       ├── FlawrightTimeoutException.cs  # Timeout exception\r\n│       ├── Interfaces.cs                 # All public interfaces\r\n│       ├── AutoWait.cs                   # Internal polling loop\r\n│       ├── Selectors/SelectorParser.cs   # Selector string → FlaUI condition\r\n│       └── Input/KeyParser.cs            # Key/chord string → FlaUI keyboard input\r\n├── tests/\r\n│   ├── JerrettDavis.Flawright.UnitTests/ # Unit tests (SelectorParser, KeyParser, AutoWait)\r\n│   └── JerrettDavis.Flawright.E2ETests/  # E2E tests (Notepad, Calculator)\r\n└── docs/                                 # Extended documentation\r\n```\r\n\r\n## Contributing\r\n\r\nSee [CONTRIBUTING.md](CONTRIBUTING.md).\r\n\r\n## License\r\n\r\n[MIT](LICENSE)\r\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjerrettdavis%2Fflawright","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjerrettdavis%2Fflawright","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjerrettdavis%2Fflawright/lists"}