Ranorex Speed #2: The Repository Cache

Ranorex supports caching for app folders and rooted folders, which are two types of repository folders. The repository caching feature was designed specifically to improve test run performance. When an action associated with a repository item is executed during a test run, Ranorex tries to use the cache to avoid performing expensive and redundant searches.

This is post #2 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.

Let’s take a repository item that has the following structure as an example:

TheApp                app folder:      /form[@title='My App']
|--MainToolbar        rooted folder:   /toolbar[@name='MainToolbar']
|  |--NewButton       item:            /button[@name='New']
|  |--ExitMenuItem    item:            /button[@name='Exit']
|--Title              item:            /container[@name='TitleContainer']/text[@name='Title']
|--ContentContainer   rooted folder:   /container[@name='ContentContainer']

The first column represents the repository tree, the second column represents the type of item or folder in the repository, and the third represents the relative path of the item. Assume that all the app and rooted folders in this repository have their Use Cache property set to True.

When an action that is associated with TheApp.MainToolbar.NewButton is reached, Ranorex performs a series of steps to locate the element referred to by the item before it executes the action. Let’s take a look at a few different flows.

Flow #1: When Nothing is Cached in the Repository

  1. First, Ranorex checks if the containing folder TheApp.MainToolbar is cached.
  2. When it determines that TheApp.MainToolbar is not cached, Ranorex checks if its containing folder TheApp is cached.
  3. When it determines that TheApp is not cached either, it searches for TheApp‘s RanoreXPath /form[@title='My App'].
  4. When it finds the element represented by the app folder, it caches the element for the TheApp repository folder.
  5. It then searches for /toolbar[@name='MainToolbar'] under TheApp‘s element, and caches that element for the TheApp.MainToolbar repository folder.
  6. Finally, it searches /button[@name='New'] under the TheApp.MainToolbar and uses that element to execute the action.

Ranorex Studio shows a read-only AbsolutePath property for every repository item and folder. This property shows the effective path of the repository item. You can copy this path to the Ranorex Spy to explore and test your paths. But keep in mind, as you build your repository, that Ranorex does not (usually) actually use this absolute path during a test run.

As we can see from the sequence of actions described in this flow, Ranorex actually walks up from the repository item until it finds the deepest cached folder, and then walks back down to the item, searching each relative path in turn and updating the cache as it goes.

Another important takeaway from this process is that while it is logically equivalent to searching the whole absolute path directly, its performance is significantly different. Each cache-enabled level of the repository is a tradeoff. More levels means potentially more individual – and expensive – searches, but more caches means more performance improvement opportunities. Depending on your situation, it may make sense to either flatten out your repository tree or add rooted folders.

Multiple Caches

Note that the TheApp.MainToolbar.NewButton repository item is not cached by the repository. The repository only caches app folders and rooted folders, and only when their Use Cache property is set to True. But the repository cache is not the only cache that Ranorex utilizes. It is a second-level cache and is designed for the explicit purpose of giving test authors some control over test performance.

Internally, Ranorex uses a number of cache mechanisms to optimize performance. Just a few examples:

  • Ranorex uses a special attribute cache to store the values of the more common attributes, such as whether an element is visible or valid. This ensures that repeated tests of the same property in quick succession do not incur the cost of communicating with the underlying UI platform more than necessary. But this also means that in some rare cases, Ranorex might not return the most up-to-date value.
  • Individual adapters keep track of their elements. Once an adapter is used to find an element, repeated use of the same adapter will not incur the cost of searching for that element again. So if a coder needs to access a lot of information or perform multiple actions with the same element, that coder would prefer to use the same adapter instance. If the coder expects some of the information to be invalid, the coder can clone the adapter for the same element, thus preventing a repeated search and effectively “invalidating” the cache.
  • Scanning the UI is expensive and caching the data necessary to find the elements is the only way to achieve reasonable performance. When possible, Ranorex caches whatever information is needed to quickly get access to the underlying object. This is often a window handle, though different plugins can use different mechanisms. Some plugins, such as the Java and Flex plugins don’t use this cache, but they do have other ways to improve performance.

Besides these mechanisms, Ranorex has quite a few other caches and performance tricks up its sleeve. Some of these are completely hidden from Ranorex Studio users and almost unreachable for programmers coding directly against the Ranorex API. But whether Ranorex provides direct access or not, understanding the way the caches work can help fine tune performance, sometimes by working around the caches. Selectively cloning adapters is one such example, though there are other techniques as well.

Flow #2: When All the Folders Are Cached

After running flow #1, both TheApp and TheApp.MainToolbar are cached. On subsequent calls, Ranorex uses those elements.

  1. First, Ranorex checks if the containing folder TheApp.MainToolbar is cached.
  2. When Ranorex determines that TheApp.MainToolbar is cached, it searches for /button[@name='New'] under the TheApp and uses that element to execute the action.

The same is true if the next call uses just part of the initially cached path. Trying to access TheApp.TitleField therefore produces a similar sequence.

  1. First, Ranorex checks if the containing folder TheApp is cached.
  2. When Ranorex determines that TheApp is cached, it searches for /container[@name='TitleContainer']/text[@name='Title'] under the TheApp.MainToolbar and uses that element to execute the action.

Because TheApp was cached while searching for TheApp.MainToolbar.NewButton, it is available when searching for other descendants of TheApp.

Also note that unlike the other items and folders in this repository tree, the relative path for TheApp.TitleField is deeper, which is to say that it follows the form /container/text while the other relative paths specify just one level (e.g., /form). The repository doesn’t really care how deeply nested each relative path is, but it does demonstrate the flexibility of the repository cache. The relative path of a cached rooted folder could easily be more complex and much more expensive (for example, /container/?/?/container[@automationid='form']//text[1]). It makes a lot of sense in cases such as this to compare the performance of placing the whole path in one rooted folder vs. splitting it up into multiple nested rooted folders.

Flow #3: When Intermediate Folders Are Not Cached

By default, Ranorex enables the cache for app folders and disables it for rooted folders. The idea is that the app folders represent the application windows and dialogs, which rarely change. On the other hand, rooted folders can represent any element, so Ranorex can’t predict the element’s life span. To ensure consistent results, Ranorex disables the cache for rooted folders.

When a rooted folder is disabled, the sequence of actions for accessing the TheApp.MainToolbar.NewButton looks something like the following:

  1. First, Ranorex checks if the containing folder TheApp.MainToolbar is cached.
  2. When it determines that TheApp.MainToolbar cannot be cached because the cache is disabled, Ranorex checks if its containing folder TheApp is cached.
  3. When it determines that TheApp is cached, it searches for /toolbar[@name='MainToolbar'] under TheApp‘s element. It does not cache that element, so repeated queries will search for it again.
  4. Finally, it searches /button[@name='New'] under the TheApp.MainToolbar and uses that element to execute the action.

Although this produces more consistent results, it is also quite clearly slower. It’s up to you to decide whether to enable caching for rooted folders. As long as you take the different factors into consideration, you can control the performance.

Flow #4: When Partial Paths Cannot Be Found

Although not obvious, there are some cases in which caching rooted folders can actually have the opposite effect and slow down your test runs. This can happen when cached folders cannot be found. Take a look at this sequence, which assumes that all the folders are cached, as an example:

  1. First, Ranorex checks if the containing folder TheApp.MainToolbar is cached.
  2. When it determines that TheApp.MainToolbar is cached, Ranorex begins searching for /button[@name='New'] under TheApp.MainToolbar.
  3. However, although TheApp.MainToolbar is cached, the UI control represented by the element may no longer be available. This will cause the search for the button to fail.
  4. Upon failure, Ranorex will abort the folder-by-folder search strategy, and instead search for the absolute path of the TheApp.MainToolbar.NewButton element: /form[@title='My App']/toolbar[@name='MainToolbar']/button[@name='New']. It will bypass the cache.

In this situation, instead of performing the highly-performative search just for the button, Ranorex ends up searching for the button, timing out while searching for the button, and then running another search for the much more complex absolute path of the button. It will search for the absolute path even though the app folder TheApp is cached. Quite simply, it will try to use the repository caches, and upon failing, will try again without the caches. This process is doubly expensive.

Summary

We’ve seen that Ranorex uses a number of caches to improve and manage the test run performance. The repository cache, with its advantages and disadvantages, is a powerful tool designed specifically to give test authors a hand in managing the performance based on their knowledge of the app and the tests.

When working with the repository cache, you need to carefully decide which rooted folders to enable or disable, and further, how deeply nested the repository folders should be.

Leave a Reply