
Winium.Desktop: Selenium for Windows desktop applications

Hi, my name is Gleb, and I am engaged in test automation in 2GIS. More than a year ago I wrote about our Cruciatus tool - with its help we test UI of desktop applications under Windows.
Cruciatus perfectly solves the problem of access to controls, but tests are written strictly in C #. This makes it difficult to fumble knowledge and experience between testers for different platforms: mobile, web and desktop.
We saw the solution in Selenium - perhaps the most famous tool for testing automation. In this article I will tell how we crossed Cruciatus and Selenium and how to test the Windows interface of desktop applications using the usual Selenium binders.
Why Cruciatus Didn't Enough
Almost all teams that develop 2GIS internal products have used Cruciatus. And each of these teams offered improvements to the tool. To please everyone, we redesigned Cruciatus logic right up to the breakdown of backward compatibility. It was painful but helpful.
We also abandoned the Mouse & Keyboard classes from CodedUI to remove the dependency on the libraries that came with VisualStudio. So they learned how to build a project on public CI servers like AppVeyor .
As a result, we made a convenient and self-sufficient tool that solves all our tasks of accessing elements of desktop applications for Windows. But at the same time, Cruciatus has one serious limitation - the dictatorship of C #.
How did you come to Selenium
Selenium is a set of tools and libraries for automating application testing in browsers. The heart of the Selenium project can be considered the Json Wire Protocol (JSWP) - a single REST protocol for the interaction between the tests and the application under test.
Advantages of a single protocol:
- tests work on all platforms and in all browsers;
- developers write them in any language. Selenium binders are already available for Python, C #, Java, JavaScript, Ruby, PHP, Perl. For other languages, bindings can be developed by yourself;
- the same commands work for different types of applications. At the test level, clicking a button in the web interface does not differ from a click in the mobile interface.
We decided to use these advantages in the automation of testing desktop applications - just as we use them for the web.
What is Winium.Desktop
To get away from the dictatorship of C #, we wrote a wrapper compatible with Selenium for Cruciatus. In parallel, the company created the same Selenium-compatible tool for self-tests, but for mobile Windows-based applications. We combined these developments under the common name Winium, and our tool was named Winium.Desktop.
In essence, Winium.Desktop is an http client. It implements the JSWP protocol and uses Cruciatus to work with user interface elements. In fact, this is an implementation of WebDriver for desktop applications under Windows.

With Winium.Desktop, we use the familiar Selenium binders to test desktop applications for Windows.
How to work with Winium.Desktop
To work with Winium.Desktop, download the latest driver release from github and run it with administrator privileges. This is not a prerequisite, but otherwise you will sooner or later come across Access denied either from the operating system or from the application.
All is ready. Now take your favorite language, favorite IDE and write tests the same way you would for a web application. And if you are new to Selenium, read any documentation. We recommend starting with Selenium Python Bindings .
The only difference from testing web applications: to find out element locators, use tools like UISpy or UI Automation Verify. We will talk more about them later.
When you run the tests, do not touch the mouse and keyboard: the cursor will shift, the focus will change, and automation magic will not happen.
What the driver can do
When implementing the Json Wire Protocol, we relied on two draft protocols used by WebDriver: JsonWireProtocol and the more recent webdriver-spec .
Now we have implemented most of the most sought after teams.
Full list
Command | Inquiry |
---|---|
Newsession | Post / session |
Findelement | POST / session /: sessionId / element |
FindChildElement | POST / session /: sessionId / element /: id / element |
Clickelement | POST / session /: sessionId / element /: id / click |
SendKeysToElement | POST / session /: sessionId / element /: id / value |
GetElementText | GET / session /: sessionId / element /: id / text |
GetElementAttribute | GET / session /: sessionId / element /: id / attribute /: name |
Quit | DELETE / session /: sessionId |
Clearelement | POST / session /: sessionId / element /: id / clear |
Close | DELETE / session /: sessionId / window |
Elementequals | GET / session /: sessionId / element /: id / equals /: other |
ExecuteScript | POST / session /: sessionId / execute |
FindChildElements | POST / session /: sessionId / element /: id / elements |
Findelements | POST / session /: sessionId / elements |
GetActiveElement | POST / session /: sessionId / element / active |
GetElementSize | GET / session /: sessionId / element /: id / size |
Implicitly whait | POST / session /: sessionId / timeouts / implicit_wait |
IsElementDisplayed | GET / session /: sessionId / element /: id / displayed |
IsElementEnabled | GET / session /: sessionId / element /: id / enabled |
IsElementSelected | GET / session /: sessionId / element /: id / selected |
Mouseclick | POST / session /: sessionId / click |
Mousedoubleclick | POST / session /: sessionId / doubleclick |
MouseMoveTo | POST / session /: sessionId / moveto |
Screen shot | GET / session /: sessionId / screenshot |
SendKeysToActiveElement | POST / session /: sessionId / keys |
Status | Get / status |
Submitelement | POST / session /: sessionId / element /: id / submit |
An example of using the simplest commands (Python):
- We start the application with the NewSession command when creating the driver:
driver = webdriver.Remote( command_executor='http://localhost:9999', desired_capabilities={ "app": r"C:/windows/system32/calc.exe" })
- Find the window of the application under test with the FindElement command :
window = driver.find_element_by_class_name('CalcFrame')
- Find the element in the window with the FindChildElement command :
result_field = window.find_element_by_id('150')
- We get the property of the element with the GetElementAttribute command :
result_field.get_attribute('Name')
- Close the application with the Quit command :
driver.quit()
Same thing, only C #:
var dc = new DesiredCapabilities(); dc.SetCapability("app", @"C:/windows/system32/calc.exe"); var driver = new RemoteWebDriver(new Uri("http://localhost:9999"), dc); var window = driver.FindElementByClassName("CalcFrame"); resultField = window.FindElement(By.Id("150")); resultField.GetAttribute("Name"); driver.Quit();
Read more about supported commands on the wiki in the project repository.
Work with elements
To manage elements in tests, these elements must first be found. Elements are searched by locators - properties that uniquely identify elements.
To find out item locators, use UISpy , its newer version of Inspect, or UIAVerify . The last two are installed with VisualStudio and are located in the “% PROGRAMFILES (X86)% \ Windows Kits \ 8.1 \ bin \” directory (there may be a difference in the version of Windows Kits).
It is advisable to run any of these tools from the administrator.
We recommend using UIAVerify. In our opinion, it is the most productive and convenient.
Although Cruciatus can search for elements by any property from the AutomationElementIdentifiers class, Winium.Desktop supports only three search strategies (such as locators):
- AutomationProperties.AutomationId;
- Name;
- ClassName
The root element in the search is the desktop. We recommend that you first find the window of the application under test (FindElement) and only then the elements inside it (FindChildElement).
If you need to expand possible search strategies, write to us or immediately create a new issue .
Example. Code that writes code
from selenium import webdriver
from selenium.webdriver import ActionChains
import time
driver = webdriver.Remote(
command_executor='http://localhost:9999',
desired_capabilities={
'app': r'C:\Program Files (x86)\Microsoft Visual Studio 12.0\Common7\IDE\devenv.exe'
})
window = driver.find_element_by_id('VisualStudioMainWindow')
menu_bar = window.find_element_by_id('MenuBar')
menu_bar.click()
menu_bar.find_element_by_name('File').click()
menu_bar.find_element_by_name('New').click()
menu_bar.find_element_by_name('Project...').click()
project_name = 'SpecialForHabrahabr-' + str(time.time())
new_project_win = window.find_element_by_name('New Project')
new_project_win.find_element_by_id('Windows Desktop').click()
new_project_win.find_element_by_name('Console Application').click()
new_project_win.find_element_by_id('txt_Name').send_keys(project_name)
new_project_win.find_element_by_id('btn_OK').click()
text_view = window.find_element_by_id('WpfTextView')
text_view.send_keys('using System;{ENTER}{ENTER}')
actions = ActionChains(driver)
actions.send_keys('namespace Habrahabr{ENTER}')
actions.send_keys('{{}{ENTER}')
actions.send_keys('class Program{ENTER}')
actions.send_keys('{{}{ENTER}')
actions.send_keys('static void Main{(}string{[}{]} args{)}{ENTER}')
actions.send_keys('{{}{ENTER}')
actions.send_keys('Console.WriteLine{(}\"Hello Habrahabr\"{)};')
actions.send_keys('^{F5}')
actions.perform()
Continuous Integration for Winium.Desktop Tests
In a CI project, tests controlled by the Winium.Desktop driver are included in a standard way. However, to execute them, you need a real or virtual machine. When setting up such a machine, follow a few formalities.
Firstly, the system requires a so-called active desktop. It exists on your computer or on an RDP connection. Moreover, the window of this connection cannot be minimized. To automatically create an active desktop, use Autologon .
Secondly, an active desktop must be kept active. To do this, configure the power supply on the machine (from under the user Autologon is configured to). Cancel display off and sleep. If you are using an RDP connection, restart the machine when it is complete. This will restore the active desktop. To spy on running tests, use the System Center App Controller or VNC .
Thirdly, the agent of your build server must work as a process, and not as a service. This limitation is due to the fact that on Windows the service does not have the right to launch the user interface of the application.
Total: configure Autologon, keep the desktop active and start the build server agent as a process.
Conclusion
The Winium.Desktop project allowed us to erase the line between the automation of testing the user interface of web and desktop applications.
Now testers freely exchange experiences and practices using Selenium. And autotests written for these completely different platforms are run in the same cloud infrastructure, built on the basis of Selenium-Grid.
Once again, a link to the repository and other opensource 2GIS products .