Automate Windows Desktop Apps with WebDriver- WinAppDriver

WebDriver
Share
Automate Desktop Apps WinAppDriver

In the WebDriver Series, you can find invaluable tips and tricks about web automation. I will dedicate the next couple of articles on the automation of desktop apps. For the job we will leverage on a new piece of  technology called WinAppDriver, developed and maintained by Microsoft. The framework utilizes the WebDriver Wire Protocol and uses the same client-server architecture as Selenium WebDriver.  This will be the first article from the series helping you to get started.

Installing and Running Windows Application Driver

1. Download WinAppDriver

2. Install WinAppDriver

Install WinAppDriver

3. Enable Windows 10 Developer Mode

Enable Developer Mode Windows 10

Open Windows 10 Settings. Then navigate to Update & Settings section. Click For Developers tab. Choose the Developer mode option.

4. Download Windows SDK

5. Install Windows SDK

Install Windows SDK

6. Create a new test project in Visual Studio

7. Install the required NuGet packages

<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Appium.WebDriver" version="3.0.0.2" targetFramework="net452" />
<package id="Castle.Core" version="4.1.1" targetFramework="net452" />
<package id="Newtonsoft.Json" version="10.0.3" targetFramework="net452" />
<package id="NUnit" version="3.7.1" targetFramework="net452" />
<package id="NUnit3TestAdapter" version="3.8.0" targetFramework="net452" />
<package id="Selenium.Support" version="3.5.1" targetFramework="net452" />
<package id="Selenium.WebDriver" version="3.5.1" targetFramework="net452" />
</packages>

Since WinAppDriver is built on top of the Appium framework the most important NuGet package that you need to install is Appium.WebDriver. You can use some other unit test framework if you don't like NUnit. All other packages are installed together with Appium.

Using Appium

Windows Application Driver is integrated with Appium, meaning if you use Appium as part of the test runner then it will launch WinAppDriver.exe and proxy the requests for you.

Initialize Windows Application Driver

Before you can run your tests you need to start the WinAppDriver server. By default you can find it here: C:Program Files (x86)Windows Application DriverWinAppDriver.exe

WinAppDriver Server

Once opened it starts listening on port 4723. In your tests, you connect to this URL.

This is how we create a new instance of the Windows Application Driver.

var appCapabilities = new DesiredCapabilities();
appCapabilities.SetCapability("app", "Microsoft.WindowsCalculator_8wekyb3d8bbwe!App");
appCapabilities.SetCapability("deviceName", "WindowsPC");
_driver = new WindowsDriver<WindowsElement>(new Uri("http://127.0.0.1:4723"), appCapabilities);
_driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);

For the app capability, you need to set the appId for the desktop application that you want to automate. For the deviceName capability, you need always to set WindowsPC. In future you will be able to automate other Microsoft devices such as XBox then you will set something else. The constructor of WindowsDriver accepts a Uri pointing the URL of the server that we already started.

Find Application AppId

It is a little bit tricky to find the appId of your applications. You can find more detailed information in the following article- Find the Application User Model ID of an installed app

In short, you can use the below PowerShell code to locate the appId of all installed apps.

function listAumids( $userAccount ) {
if ($userAccount -eq "allusers")
{
# Find installed packages for all accounts. Must be run as an administrator in order to use this option.
$installedapps = Get-AppxPackage -allusers
}
elseif ($userAccount)
{
# Find installed packages for the specified account. Must be run as an administrator in order to use this option.
$installedapps = get-AppxPackage -user $userAccount
}
else
{
# Find installed packages for the current account.
$installedapps = get-AppxPackage
}
$aumidList = @()
foreach ($app in $installedapps)
{
foreach ($id in (Get-AppxPackageManifest $app).package.applications.application.id)
{
$aumidList += $app.packagefamilyname + "!" + $id
}
}
return $aumidList
}

Call listAumids(“allusers”) to find the appId of all installed apps for all users of the current machine.

Locating Elements

The latest Microsoft Visual Studio version by default includes the Windows SDK with a great tool to inspect the application you are testing. This tool allows you to see every UI element/node that you can query using Windows Application Driver. This inspect.exe tool can be found under the Windows SDK folder which is typically C:Program Files (x86)Windows Kits10bin

inspect.exe

Once started, each time you point some element, you can find a detailed information which you can later use to locate it in WebDriver.

The locators supported by WinAppDriver are similar to the ones in Selenium WebDriver.

Client API

Locator Strategy

Matched Attribute in inspect.exe

Example

FindElementByAccessibilityId

accessibility id

AutomationId

AppNameTitle

FindElementByClassName

class name

ClassName

TextBlock

FindElementById

id

RuntimeId (decimal)

42.333896.3.1

FindElementByName

name

Name

Calculator

FindElementByTagName

tag name

LocalizedControlType (upper camel case)

Text

Tests Examples C# Code

Windows 10 Calculator

Bellow, you can find a simple NUnit test for testing the default Windows 10 Calculator. We add 5 to 7 and expect 12 to be the result.

[Test]
public void Addition()
{
_driver.FindElementByName("Five").Click();
_driver.FindElementByName("Plus").Click();
_driver.FindElementByName("Seven").Click();
_driver.FindElementByName("Equals").Click();
var calculatorResult = GetCalculatorResultText();
Assert.AreEqual("12", calculatorResult);
}
private string GetCalculatorResultText()
{
return _driver.FindElementByAccessibilityId("CalculatorResults").Text.Replace("Display is", string.Empty).Trim();
}

We locate the buttons by name through the help of inspect.exe. We need to modify a little bit the result to simplify the code of the rest of the tests.

Below you can find a couple of more tests where we execute various calculator operations. In each test, we use different locator strategies so that you can see all of them in action. The last test shows how you can use data driven testing in desktop apps' automation.

[TestFixture]
public class CalculatorTests
{
private WindowsDriver<WindowsElement> _driver;
[SetUp]
public void TestInit()
{
var appCapabilities = new DesiredCapabilities();
appCapabilities.SetCapability("app", "Microsoft.WindowsCalculator_8wekyb3d8bbwe!App");
appCapabilities.SetCapability("deviceName", "WindowsPC");
_driver =
new WindowsDriver<WindowsElement>(new Uri("http://127.0.0.1:4723"), appCapabilities);
_driver.Manage().Timeouts().ImplicitWait = TimeSpan.FromSeconds(5);
}
[TearDown]
public void TestCleanup()
{
if (_driver != null)
{
_driver.Quit();
_driver = null;
}
}
[Test]
public void Addition()
{
_driver.FindElementByName("Five").Click();
_driver.FindElementByName("Plus").Click();
_driver.FindElementByName("Seven").Click();
_driver.FindElementByName("Equals").Click();
var calculatorResult = GetCalculatorResultText();
Assert.AreEqual("12", calculatorResult);
}
[Test]
public void Division()
{
_driver.FindElementByAccessibilityId("num8Button").Click();
_driver.FindElementByAccessibilityId("num8Button").Click();
_driver.FindElementByAccessibilityId("divideButton").Click();
_driver.FindElementByAccessibilityId("num1Button").Click();
_driver.FindElementByAccessibilityId("num1Button").Click();
_driver.FindElementByAccessibilityId("equalButton").Click();
Assert.AreEqual("8", GetCalculatorResultText());
}
[Test]
public void Multiplication()
{
_driver.FindElementByXPath("//Button[@Name='Nine']").Click();
_driver.FindElementByXPath("//Button[@Name='Multiply by']").Click();
_driver.FindElementByXPath("//Button[@Name='Nine']").Click();
_driver.FindElementByXPath("//Button[@Name='Equals']").Click();
Assert.AreEqual("81", GetCalculatorResultText());
}
[Test]
public void Subtraction()
{
_driver.FindElementByXPath("//Button[@AutomationId="num9Button"]").Click();
_driver.FindElementByXPath("//Button[@AutomationId="minusButton"]").Click();
_driver.FindElementByXPath("//Button[@AutomationId="num1Button"]").Click();
_driver.FindElementByXPath("//Button[@AutomationId="equalButton"]").Click();
Assert.AreEqual("8", GetCalculatorResultText());
}
[Test]
[TestCase("One", "Plus", "Seven", "8")]
[TestCase("Nine", "Minus", "One", "8")]
[TestCase("Eight", "Divide by", "Eight", "1")]
public void Templatized(string input1, string operation, string input2, string expectedResult)
{
_driver.FindElementByName(input1).Click();
_driver.FindElementByName(operation).Click();
_driver.FindElementByName(input2).Click();
_driver.FindElementByName("Equals").Click();
Assert.AreEqual(expectedResult, GetCalculatorResultText());
}
private string GetCalculatorResultText()
{
return _driver.FindElementByAccessibilityId("CalculatorResults").Text.Replace("Display is", string.Empty).Trim();
}
}

In the above code we open and close the app for each test, which is a slow operation. You can always reuse the app by initializing the driver in class initialize method.

Do not forget to dispose the app driver at the end of your tests. Also, always make sure to close the WinAppServer.exe after the tests' execution.

You can find more examples and explanations on the project's official page.

In the next articles from the series, I will show you how to utilize the Page Object Design Pattern.