WinAppDriver: Windows Desktop App Testing with WebDriver

WinAppDriver: Windows Desktop App Testing with WebDriver

Windows desktop applications — whether built with WPF, WinForms, or Win32 — require specialized automation tooling. WinAppDriver (Windows Application Driver) is Microsoft's open-source tool that implements the WebDriver protocol for Windows UIA (UI Automation), letting you automate Windows applications using the same Appium-style API used for mobile testing.

What WinAppDriver Does

WinAppDriver acts as a bridge between your test code and Windows' UI Automation framework. It:

  1. Runs as a local HTTP server on port 4723
  2. Accepts WebDriver/Appium commands from your tests
  3. Translates them to Windows UI Automation API calls
  4. Returns results back to your test code

This means you can write tests in any language with an Appium client library (C#, Java, Python, JavaScript) and they work against any Windows application.

Installation

Prerequisites:

  • Windows 10 or 11
  • Developer Mode enabled (Settings > Update & Security > For developers > Developer mode)

Install WinAppDriver:

Download from GitHub releases: microsoft/WinAppDriver

Or via winget:

winget install Microsoft.WinAppDriver

Start the server:

# Default — listens on localhost:4723
"C:\Program Files (x86)\Windows Application Driver\WinAppDriver.exe"

# With specific address and verbose logging
WinAppDriver.exe 127.0.0.1 4723 /wd/hub 1

Writing Tests in C#

The standard language choice for Windows testing is C#, using Appium.WebDriver:

<!-- NuGet packages -->
<PackageReference Include="Appium.WebDriver" Version="5.0.0" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="NUnit" Version="3.14.0" />
using NUnit.Framework;
using OpenQA.Selenium;
using OpenQA.Selenium.Appium;
using OpenQA.Selenium.Appium.Windows;
using System;

[TestFixture]
public class NotepadTests
{
    private WindowsDriver<WindowsElement> _driver;
    private const string WinAppDriverUrl = "http://127.0.0.1:4723";

    [SetUp]
    public void Setup()
    {
        var options = new AppiumOptions();
        options.AddAdditionalCapability("app", @"C:\Windows\System32\notepad.exe");
        options.AddAdditionalCapability("deviceName", "WindowsPC");

        _driver = new WindowsDriver<WindowsElement>(
            new Uri(WinAppDriverUrl), options
        );
        _driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
    }

    [TearDown]
    public void Teardown()
    {
        _driver?.Quit();
    }

    [Test]
    public void TypeAndVerifyText()
    {
        var editArea = _driver.FindElementByClassName("Edit");
        editArea.SendKeys("Hello, WinAppDriver!");

        var text = editArea.Text;
        Assert.That(text, Is.EqualTo("Hello, WinAppDriver!"));
    }

    [Test]
    public void FileMenuExists()
    {
        var fileMenu = _driver.FindElementByName("File");
        Assert.That(fileMenu.Displayed, Is.True);

        fileMenu.Click();

        var newMenuItem = _driver.FindElementByName("New");
        Assert.That(newMenuItem.Displayed, Is.True);
    }
}

Finding Elements

UI Automation exposes elements differently than HTML DOM. The most reliable locators are:

// By Automation ID (set by developers in the app)
driver.FindElementByAccessibilityId("MyButtonAutomationId");

// By Name (text label)
driver.FindElementByName("Save");
driver.FindElementByName("File");

// By Class Name (Windows control class)
driver.FindElementByClassName("Button");
driver.FindElementByClassName("TextBox");
driver.FindElementByClassName("ListBox");

// By XPath (most flexible, slowest)
driver.FindElementByXPath("//Button[@Name='OK']");
driver.FindElementByXPath("//Edit[@AutomationId='usernameField']");

// Tag Name (mapped to UIA control type)
driver.FindElementByTagName("Button");

Automation IDs are the gold standard. Ask your development team to add AutomationProperties.AutomationId to key UI elements in WPF/XAML:

<Button AutomationProperties.AutomationId="SaveButton" Content="Save" />
<TextBox AutomationProperties.AutomationId="EmailInput" />

Using Inspect.exe to Find Elements

Windows SDK includes Inspect.exe — hover over any UI element to see its Automation properties:

Tool location: C:\Program Files (x86)\Windows Kits\10\bin\10.0.xxxxx.0\x64\inspect.exe

Inspect shows:

  • Name — the visible label
  • AutomationId — the programmatic ID
  • ClassName — the control type
  • ControlType — UIA control type (Button, Edit, etc.)

Testing WPF Applications

WPF applications expose rich UI Automation trees. A typical test flow:

[TestFixture]
public class CustomerManagementTests
{
    private WindowsDriver<WindowsElement> _driver;

    [SetUp]
    public void Setup()
    {
        var options = new AppiumOptions();
        options.AddAdditionalCapability("app", 
            @"C:\MyApp\CustomerManagement.exe");
        _driver = new WindowsDriver<WindowsElement>(
            new Uri("http://127.0.0.1:4723"), options
        );
    }

    [Test]
    public void CreateNewCustomer()
    {
        // Click "New Customer" button
        _driver.FindElementByAccessibilityId("NewCustomerButton").Click();

        // Fill in the form
        _driver.FindElementByAccessibilityId("FirstNameInput").SendKeys("Alice");
        _driver.FindElementByAccessibilityId("LastNameInput").SendKeys("Smith");
        _driver.FindElementByAccessibilityId("EmailInput")
            .SendKeys("alice@example.com");

        // Submit
        _driver.FindElementByAccessibilityId("SaveCustomerButton").Click();

        // Verify the customer appears in the list
        var customerList = _driver.FindElementByAccessibilityId("CustomerList");
        var newCustomer = customerList.FindElementByName("Alice Smith");
        Assert.That(newCustomer, Is.Not.Null);
    }
}

Session Management for Multiple Windows

When your app opens new windows:

// Get all window handles
var handles = _driver.WindowHandles;

// Switch to a specific window
_driver.SwitchTo().Window(handles[1]);

// Or wait for a new window to appear
var initialHandles = new HashSet<string>(_driver.WindowHandles);

_driver.FindElementByName("Open Dialog").Click();

WebDriverWait wait = new WebDriverWait(_driver, TimeSpan.FromSeconds(10));
wait.Until(d => d.WindowHandles.Count > initialHandles.Count);

var newHandle = _driver.WindowHandles
    .FirstOrDefault(h => !initialHandles.Contains(h));
_driver.SwitchTo().Window(newHandle);

Attaching to a Running Application

Instead of launching the app from WinAppDriver, attach to an already-running process:

// Get the process ID
var process = System.Diagnostics.Process.GetProcessesByName("MyApp").First();

var options = new AppiumOptions();
options.AddAdditionalCapability("appTopLevelWindow", 
    process.MainWindowHandle.ToString("x"));  // hex window handle

_driver = new WindowsDriver<WindowsElement>(
    new Uri("http://127.0.0.1:4723"), options
);

This is useful when your app has a complex startup sequence that's easier to handle outside of tests.

Running in CI

WinAppDriver requires Windows — it won't run on Linux or macOS. GitHub Actions provides Windows runners:

# .github/workflows/windows-tests.yml
jobs:
  desktop-tests:
    runs-on: windows-latest
    steps:
      - uses: actions/checkout@v4

      - name: Enable Developer Mode
        run: |
          reg add "HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\AppModelUnlock" /t REG_DWORD /f /v "AllowDevelopmentWithoutDevLicense" /d "1"

      - name: Start WinAppDriver
        run: |
          Start-Process "C:\Program Files (x86)\Windows Application Driver\WinAppDriver.exe"
          Start-Sleep 3
        shell: pwsh

      - name: Run tests
        run: dotnet test tests/DesktopTests.csproj

Limitations and Alternatives

WinAppDriver has known limitations:

  • No support for DirectX/OpenGL rendered content
  • XPath queries can be slow on complex UIs
  • Some WinForms controls expose limited accessibility data

Alternatives for specific scenarios:

  • Playwright — for Electron or Chrome-based desktop apps
  • FlaUI — open-source .NET library wrapping UI Automation directly, faster and more flexible
  • Ranorex / TestComplete — commercial tools with more features

Monitoring Desktop App Services

WinAppDriver tests verify the UI. For monitoring the server-side services your Windows desktop app depends on, HelpMeTest provides 24/7 API health monitoring. If your desktop app's backend goes down, you know before users call support.

Summary

WinAppDriver brings WebDriver automation to Windows native apps:

  • Works with any Windows application — WPF, WinForms, Win32, UWP
  • Uses Windows UI Automation, which reads the actual accessibility tree
  • AutomationId locators are most reliable — request them from developers
  • Inspect.exe reveals what WinAppDriver can see in your app
  • Runs on any Windows with Developer Mode enabled
  • CI support via GitHub Actions Windows runners

For teams already familiar with Selenium or Appium, WinAppDriver requires minimal conceptual adjustment — the command vocabulary is identical.

Read more