
Hi, my name is Gleb, and I do test automation in 2GIS. More than a year ago, I wrote about our
Cruciatus tool - with its help, we are testing UI 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 test automation. In this article, I will tell you how we crossed Cruciatus and Selenium and how to test the Windows interface of a desktop application using familiar Selenium bindings.
Why not enough Cruciatus
Almost all the teams that develop the internal products of 2GIS used Cruciatus. And each of these commands offered improvements for the tool. In order to please everyone, we reworked the logic of Cruciatus to the extent of backward compatibility. It was painful, but helpful.
')
We also abandoned the CodedUI Mouse & Keyboard classes to remove 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 have made a convenient and self-sufficient tool that solves all our tasks in accessing elements of desktop applications under Windows. But at the same time, Cruciatus has one serious limitation left - the C # dictatorship.
How to 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
Json Wire Protocol (JSWP) - a single REST-protocol for interaction between 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 binding already exists 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 on a button in the web interface is no different from clicking on the mobile interface.
We decided to use these advantages in automating the testing of 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 for Selenium compatible with Cruciatus. In parallel, the company created the same Selenium-compatible tool for autotests, but for mobile Windows applications. We combined these developments under the common name Winium, and our tool was called 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 a WebDriver implementation for desktop applications under Windows.

With Winium.Desktop, we use familiar Selenium bindings 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 as an administrator. This is not a mandatory condition, but otherwise, sooner or later you will come across
Access denied from either the operating system or the application.
All is ready. Now take your favorite language, your favorite IDE and write tests the same way you would for a web application. And if you are not familiar with Selenium, read any documentation. We recommend starting with
Selenium Python Bindings .
The only difference from web application testing is: to find out element locators, use tools like UISpy or UI Automation Verify. Let's talk more about them further.
When run the tests, do not touch the mouse and keyboard: the cursor will move, the focus will change, and automation will not happen.
What can a driver
When implementing the Json Wire Protocol, we relied on two drafts of the protocol used by WebDriver: the
JsonWireProtocol and the more
recent webdriver-spec .
Now we have implemented most of the most popular teams.
Full listTeam | Request |
---|
New session | POST / session |
Find element | 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 |
Elementmentals | 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 |
ImplicitlyWait | 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 |
Screenshot | GET / session /: sessionId / screenshot |
SendKeysToActiveElement | POST / session /: sessionId / keys |
Status | GET / status |
SubmitElement | POST / session /: sessionId / element /: id / submit |
An example of using the most simple commands (Python):
- Run 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 an element in the window with the FindChildElement command:
result_field = window.find_element_by_id('150')
- Get the property of an item with the GetElementAttribute command:
result_field.get_attribute('Name')
- Close the application with the Quit command:
driver.quit()
The same, 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 control the elements in tests, these elements must first be found. Elements are searched by locators - properties that uniquely identify elements.
To find out element 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 (a difference in the Windows Kits version is possible).
Run any of these tools preferably from the administrator.
We recommend using UIAVerify. In our opinion, it is the most productive and convenient.
Although Cruciatus can search for items 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 being tested (FindElement) and only then the elements inside it (FindChildElement).
If you need to expand possible
search strategies, please contact 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 the CI project, tests managed by the Winium.Desktop driver are included in the standard way. However, they require real or virtual machine. When setting up such a machine, follow a few formalities.
First, the system requires a so-called active desktop. It exists on your computer or in RDP connection. And the window of this connection can not be minimized. To automatically create an active desktop, use
Autologon .
Secondly, the active desktop must be kept active. To do this, set up the power supply on the machine (from under the user for whom Autologon is configured). Cancel the display off and sleep. If you are using an RDP connection, restart the machine upon completion. This will restore the active desktop. To peek at test execution, use
System Center App Controller or
VNC .
Thirdly, your build server agent must work as a process, not as a service. This limitation is due to the fact that on Windows the service does not have rights to run 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 blur the line between automating user interface testing of web and desktop applications.
Now testers freely exchange experience and practices of using Selenium. And autotests, written for these very different platforms, run in the same cloud infrastructure, built on the basis of Selenium-Grid.
Once again the
link to the repository and
other opensource 2GIS products .