BLOG

XPaths. Guiding Functionize when your site changes

Functionize tests use fingerprints, not selectors. But sometimes you need to make the test select a new element. So, how do you write robust XPaths?

As we saw in the first article in this mini-series, Functionize has outgrown the classic selectors used in Selenium. Instead, we collect a huge amount of data about your site, which allows us to create a model of how it functions. We then learn the intent behind each of your tests and make sure we select the correct elements to achieve this. However, as you will see, there are times when it may make sense to specify a selector.

The Functionize approach to self-healing

As you develop and improve your product, your site is constantly evolving. Typically, each time you update your site, you need to update all your test scripts accordingly. Changing the XPaths, providing new CSS-selectors, etc. This applies equally to on-screen changes and changes that happen under the hood. However, Functionize is a bit different. We have developed a unique approach to ensuring your tests evolve alongside your site. Every time your test runs, we collect a huge amount of data about what is going on. This includes details from the DOM, the CSS (including computed values), any scripts, API responses, and more. 

Our machine learning model uses all this data to constantly learn how your site is changing. When something changes in the UI, our system uses this intelligence to work out which is the correct element to click. For instance, if you redesign your UI to move the login button from the top left to top right on the screen. Moreover, we also look at things like computed CSS values. We look for unexpected changes here. For instance, when a computed block becomes much smaller than it was before. Or when a computed element is suddenly wider on the screen. When we spot an anomaly like this, the model is updated to reflect a change. But we also set a self-heal flag within the test to let you know something has changed. 

When self-healing might not work

Self-healing is really clever, and almost always it will do exactly the right thing. However, there are times when you actually don’t want the test to be too clever. This is especially true when you are making some fundamental changes to the site logic or behavior.

Imagine you are redesigning your shopping page. Previously, you only had a single “Buy Now” button. This puts the item in the basket with Sales Tax automatically added. But now you want to include different options for commercial customers and normal customers. So, you add a second button. This will add the product to the basket without automatically applying Sales Tax. The problem is, you have now given our machine learning system a bit of a quandary. Which button should it choose? You could hope for the best: more than 99 times out of 100, the system is clever enough to make the correct choice. After all, it looks at all sorts of aspects relating to the button, including the actual script that button calls. However, you can’t be certain it will get it right. This is when you want to manually edit the test and force it to select the correct button.

Using selectors within Functionize tests

The Functionize interface allows you to edit a test and specify an exact selector. This will just be passed straight to the system, effectively bypassing the intelligent self-heal function. You now have a challenge. One of the problems with selectors is they are notoriously brittle. They are the root cause of a large amount of test maintenance pain. So, how can you make sure you choose a good one?

Choosing a good XPath

There are many selectors you could use. These include element names and ids, CSS-selectors, and XPaths. We’d always suggest creating XPaths if possible. But how can you make sure the XPath is good? Fortunately, there are some simple rules you can follow.

Rule 1: Use Tag > Attribute > Value trios

This rule is really simple. It ensures you start by defining a well-formed selector. The syntax is

 //tag[@attribute=’value‘]. 

For example:

//input[@id=’email‘] finds an input element with the id of ‘email’.

Rule 2: Use ‘contains’ or ‘starts with’

Often, you know that elements on your page may content a specific word, or start with a given phrase. But maybe you don’t know the whole name or id. Here, you can use the patterns ‘contains’ or ‘starts with’. This approach works really well if your site is generated dynamically with IDs and names assigned programmatically.

The syntax for contains is:

//tag[contains(@attribute, ‘value‘)]. 

For example: 

//input[contains(@name, reset)] would find elements named “reset password” and “Password reset”. 

Starts-with is very similar, but results are returned for any match that starts with the pattern given. The syntax is 

//tag[starts-with(@attribute, ‘value‘)]

For example: 

//input[starts-with(@name, reset)] will return an element called “reset password”, but not “password reset”.

Note that these come with a health warning. If there are multiple elements that match the pattern, then the XPath will simply return the first one. Don’t fall into the trap of searching for elements containing “button” in the id!

Rule 3: Use logical operators ‘and’ and ‘or’

You can construct quite complex expressions using ‘and’ and ‘or’. This is really useful if, for instance, you have multiple similar sections in your UI. Maybe you have two forms, both with elements with the id ‘name’. 

For and, the syntax is 

//tag[XPath1 and XPath2] 

e.g. BillingName: //*[@id=’name’ and @class=’billing-form’]

Or is a bit more nuanced. It potentially returns multiple elements, so you need to be cautious. It would only really make sense where you have a page that may have different content depending on a previous step, but you still want to select a given element. 

The or syntax is 

//tag[XPath1 or XPath2]

For example:

//input[@name=’add to cart’ or @id-’buy now’]

Of course, XPath provides many more ways to navigate to a specific element. Indeed, you can create XPaths to locate absolutely any element in your site from any location! For instance, navigating via the parent element:

//*[@id=’input’]/button//parent::form

However, we recommend that you try to stick to the three rules above to avoid creating overly complex (and brittle) XPaths. 

A final word of advice

When possible, you should avoid the need to specify any sort of selector. But if you do have to, try and create a robust XPath. The only time this won’t work is when you are dealing with transient content. For instance, checking tooltips that appear when the mouse hovers or testing if a checkbox has been selected. Then you might need to use a CSS-selector instead. But once again, specifying your own selectors should be very much the exception, not the rule!