winappdriver
Authors and runs Windows UI tests against the WinAppDriver UIA surface via both invocation paths - the direct Microsoft W3C-WebDriver service (installing + launching `WinAppDriver.exe` on `127.0.0.1:4723`, `app` / `platformName` / `appArguments` / `appTopLevelWindow` capabilities) and the actively-maintained Appium 2.x wrapper (`appium driver install windows`, `appium:` prefixed capabilities, `windows:` gestures, PowerShell prerun/postrun hooks). Covers UWP / WPF / WinForms / Win32 apps, `AccessibilityId` / `Name` / `ClassName` locators, and Windows-runner CI. Use when driving a native Windows app from a Selenium-style client (C#, Java, Python, Ruby, JS) - directly when no Appium install is wanted, via Appium when the stack already runs Appium for iOS / Android / Mac2; for a C#-only FlaUI client use flaui-tests, and to choose among Windows desktop drivers first use desktop-test-strategy-reference.
Install with skills.sh (any agent)
npx skills add testland/qa --skill winappdriverwinappdriver
Overview
Per the WinAppDriver repository (opens in new window):
"Windows Application Driver (WinAppDriver) is a service to support Selenium-like UI Test Automation on Windows Applications."
WinAppDriver exposes Microsoft UI Automation (UIA) - the Windows accessibility tree described in desktop-test-strategy-reference - behind a W3C-WebDriver-compatible HTTP endpoint. Per wad (opens in new window), it supports four application classes on Windows 10: "Universal Windows Platform (UWP)", "Windows Forms (WinForms)", "Windows Presentation Foundation (WPF)", and "Classic Windows (Win32) apps".
The same UIA surface has two invocation paths: talking to the Microsoft-maintained WinAppDriver.exe service directly from a Selenium client, or going through the Appium ecosystem's actively-maintained Node.js proxy that sits in front of it and adds gestures, multi-window helpers, and PowerShell hooks. This skill covers both - the direct path in the sections below, the Appium path in "Invoking via Appium". Go direct when no Appium (or Node.js) install is wanted on the test host; go via Appium when the project already runs Appium for other platforms.
When to use
For Qt-on-Windows out-of-process tests, this is the recommended driver per desktop-test-strategy-reference; the Qt application must publish a usable QAccessible tree.
How to use
Install + enable
Per wad (opens in new window):
Download the latest WinAppDriver installer from the releases page (opens in new window) (latest stable per wad (opens in new window): v1.2.1, published 2020-11-05) and run it on the test machine. The installer drops WinAppDriver.exe under C:\Program Files (x86)\Windows Application Driver\.
Launch the service
Per wad (opens in new window), launch on the default endpoint:
:: Default - 127.0.0.1:4723
"C:\Program Files (x86)\Windows Application Driver\WinAppDriver.exe"The service prints Press ENTER to exit. and listens for incoming W3C-WebDriver session requests. Custom IP / port / URL-prefix bindings (which require an admin shell) are in references/locators-and-ci.md.
Declare session capabilities
Per the WinAppDriver authoring guide (opens in new window):
| Capability | Purpose |
|---|---|
app | Application identifier (UWP family name) or full executable path (wadauth (opens in new window)) |
appArguments | Launch arguments string (wadauth (opens in new window)) |
appWorkingDir | Working directory for classic Win32 apps (wadauth (opens in new window)) |
appTopLevelWindow | Hexadecimal handle of an existing window to attach to (wadauth (opens in new window)) |
platformName | Target platform - set to Windows |
platformVersion | Platform version string |
Per wadauth (opens in new window), the UWP Application Id appears in the generated AppX\vs.appxrecipe file under the RegisteredUserModeAppID node (example shape: c24c8163-548e-4b84-a466-530178fc0580_scyf5npe3hv32!App).
Worked example
Drive one element end to end - launch Notepad, locate its editor by AccessibilityId, type into it, and close the session (C# client). The canonical example from wadauth (opens in new window):
using System;
using OpenQA.Selenium.Appium.Windows;
using OpenQA.Selenium.Remote;
var capabilities = new AppiumOptions();
capabilities.AddAdditionalCapability("app", @"C:\Windows\System32\notepad.exe");
capabilities.AddAdditionalCapability("appArguments", @"MyTestFile.txt");
capabilities.AddAdditionalCapability("appWorkingDir", @"C:\MyTestFolder\");
capabilities.AddAdditionalCapability("platformName", "Windows");
var session = new WindowsDriver<WindowsElement>(
new Uri("http://127.0.0.1:4723"),
capabilities);
// Locate by AccessibilityId (the AutomationId attribute)
var editor = session.FindElementByAccessibilityId("15");
editor.SendKeys("Hello from WinAppDriver");
session.Quit();The AccessibilityId locator maps to the UIA AutomationId property - the stable locator per the desktop-test-strategy-reference locator table. The full method-to-attribute mapping and the other locator strategies are in references/locators-and-ci.md.
Invoking via Appium
Per the appium-windows-driver repository (opens in new window), "Appium Windows Driver is a test automation tool for Windows devices and acts as a proxy to Microsoft's WinAppDriver server." The Node.js driver itself is actively maintained while the underlying service is described on the Appium ecosystem drivers page (opens in new window) as "has not been maintained since 2022" - which is why the wrapper ships a built-in installer to pin a known-good WinAppDriver version. Same UIA surface, same locator semantics; what Appium adds: windows: gestures (scroll / clickAndDrag / keys / launchApp), multi-window session switching, and appium:prerun / appium:postrun PowerShell hooks.
Install and launch (awd (opens in new window)):
npm install -g appium
appium driver install windows
appium driver run windows install-wad # pins + installs WinAppDriver.exe
appium --port 4723Session capabilities carry the appium: vendor prefix required by Appium 2.x for non-W3C-standard caps (awd (opens in new window)):
{
"platformName": "windows",
"appium:automationName": "windows",
"appium:app": "C:\\Windows\\System32\\notepad.exe",
"appium:appArguments": "MyTestFile.txt"
}platformName and appium:automationName must both be windows (case-insensitive); appium:appTopLevelWindow attaches to an existing window by hex handle; appium:prerun / appium:postrun run short, non-interactive PowerShell around the session (awd (opens in new window)). Python client:
from appium import webdriver
from appium.options.windows import WindowsOptions
from selenium.webdriver.common.by import By
options = WindowsOptions()
options.platform_name = 'windows'
options.automation_name = 'windows'
options.app = r'C:\Windows\System32\notepad.exe'
driver = webdriver.Remote('http://127.0.0.1:4723', options=options)
editor = driver.find_element(By.NAME, 'Text editor')
editor.send_keys('Hello from Appium Windows')
driver.quit()Appium-path traps: omitting the appium: prefix (session rejected); legacy automationName: WinAppDriver (use windows); hand-installing a random WinAppDriver MSI instead of install-wad (version mismatch with the proxy); hard-coding appTopLevelWindow (hex handle is per-launch); windows: scroll with positive deltaY to scroll down (negative scrolls down). The gestures API, multi-window flows, PowerShell hook recipes, and the Appium-path CI workflow are in references/gestures-hooks-and-ci.md.
Anti-patterns
| Anti-pattern | Why it fails | Fix |
|---|---|---|
Locating by FindElementByName for localised apps | Element name changes per language | Use AccessibilityId (UIA AutomationId) - stable across locales (wadauth (opens in new window)) |
Hard-coded screen coordinates via MouseAction | DPI / window-state / multi-monitor break | Resolve element via accessibility tree; the driver computes hit-test centre |
| Running tests with Developer Mode disabled | Session creation fails with cryptic error | Enable Developer Mode (Install + enable) (wad (opens in new window)) |
| Custom IP / port without admin privileges | Service refuses to bind to non-default address | Run admin shell or stay on default 127.0.0.1:4723 (wad (opens in new window)) |
| One mega-session that drives multiple apps | UIA tree gets stale between app switches | One session per app; close + recreate on app change |
Forgetting session.Quit() | Orphaned WinAppDriver child processes accumulate | try/finally around session lifecycle |
| Driving Edge / Chrome via WinAppDriver | Browser apps need a real WebDriver (Selenium / Playwright) | Use a browser driver, not WinAppDriver |
| Pixel-image matching for primary assertions | Brittle to font / theme / DPI changes | Accessibility tree first; image matching only for canvas-rendered content (per desktop-test-strategy-reference) |
Limitations
References
Appium Windows driver - gestures, hooks, and CI
View source (opens in new window)Appium Windows driver - gestures, hooks, and CI
Deep reference for the "Invoking via Appium" section of SKILL.md (opens in new window). Consult when adding Windows-specific gestures, multi-window flows, PowerShell session hooks, or wiring the Appium-wrapped driver into CI on a Windows runner.
Windows-specific gestures
Per awd (opens in new window), the driver adds Windows-namespaced extensions on top of the W3C WebDriver baseline:
| Command | Purpose |
|---|---|
windows: scroll | "Mouse wheel gesture" with deltaX / deltaY parameters (awd (opens in new window)) |
windows: clickAndDrag | "Drag-and-drop operations" (awd (opens in new window)) |
windows: keys | "Customized keyboard input with virtual key codes" (awd (opens in new window)) |
windows: launchApp | Open another app window within the same session (awd (opens in new window)) |
Scroll a list inside the app under test (negative deltaY scrolls down):
driver.execute_script('windows: scroll', {
'elementId': list_element.id,
'deltaY': -300, # negative scrolls down
})Drag a file across panels:
driver.execute_script('windows: clickAndDrag', {
'startElementId': source.id,
'endElementId': target.id,
})Multi-window sessions
Per awd (opens in new window), "It is possible to switch between app windows using WebDriver Windows API" and windows: launchApp "creates new app windows within the same session". This is the cross-app workflow path (e.g., test a deep-link flow that crosses from a desktop app into the Settings app):
settings_handle = driver.execute_script('windows: launchApp', {
'app': 'ms-settings:',
})
driver.switch_to.window(settings_handle)Pre/post-run PowerShell hooks
Per awd (opens in new window), appium:prerun and appium:postrun accept PowerShell scripts that the driver executes around session lifecycle. This is the canonical place to:
{
"platformName": "windows",
"appium:automationName": "windows",
"appium:app": "MyApp",
"appium:prerun": {
"script": "Copy-Item .\\fixtures\\config.json $env:APPDATA\\MyApp\\config.json -Force"
},
"appium:postrun": {
"script": "Remove-Item $env:APPDATA\\MyApp\\config.json -Force"
}
}Run and parse results
# Pytest example
pytest tests/windows --junitxml=reports/windows-junit.xmlJUnit XML output feeds junit-xml-analysis (in the qa-test-reporting plugin) for the cross-runner aggregation pipeline.
CI integration
# .github/workflows/appium-windows.yml
jobs:
test:
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
- uses: actions/setup-node@v4
with: { node-version: '22' }
- run: npm install -g appium
- run: appium driver install windows
- run: appium driver run windows install-wad
- name: Enable Developer Mode
run: |
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" `
/t REG_DWORD /f /v AllowDevelopmentWithoutDevLicense /d 1
- name: Start Appium
run: |
Start-Process -FilePath "appium" -ArgumentList "--port 4723" -PassThru
Start-Sleep -Seconds 5
- uses: actions/setup-python@v5
with: { python-version: '3.12' }
- run: pip install Appium-Python-Client pytest
- run: pytest tests/windows --junitxml=reports/windows-junit.xml
- uses: actions/upload-artifact@v4
if: always()
with: { name: junit, path: reports/ }Same hosted-vs-self-hosted runner caveats as the direct service (SKILL.md (opens in new window) Limitations) - UIA requires an interactive desktop session, so Session-0 Windows containers won't work without extra display setup.
WinAppDriver - locators, attach, and CI
View source (opens in new window)WinAppDriver - locators, attach, and CI
Deep reference for winappdriver SKILL.md. Consult for the full element-locator table, custom service bindings, attaching to an already-running window, and CI wiring on a Windows runner.
Custom service bindings
Per wad (opens in new window), beyond the default 127.0.0.1:4723:
:: Custom port (admin shell)
WinAppDriver.exe 4727
:: Bind to LAN IP (admin shell)
WinAppDriver.exe 10.0.0.10 4725
:: Bind to URL prefix (admin shell)
WinAppDriver.exe 10.0.0.10 4723/wd/hubCustom IP / port bindings require Administrator privileges (wad (opens in new window)); the default 127.0.0.1:4723 runs as a normal user.
Element-locator strategies
Per wadauth (opens in new window):
| C# / Java method | UIA attribute |
|---|---|
FindElementByAccessibilityId | AutomationId |
FindElementByClassName | ClassName |
FindElementById | RuntimeId (decimal) |
FindElementByName | Name |
FindElementByTagName | LocalizedControlType |
FindElementByXPath | any attribute (XPath over the UIA tree) |
To discover the right id during authoring, use Inspect.exe (ships with the Windows SDK) or Accessibility Insights for Windows - both walk the same UIA tree the driver sees.
Attaching to an already-running window
For tests where the app is launched externally:
var capabilities = new AppiumOptions();
// Hex window handle from Inspect.exe / Spy++
capabilities.AddAdditionalCapability("appTopLevelWindow", "0xB822E2");
capabilities.AddAdditionalCapability("platformName", "Windows");
var session = new WindowsDriver<WindowsElement>(
new Uri("http://127.0.0.1:4723"),
capabilities);Per wadauth (opens in new window), the appTopLevelWindow capability takes a hex window handle. This is the path for testing apps that don't support fresh-launch (apps with single-instance locks, or apps requiring authenticated login flows that run outside the test).
Run
:: Build + test (NUnit example)
dotnet test --logger "trx;LogFileName=results.trx"
:: With session retry on flaky launches
dotnet test --filter "Category=Smoke" -- RunConfiguration.TestSessionTimeout=600000Tests assume WinAppDriver.exe is running on 127.0.0.1:4723. A Setup fixture per test class should start the driver if it isn't already, then dispose at TearDown.
Parsing results
The C# Selenium client emits standard NUnit / MSTest / xUnit results (TRX, XML, or JUnit depending on logger choice). Pair with junit-xml-analysis (in the qa-test-reporting plugin) for the cross-runner aggregation pipeline.
CI integration
Windows-only runner required - WinAppDriver does not run on Linux or macOS:
# .github/workflows/winappdriver.yml
jobs:
test:
runs-on: windows-latest
steps:
- uses: actions/checkout@v5
- name: Install WinAppDriver
# Choco installs to default path + adds shortcut
run: choco install winappdriver -y
- name: Enable Developer Mode (Win 10/11 runners)
run: |
reg add "HKLM\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" `
/t REG_DWORD /f /v AllowDevelopmentWithoutDevLicense /d 1
- name: Start WinAppDriver
run: |
Start-Process -FilePath "C:\Program Files (x86)\Windows Application Driver\WinAppDriver.exe" `
-PassThru
Start-Sleep -Seconds 3 # Let the service bind to 4723
- uses: actions/setup-dotnet@v4
with: { dotnet-version: '8.0.x' }
- name: Test
run: dotnet test --logger "trx;LogFileName=results.trx"
- uses: actions/upload-artifact@v4
if: always()
with:
name: trx-results
path: '**/results.trx'WinAppDriver runs interactive - GitHub-hosted windows-latest runners have an interactive session by default, but headless self- hosted Windows containers need additional setup (the service refuses to start under Session 0).
Related skills
desktop-test-strategy-reference
Reference catalog of desktop GUI test strategies across Windows, macOS, and Linux. Defines the three accessibility-tree backends (Microsoft UI Automation on Windows, Apple Accessibility / XCTest on macOS, AT-SPI on Linux), the wrapper-tools that drive each backend, the cross-toolkit Electron + Qt paths, the project-marker detection table plus one-driver-per-app decision table (FlaUI / WinAppDriver / electron-playwright / QtTest / XCUITest / AT-SPI), an accessibility-first locator strategy, and a desktop test-review hazard checklist (screen-object encapsulation, locator stability, explicit waits, STA / foreground-lock / elevation). Deep operational detail (per-OS async-wait hierarchies, parallel-test policy, UAC / TCC / AT-SPI elevation hazards, the high-DPI matrix) lives in references/. Use when choosing how to test or automate a desktop GUI application on Windows, macOS, or Linux, or when reviewing an existing desktop UI test suite - the strategic reference ahead of the per-tool implementation skills.
electron-playwright
Authors Playwright `_electron` tests for packaged Electron desktop apps - launches the app via `electron.launch({ args })`, returns an `ElectronApplication` handle, drives renderer windows as Playwright `Page` objects, and probes the main process via `electronApp.evaluate(({ app, BrowserWindow }) => …)`. Distinct from ordinary browser page automation: this wraps the `_electron` API for launching packaged Electron apps and probing main + renderer processes. Includes the legacy Spectron reference and Spectron-to-Playwright migration shopping list (references/spectron-migration.md). Use for end-to-end tests of Electron apps where main-process state, IPC, and renderer DOM must all be asserted from one suite, or when migrating a deprecated Spectron suite.
flaui-tests
Authors and runs FlaUI-based Windows UI tests - the .NET-native wrapper around Microsoft UI Automation (UIA2 + UIA3). Covers the `FlaUI.Core` / `FlaUI.UIA2` / `FlaUI.UIA3` NuGet packages, `Application.Launch` / `Application.Attach` lifecycles, `ConditionFactory` + `FindFirstDescendant` locator patterns, `Retry` waits, and xUnit / NUnit / MSTest harness integration. Use when the test stack is C# / .NET-first and the team wants idiomatic in-process UIA calls rather than the HTTP/JSON wire protocol of `winappdriver` (direct or Appium-wrapped).
qt-test-framework
Authors and runs Qt Test - the first-party C++ unit + GUI test framework that ships with Qt 6 (via the `QtTest` module header). Covers the `QTEST_MAIN` / `QTEST_APPLESS_MAIN` / `QTEST_GUILESS_MAIN` entry-point macros, the `QObject` private-slot test pattern, `QVERIFY` / `QCOMPARE` / `QFETCH` assertions, GUI event simulation (`QTest::mouseClick`, `QTest::keyClick`, `QTest::touchEvent`), `QSignalSpy` for signal introspection, `QBENCHMARK` for performance regression, and the `-o file,junitxml` CI output. Use for in-process testing of Qt widgets, QObject signal/slot chains, and Qt Quick / QML application logic; for out-of-process Qt-app driving, use an OS-native accessibility driver instead.