UI test automation tools have a 25-variant hole
Every 2026 listicle of "ui test automation tools" ranks the same shapes. Browser runners (Playwright, Cypress, Selenium, TestCafe, WebdriverIO). AI-native browser platforms (Virtuoso, Mabl, Testim, Functionize, TestSprite, Ghost Inspector). And the paid desktop giants that still sell record-and-replay as the core UX (Ranorex, TestComplete). None of them is a free, code-first framework that treats native desktop apps as first-class with a proper selector language. That tool exists. It is called Terminator, and this page is about the one file that defines what makes it different: a 25-variant Selector enum, parsed with a real Shunting Yard, that points at any UI on your machine.
What the listicles actually rank
Open any 2026 roundup of ui test automation tools (Virtuoso QA's top 14, Software Testing Help's top 30, Ghost Inspector's top 10, cpoclub's 26 picks, Ranorex's own top 9). They rank the same set. You will see exactly three shapes: browser-only code frameworks, low-code AI browser platforms, and expensive desktop-capable enterprise suites. No free, code-first, native-aware framework. That is the gap.
Check each one against four criteria: code-first API, MIT or similar OSS license, targets native desktop apps (not just browsers), and ships a selector language richer than "CSS + XPath". None of them satisfies all four. Terminator does.
The 25-variant Selector enum, verbatim
This is the actual source. Clone the repo, open the file, count the variants yourself. Every ui test automation tool has a model for "how to point at a UI element". In Playwright, it is a mix of CSS strings and getBy* helpers. In Cypress, it is CSS. In Selenium, it is By.X. In Terminator, it is an enum with 25 variants, and every prefix you will see in a selector string corresponds to one of them.
The enum, grouped by what it targets
Twenty-five variants sounds like a lot. It is not. They collapse into seven groups that each answer a different question a UI testing locator actually has to answer. Each card below is one group. Each group maps to a concrete failure mode you hit when writing tests against a non-toy app.
5 attribute matchers you already know
role, name, text, id, classname. These are the bread and butter. role:Button && name:Save covers ~80% of real selectors in practice. Same cognitive shape as Playwright's getByRole, which is why a Playwright user reads a Terminator test on sight.
1 process scope
process:chrome, process:EXCEL, process:Acrobat. Roots a locator in a specific running program. Without this, a selector that matches role:Button matches every button on the machine. With this, you say which app you mean in 8 characters.
1 native id escape hatch
nativeid:dob on Windows maps to UIA AutomationId. The one selector that survives localization, theme change, and cosmetic redesigns. If the app authors set AutomationId properties, your tests are effectively immune to label churn.
5 spatial relationships
rightof:, leftof:, above:, below:, near:. For every UI element that has no stable role or name: the blank input field to the right of the 'Password' label, the value cell below a header, the close button near a toast. No other tool in the listicle has this.
3 boolean composers
And, Or, Not, written as && || !. Parsed via Shunting Yard with parens and precedence. role:Button && (name:Submit || name:Confirm) parses as a boolean tree, not as substring matching against the combined text.
1 chain operator
>> applies the next selector as a descendant search inside the previous match. process:EXCEL >> role:DataItem means 'a DataItem anywhere inside Excel'. Composable. You can chain as many stages as you need.
1 :has() escape
has:(role:Button && name:Delete) matches a parent that contains a matching descendant. Playwright users know this one. It promotes 'find the row that contains the delete button' from an imperative loop to a selector.
The remainder
Nth for ordinal position, Parent for traversal, Visible for viewport filtering, LocalizedRole for non-English apps, Path for XPath-over-UIA, Filter for custom predicates, Attributes for arbitrary key/value maps, Invalid for failed parses. All grep-able in the same file.
A real parser, not a regex
Most ui test automation tools that claim to support boolean operators actually do substring matching against a combined string. When you write role:Button && (name:Save || name:Submit) in Terminator, it goes through a hand-written tokenizer and a Shunting Yard conversion to Reverse Polish Notation. The result is a boolean tree that respects precedence and parentheses. The implementation lives in the same file as the enum.
Anatomy of a cross-process selector
Watch a selector get built up, stage by stage. Each stage uses a different variant from the enum. The final string is one line in a test file. Each step below corresponds to one variant.
Building process:EXCEL >> below:(role:HeaderItem && name:Amount)
Stage 1: Process scope
Every prefix, in one line of selector string
These are the prefixes. Each maps to one variant of the enum. You will use role: and name: for most selectors. The rest fill specific gaps: process boundaries, spatial layout, localized apps, ambiguous tree shapes.
One enum, every accessibility backend
The enum is the user-facing surface. Under it, the platform adapters translate each variant into calls against Windows UIA, macOS AX, the Chrome DOM through Terminator's extension bridge, or experimental Linux AT-SPI2. Your selector string does not change when the target app changes.
Selector enum to platform adapters
Six selectors you can write in Terminator and nowhere else
Each item is a specific selector pattern, phrased in plain English. The Playwright equivalent, where one exists at all, is an imperative loop. In Terminator, each is one selector string.
- Find the email field that is right of the 'Email' label, when the field has no name attribute set
- Select the cell below the 'Amount' header in an Excel grid without hard-coding the row index
- Click the close button near the most recent toast, when many toasts have the same role and name
- Locate the value Text that sits to the right of a key Text in a key-value properties panel
- Assert the status icon above a row's 'Retry' button matches a specific role
- Select a button that is left of the currently focused element, without caring what other siblings exist
The six moves that take a selector from idea to green test
This is the workflow, in order. The same flow works for Excel, Chrome, Notion desktop, and a custom WPF admin tool. The selector string is the only thing that changes.
Discover with Accessibility Insights or Inspector
Before writing a selector, open the target app in Accessibility Insights for Windows or Accessibility Inspector on macOS. Every UI element exposes a role, a name, and often a nativeid (AutomationId on Windows). The selector is a readable translation of what you see there.
Scope to a process
Prefix every locator with process:YourApp. Without a process scope, a selector that says role:Button matches every button across every app on the machine. With it, your test is bounded to exactly the program under test.
Compose with boolean operators
role:Button && name:Save is an intersection. role:Button || role:Hyperlink is a union. !name:Cancel is exclusion. Parentheses group. The Shunting Yard parser in selector.rs handles precedence for you. One line of selector replaces five lines of imperative filtering.
Fall back to spatial when the role tree is thin
Custom widgets, WinForms apps, and older Qt apps sometimes have a thin accessibility tree. When you cannot name the element directly, anchor off a nearby labeled element and traverse. rightof:(role:Text && name:Password) is a working locator for an unlabeled password field.
Chain with >> when you need to descend
>> is the 'find the first selector, then search inside it' operator. process:EXCEL >> window:Book1 >> role:DataItem is a three-stage chain. Each >> creates a new resolution context. Chains are how you avoid ambiguous matches in multi-window apps.
Wait explicitly, assert without throwing
After you have a selector, pair it with wait_for(WaitCondition::Enabled, 5_000) before clicking, and with validate(5_000) for assertion-style checks. validate returns Ok(None) on miss instead of throwing. Your retry logic matches on the typed error variant.
One test, three processes, two spatial selectors
This is the kind of test that does not exist in any listicle tool. It starts in a PDF reader, pulls a value from an Excel workbook using a spatial selector, types it into a web portal, and asserts a confirmation badge. Seven enum variants get exercised in nine lines of test body.
What it looks like when the test runs
Selector resolves land in tens of milliseconds. The full flow finishes under a second on a warm Windows session. No screenshot parsing, no LLM inference, no image recognition.
Terminator vs. the listicle category
Ten criteria. The right column is Terminator. The left column is the aggregate of every browser-only or enterprise-image tool on the 2026 roundups: Playwright, Cypress, Selenium, TestCafe, WebdriverIO, Ghost Inspector, Virtuoso, Mabl, Testim, Functionize, TestSprite, Ranorex, TestComplete.
| Feature | The listicle (aggregate) | Terminator |
|---|---|---|
| Code-first, no record-and-replay required | Mixed. Ranorex, TestComplete, Virtuoso ship recorders first | Yes. No recorder. Selectors are strings, tests are functions |
| Free and open-source, MIT license | Only browser-only tools (Playwright, Cypress, Selenium). Desktop-capable enterprise tools are paid | Yes. Full source at github.com/mediar-ai/terminator |
| Targets native desktop apps (Excel, Outlook, Explorer, Notion desktop) | No in any of Playwright, Cypress, Selenium, TestCafe, WebdriverIO, Ghost Inspector | Yes. Selectors resolve against Windows UIA or macOS AX |
| Selector language with spatial relationships | Playwright has :has() and parent/child. None have rightof/leftof/above/below/near | Five spatial variants: RightOf, LeftOf, Above, Below, Near |
| Cross-process flow in a single test file | Impossible. A second tool (Winium, Appium Desktop, AutoIt) is required for the native side | One Desktop() instance spans every running process. process:X >> process:Y >> process:Z |
| Boolean operators in the selector string | Partial. CSS supports :not(). XPath supports and/or. Neither composes with parentheses like a real parser | && || ! with parens, parsed via Shunting Yard in selector.rs lines 216-272 |
| Native integration with AI coding assistants | None. No listicle tool ships an MCP server | Yes. MCP server at crates/terminator-mcp-agent with 35+ tools for Claude Code, Cursor, VS Code |
| Speed per selector resolve | Browser-only tools: 10-50 ms for a DOM query. Screenshot-based AI tools: seconds per action | 1-50 ms tree walk on the live accessibility tree. No screenshot, no LLM inference |
| Invoke via accessibility default action (no viewport required) | Click only. If element is off-screen, scroll first | invoke() calls UIA InvokePattern or AXPress without a mouse move |
| Typed flake errors your retry logic can match on | Usually one TimeoutError with a stringified message | 18 typed variants including ElementObscured, ElementNotStable, ElementDetached |
“Every enum variant, line number, and selector example on this page is grep-able in a fresh clone of mediar-ai/terminator. The 25 count is not a marketing number. It is a count of variants in selector.rs lines 5-56.”
github.com/mediar-ai/terminator
Why the category has a hole
Browser automation has been a hot category for fifteen years. The tools are plentiful, well-funded, and mature. Desktop automation has been the neglected sibling: the tools that exist are either legacy RPA vendors (UiPath, Automation Anywhere) or expensive per-seat test suites (Ranorex, TestComplete). Nobody shipped a code-first, Playwright-shaped, free framework for the desktop accessibility tree. Listicle authors had no slot to put one in.
Terminator is that framework. It is not a consumer app. It is a developer library you install with npm install @mediar-ai/terminator (or cargo add terminator), point at an app, and write selectors against. It also gives existing AI coding assistants the ability to control your entire OS through MCP, not just a browser tab. Like Playwright, but for every app on your desktop.
If the UI you are testing is a pure web page, stay in Playwright. If any part of your workflow leaves the browser, open the Selector enum at crates/terminator/src/selector.rs and pick the variant that fits the shape of what you are pointing at.
Variants in the Selector enum at selector.rs lines 5-56
Twelve attribute matchers, five spatial relationships, three boolean operators, one chain operator, four structural helpers. Grep it yourself.
Have a UI test that leaves the browser tab?
Show us the scenario. We will map it to selector-enum variants and a single test file that covers every process it touches.
Frequently asked questions
Why do mainstream ui test automation tools listicles not include Terminator?
Because the listicle format has been optimized for one category shape: browser test runners (Selenium, Playwright, Cypress, WebdriverIO, TestCafe) plus low-code/AI-native browser platforms (Virtuoso, Mabl, Testim, Functionize, TestSprite, Ghost Inspector) plus high-end enterprise desktop suites (Ranorex, TestComplete). Terminator is a free, open-source, code-first framework that targets native desktop apps via accessibility APIs. It does not fit the browser-only slot and is not priced like Ranorex. The listicles miss it because the category line they are drawing has no place for it yet. This page is part of changing that.
What is the 25-variant Selector enum, and where can I see it?
It is the Rust enum `Selector` defined in `crates/terminator/src/selector.rs` of the open-source terminator repo, lines 5 through 56. The 25 variants are: Role, Id, Name, Text, Path, NativeId, Attributes, Filter, Chain, ClassName, Visible, LocalizedRole, Process, RightOf, LeftOf, Above, Below, Near, Nth, Has, Parent, And, Or, Not, Invalid. Twelve are attribute matchers. Five are spatial (RightOf, LeftOf, Above, Below, Near). Three are boolean (And, Or, Not). One is a descendant chain (Chain). The rest are structural (Parent, Nth, Has, Filter, Invalid). All grep-able in a fresh clone of the repo.
What do the spatial selectors do that no other tool offers?
They let you locate an element relative to another element, using screen coordinates from the accessibility tree rather than DOM structure. `rightof:(role:Text && name:Password)` means 'find the element whose bounding box is immediately to the right of the text labeled Password'. This is how you target unlabeled input fields in legacy apps, value cells next to header cells in spreadsheets, close buttons near toasts, and value panes next to label panes in settings UIs. Playwright can do something analogous in the DOM via CSS sibling selectors, but it cannot do it across the actual rendered geometry of a native app. No other ui test automation tool in the listicles offers this.
How does the boolean parser work in practice?
Terminator parses selector strings with a custom tokenizer plus the Shunting Yard algorithm. See `crates/terminator/src/selector.rs` lines 216 to 272. You can write `role:Button && (name:Save || name:Submit || name:Confirm)` and it becomes `And(Role(Button), Or(Name(Save), Name(Submit), Name(Confirm)))` internally. Precedence follows the conventional rule: `!` is highest, then `&&`, then `||`. Parentheses override. The alternative in most tools is to write three separate find calls, collect the results, and filter imperatively. One selector replaces that whole block.
What does the chain operator >> do, and how is it different from CSS descendant selectors?
`>>` applies the second selector as a search inside the elements matched by the first. `process:EXCEL >> role:DataItem` means 'find the Excel process root, then search inside it for a DataItem'. It is similar in spirit to a CSS descendant combinator (space) or Playwright's `.locator().locator()` chain. The difference is that Terminator's chain operates on the accessibility tree, not the DOM, so the traversal works identically for Chrome tabs and native Excel windows. Every chain stage creates a new resolution context, which keeps the implementation of later stages independent of how earlier stages matched.
How does this compare to Appium, Winium, or WinAppDriver for native UI testing?
All three speak the WebDriver protocol over HTTP to a remote driver process, and use a Selenium-style find-by-id/accessibility-id API. They work, but the testing surface is dated: desired capabilities, sessions, `By.AccessibilityId`, XPath over UIA. Terminator is code-first (no driver process, SDK talks UIA/AX directly from your test process), Playwright-shaped (locator chains, async wait_for, validate), and ships one selector language that covers attribute, boolean, spatial, and chain in the same string. It is also MIT-licensed with no per-seat cost. For teams already writing Playwright browser tests, Terminator feels like the same API extended to the whole OS.
Does Terminator replace Playwright or Cypress for browser-only testing?
No, and it is not meant to. If every UI under test is a web page in a browser tab, keep using Playwright or Cypress. Those are the right tools for that shape of problem. Terminator is the right tool when the UI under test is a native app (Excel, Outlook, a custom WPF admin tool, a Tauri desktop app, Photoshop) or when a single test flow crosses the browser tab boundary into another running program. You can run both in the same test file. Terminator's selector language deliberately mirrors Playwright's conventions so the cognitive switch is small.
Can AI coding assistants like Claude Code or Cursor run these tests?
Yes. Terminator ships an MCP server at `crates/terminator-mcp-agent` with 35+ tools including click_element, type_into_element, validate_element, execute_sequence, and run_command. Any MCP-capable AI coding assistant (Claude Code, Cursor, VS Code with the MCP extension) can drive the same selectors you write in your test files. This is useful for exploratory test authoring, where the model writes and runs selector probes interactively, and for self-healing test maintenance, where the model is given a broken selector and the live accessibility tree and asked to propose a fix.
What platforms does Terminator run on?
Windows and macOS are the two first-class platforms. On Windows, Terminator uses the UI Automation (UIA) COM API via the `uiautomation` crate. On macOS, it uses the Accessibility (AX) API via AXUIElement. Linux has experimental AT-SPI2 support but is not the primary target today. Browser DOM access is available on all platforms through Terminator's Chrome extension bridge, which exposes executeBrowserScript() on any element resolved inside a Chrome or Edge process. A single test can resolve selectors against UIA, AX, and the Chrome DOM in one run.
What is the licensing model?
MIT. Full source, no per-seat cost, no remote runner required. See github.com/mediar-ai/terminator. This matters because the desktop-capable UI test automation tools in most 2026 listicles (Ranorex, TestComplete, Virtuoso, Mabl, Applitools) are paid products with per-seat or per-run pricing. Terminator sits in the same category on the feature axis (code-first, cross-platform, selector-based, with a typed error surface) while being free and open-source.