June 14th, 2017 by Adam Sandman
Take any tool for UI test automation and you will notice that they always lack support for some of the UI controls. And I am not talking about trivial ones like buttons and edit boxes, but the one that are custom controls, which are unique and created for specific needs. It can be some sophisticated grid or menu or a fancy tree. A number of such controls in this rapidly evolving world is growing exponentially. So, every UI test automation engineer is challenged by emerging UI frameworks in every project. This is especially true for Web applications.
As a provider of a UI test automation tool - Rapise - we constantly do our best to arm engineers with the most advanced technologies to help them deal with new UI controls.
This article by Danis Markovtsev will showcase a real-life example of this.
System Under Test: Dynamics 365 for Operations
Our system under test is an enterprise resource planning (ERP) application from Microsoft - Dynamics 365 for Operations. It is cloud based and it works in a browser.
Why test the app? Did Microsoft do a lousy job and released bad quality product? I do not think so. I am sure Microsoft engineers did a great job and offer a high-quality solution. So, what's the issue here? We deal with an ERP application and every enterprise assembles this puzzle in its own way. Dynamics 365 for Operations can be configured, customized and enhanced in million ways. This is why testing of implemented workflow is a required thing. It can be tested manually or automated.
The first control you hit while using the app is the main menu.
Yes, everything displayed on the picture above is just a single menu object. It consists of two panels and each of them accommodates a tree of menu items.
To analyze the menu, one needs a Spy
that shows a DOM tree, allows to test XPATH and CSS expressions and we have one. Rapise even calculates XPATH of an element using different strategies. Notice Selectors
section in the property grid on the picture below.
Once the DOM of the menu is clear (see below, schematically) it is time for the next step.
<div class="modulesPane">
<div class="modulesList" role="tree">
<!-- Section of first-second level menu items -->
<div class="modulesPane-groupHeading" role="group" title="Modules"/>
<!-- Second level items, on the same level in the DOM tree as their parent! -->
<a class="modulesPane-module" role="treeitem" title="Procurement and sourcing" />
...
<!-- -->
...
<!-- -->
</div>
<div class="modulesFlyout">
<!-- Section of third-fourth level menu items -->
<a class="modulesFlyout-LinkGroup" role="group" title="Purchase orders"/>
<!-- Fourth level items, on the same level in the DOM tree as their parent! -->
<div class="modulesFlyout-link" role="treeitem">
<a class="modulesFlyout-linkText" title="All purchase orders" />
</div>
...
<!-- -->
...
<!-- -->
</div>
</div>
In Rapise one can define a complex object that spans across multiple DOM nodes. Using such a definition Rapise is able to recognize the object during recording and action on it during test playback. In terms of this example, given the proper definition of the menu control, Rapise is able to capture the menu object on recording and choose the desired menu item at run time. Looking ahead let me show how it looks in a test code:
SeS('MenuBar').DoMenu("Modules;Procurement and sourcing;Purchase orders;All purchase orders");
DoMenu
method receives a semicolon separated path in the menu.
Without special support for the menu, control life will not be so easy. During script recording user clicks will be captured as just clicks on some DIVs and links. And every choice of a target menu item will be dependent on 3-4 objects. This is not a resilient approach for several reasons:
- Every menu choice must be recorded first. There is no way for dynamic choice given a path in the menu. Such paths may be for example stored in a spreadsheet (data-driven testing).
- The menu remembers its state. So to make a sequence of menu choices one needs to bother of reverting the menu to the initial state.
- It turned out that on small screens the structure of the menu is different from shown above. So recorded on a big screen won't work on small and vice versa.
Rapise helps to hide this complexity from testers who implement test cases. Coupled with Rapise Visual Language for scriptless automated testing it makes possible to engage non-programmers, for example, analysts and manual testers.
Next Steps
And now the most interesting part. Details of support for the menu in Dynamics 365 for Operations.
Defining a Rule
First, we define a new type of UI control.
/** @rule */
var DomDynamicsAXMenuBarRule = new SeSMatcherRule(
{
object_type: "DomDynamicsAXMenuBar",
object_flavor: "MenuBar",
extend_rule: "HTMLObject",
dont_hash: true,
/** @behaviors */
behavior: [DomDynamicsAXMenuBarBehavior]
}
);
Recorder Callback
Then explain recorder how to recognize the menu object. Here we define a callback that receives all the information about user clicks.
function IsDomDynamicsAXMenuBar(evt,element,eventOpts,objName,description, flavor, items)
{
var root = false;
if(root=__hasParentWithAttr(element, 'class', /modulesPane/ig))
{
// we are inside the menu!
// do the magic, e.g. calculate the menu path
// record action
}
}
Implement DoMenu Action
Defining behavior stub.
/** @behavior */
var DomDynamicsAXMenuBarBehavior = {
actions: [
{
actionName: "Menu",
/** @action */
DoAction: DomDynamicsAXMenuBarBehavior_DoMenu
}
],
properties:
{
}
}
If you want to see the whole implementation of DomDynamicsAXMenuBarBehavior_DoMenu
get a trial copy of Rapise and look into
c:\Program Files(x86)\Inflectra\Rapise\Core\Engine\Lib\LibDomDynamicsAX\DomDynamicsAXMenuBar.js
.
Here I'll show an initial part that ensures that menu panel is shown on screen.
var menuPane = this._DoDOMQueryXPath('.//div[contains(@class,"modulesPane-flyout")]');
if (menuPane.length == 0)
{
var openButtonResult = this._DoDOMQueryXPath('.//div[contains(@class,"modulesPane-opener")]');
if (openButtonResult.length == 0)
{
return new SeSDoActionResult(false, false, "Open button not found");
}
var openButton = openButtonResult[0];
openButton._DoClick();
}
Conclusions
Modern Web applications continue to evolve and technologies used a while ago quickly become obsolete and new UI frameworks emerge. This leads to the appearance of a great number of complex Web UI components. The approach presented above enables automation engineers to rapidly add support for complex custom Web UI controls into Rapise. It makes further implementation and maintenance of test cases much easier, saves time and resources for QA teams.