The key to optimizing RanoreXPath queries is to reduce the number of elements Ranorex has to evaluate as it scans the UI. This post shows a few ways to do that.
A RanoreXPath, or RXPath for short, describes the path to an element or set of elements. As I explain in my post on RanoreXPath basics, RanoreXPaths are essentially queries that tell Ranorex how to search the UI element hierarchy.
But not all paths are equal. They differ in scan speed, volatility, readability, and other aspects. When you record actions with Ranorex, it makes a best guess about the type of path you need. You can often improve your repository, and your test suite, by reviewing the changes Ranorex makes to your repository as you record your actions, and then optimizing them.
This is post #3 in a series of posts on optimizing Ranorex test run performance. If you’re not sure how elements and adapters, or RanoreXPaths and repositories relate to each other, you should read up on them before continuing.
Analyzing RanoreXPaths
Every element in the UI can be reached by any number of RanoreXPaths. Put differently, multiple queries can return the same (or partially overlapping) results. Our goal is to figure out how to construct the optimal path, but to do so, we have to understand how different factors affect RanoreXPath performance. Every path has advantages and disadvantages.
Suppose, for example, that your app is a window that has a toolbar with a number of buttons, and that the Name
property of the first button is SaveButton
. All of the following paths (and more) will return this button.
/form//button[1]
This path will find the first button in the app’s element hierarchy by its index position. This path is not resilient and will cause dependent tests to fail if the button order changes for any reason. In fact, it could fail even if the change is not in the toolbar. This can happen if the toolbar itself is moved, or if a button is added anywhere in the UI element hierarchy that happens to place it before the toolbar.
-
/form//button[@name='SaveButton']
This path is immune to most changes in order, so Ranorex will find the button even if new buttons are added or existing buttons are moved around. It is also far more readable and maintainable because the name
SaveButton
is much clearer than any index position. However, the path is not completely immune to every change in the UI. There might be more than oneSaveButton
button in the UI, and they may or may not have the same behavior.Both of these paths have an additional problem: they use the
descendent-or-self
axis specifier. Axis specifiers tell Ranorex how to navigate the UI. Thedescendent-or-self
axis specifier is denoted as//
and effectively tells Ranorex to search for the specified button anywhere inside (or “under”) theform
element. This specifier is incredibly powerful because it lets Ranorex scan large swaths of the UI, so it can find the button almost anywhere. This power adds flexibility and makes it possible to write very short and relatively resilient RanoreXPaths. But it also has a downside: scanning elements is expensive. -
/form/toolbar/button[@name='SaveButton']
One way to improve the path performance is to avoid using the
descendent-or-self
axis specifier. This path replaces//
with/toolbar/
, which is achild
axis specifier that tells Ranorex that the button must be inside atoolbar
element. Although the path is visibly longer, it is actually much more specific and constrains the search to buttons that are inside a toolbar. But this also means that the path is faster because far fewer elements will end up being scanned. In fact, while path #2 will scan every single button (and perhaps every element) in the UI, path #3 will likely end up scanning just a tiny fraction. Speed is often gained at the expense of flexibility. -
/form[@title='My App']/toolbar[@automationid='MainToolbar']/button[@name='SaveButton']
This path adds additional filters to further improve performance. A filter – anything that appears in the square brackets – forces Ranorex to perform additional processing on each element as it scans it. The filters in this path are pretty simple, but filters can be much more complex and can use
and
s andor
s and parentheses. But even with all this extra work, filters are crucial to improving performance.If there are multiple open applications, each of which contains toolbars with buttons, all the previous paths will have to scan every single app, significantly slowing Ranorex down. So filtering the forms constrains the scan to all the toolbars that are exclusively in the application being tested. Filtering the toolbars further constrains the scan to all the buttons that are exclusively in the
MainToolbar
toolbar.Combining all these filters has a cumulative effect and significantly reduces the overall number of elements that Ranorex has to scan. But this too has a cost: paths with too many filters are less resilient, though this can be mitigated to some extent. For example, Ranorex automatically generates filters for the
form
elements it adds to the repository while creating recordings, to constrain most scans to just the app under test. The filters usually constrain the form based on itsTitle
property. But window titles often change over the lifetime of an app, so Ranorex uses a regular expression to match against the beginning of the title instead of a perfect match. This behavior relies on the assumption that even if the title changes, the first word or two will remain unchanged. This assumption allows Ranorex to generate partially resilient yet constraining paths. You will often have to strike a similar balance with the paths that you edit. -
/element[@title='My App']/element[@automationid='MainToolbar']/element[@name='SaveButton']
Every element, regardless of its type, is still an
element
. This path is less optimal than path #4 because it doesn’t filter on the type of the element, but it serves to demonstrate the flexibility of RanoreXPath as a query language. If we consider the result, this path is likely very similar to path #4, mainly because of the attributes and the strict hierarchy (button
undertoolbar
underform
has the same hierarchical structure aselement
underelement
underelement
).If Ranorex identifies an element, but not its behavior (behaviors are represented by adapters), it will add generic tags such as
element
orcontainer
to the path. If you know better, you can simply change the type to any adapter supported by that element. In this case, the toolbar element can be represented byelement
,toolbar
, oruiautomation
(and possibly others). But you can actually mix and match attributes and tags, so you can use attributes provided by any adapter that can be used with the element, regardless of the tag specified in the path. In this example,@automationid
is provided by theUIAutomation
adapter even though it’s being used withelement
.
RanoreXPath Construction Strategies
Ranorex has a number of algorithms for constructing RanoreXPaths. These strategies are represented by the PathBuildMode enumeration and strike different balances between performance and flexibility. If you’re a coder, you can specify the PathBuildMode
to use when you call the Element.GetPath()
method.
For example, the Volatile
strategy creates what are generally very fast and highly inflexible paths. It does this by adding many attributes to the filters, and by including non-deterministic and non-repeatable attributes such as window handles and process IDs that rarely appear twice. Ranorex does not optimize volatile paths, which essentially means that it doesn’t use the descendent-or-self
and other flexible axis specifiers. In this case, the path is often faster because it has not been optimized.
On the other end of the spectrum is the StepCostReduce
strategy that removes all the volatile attributes and intermediary elements that Ranorex guesses are unnecessary. This strategy creates somewhat slower paths that are far more flexible. It tries to make the paths unambiguous in an attempt to guarantee consistent runs. It does this by picking and choosing attributes based on other elements that are present in the element hierarchy at the time the path is constructed. The path might be ambiguous sometimes during a test run even if it was initially unambiguous.
Summary
Regardless of whether you write code or not, you should consider the paths that Ranorex generates as a starting point. Ranorex makes guesses; sometimes they’re great, and sometimes they’re not so great. Always review the generated paths and optimize them to suit your needs. And always remember the tradeoff between flexibility and speed.
great post! Thank you!
one more question regarding the path #5:
how can we force Ranorex skip these elements inbetween and generate a path only with automation id and axis specifiers?
e.g.
I want Ranorex generate following xpath
/form[@automationid=”someform”]//container[@automationid=”somepage”]//button[@automationid=”somebutton”]
Hi Kevin,
You can’t do that using the
PathBuildMode
strategies but you can configure path generation at a much higher resolution by editing the “weight rules”. Weight rules affect Ranorex’s priorities when it generates paths, so you can specify which attributes Ranorex should prefer.It’s a very flexible system but it can take some time to get the hang of it and configure the weight rules just right. You can share the weight rules with other team members so others can benefit from your efforts.
Take a look at these two links, from Ranorex’s site:
Thank you for this post.
I dont see a way to set a default behavior. Can Ranorex Spy and Ranorex Studio be forced to build only type #4 paths (PathBuildMode.Simple)?
Best wishes
Hi Karsten,
It is possible but not advisable.
In Ranorex Studio, open your repository by double-clicking the .rxrep file in the Projects pane and then click the Settings button on the repository’s toolbar. On the Advanced tab, there’s a “RanoreXPath generation mode” property that lets you choose the default strategy. Note that it only supports the StepCostReduce, Reduce and Simple strategies. The other strategies (e.g., Volatile) are only available in code.
There’s a similar Settings button on the Ranorex Spy’s toolbar.
I should note that the Simple strategy does not optimize the paths and is usually not what you’re looking for. By not optimizing, it includes all of the elements in the path, which will create fast expressions. The problem is that those paths are not resilient. Every change in the UI, regardless of how insignificant, is likely to break your tests. The only advantage Simple has over Volatile is that Simple prefers repeatable, deterministic properties.
There’s one thing I think I should clarify. The automatically generated paths are a good starting point but you should always edit your paths if you want to create a resilient performant test suite. There is a balance between performance and resilience that only you can strike. So the path generation mode is far less significant than the test author’s awareness of his/her needs.