Successful Locator Strategies for WebElements

Let’s discuss now the best strategies, patterns, tips, etc to come up with good, robust Locators that are the least likely to break down when there are changes in your application.

Finding the WebElements in your application is rarely difficult (with some exceptions, of course), but, knowing how to write great locators, thinking ahead, and keeping things organized can be troublesome.

I will try to detail here some of the tips I always think of before writing a locator.

If you are not new to the Selenium world, you’ve likely already read about the following available Locator Strategies:

  • ID
  • NAME
  • LINKTEXT
  • PARTIAL LINKTEXT
  • TAG NAME
  • CLASS NAME
  • CSS
  • XPATH

You probably also read about the Order (in terms of relevance) in which to choose them, if all are available:

ID > NAME > CLASS NAME > LINKTEXT > CSS > XPATH

I do not intend to go against the grain here, however, keep the following in mind:

  • ID's are always your safest bet, BUT:
    - They are not always unique: Althought a bad practice in Web Development, it is possible to have multiple elements sharing the same ID in one single page
    - Some Websites, specially the ones that create content for you, such as ERP's (Enterprise Resource Planning) and CRM (Customer Relationship Management), or are created dynamically, will generate ID's for the Web Developers. That means, for us, that the ID will change every time you reload your page. Lucky for us this easy to tell, as they will always look something like "SomeId_123456". If your id is concatenated with some numbers or a large set of characters, do not use ID strategy
  • Name and Class Name are often repetead in other elements. Keep this in mind before using them.
  • Choosing when to use CSS or XPATH comes out to personal preference. The difference in performance is negligible, in fact, XPATH in many circumstances comes out on top of CSS, so, you are safe to ignore the haters here
  • XPATH is the only locator able to find any web element, on any page. Making it the most exhaustive one, and useful for the trickiest elements
Tip:
When using Selenium's Page Factory, replace your wordy @FindBy(how = How.ID, using = "recipient-email"), with the shorter, more readable form @FindBy(id = "recipient-email")
Did you know ? You can even skip the @FindBy entirely, by naming your WebElement the same as its name or ID. For example: private WebElement username will be translated by Selenium to the Locator: //*[@id='username'] OR //*[@name='username'] .


A Case For XPATH's

I won’t discuss much about ID’s or Class Names here since there really isn’t much to talk about, instead, let me play devil’s advocate. Personally, I’ve mostly used XPATH. I’ve already stated some of my reasonings, but to sum up:

  1. Performance-wise, in general, CSS does better than XPATH. The difference between the two of them is, however, negligible. No one, in the history on Test Automation, has ever said that a test ran too slow because of the Locators. If you're a numbers kind of guy, you can find the Math in posts such as here. So, the next time you hear someone complain about how slow XPATH is, you can remind him that the bottleneck will always be loading the page itself, never the locators. This should never be on the table.
  2. XPATH's are able to find Elements by text, which is extremely useful. Consider the case where you got data from a Database or API, and need to compare it against your UI. Add this to the fact that you can also add other filters to the text, such as contains. You will find explanations of each of them on my XPATH CHEATSHEET
  3. With XPATH you can find parent elements, or ancestors, which is not possible with CSS.
  4. Most people say that CSS is more readable and easiear to understand than XPATH, and this is, of course, debatable. It comes down to one of my earlier points: It depends on your preference. Stick to what you feel more comfortable with, and try not to mix them too much

I am a strong believer that most of the hate you will find towards XPATH’s online comes from bad use of it, because there is such a thing as horrible xpaths, which are not common in other locators.

For example, the locator:
//div/ol//*[2] is a prime example of bad xpath.

Why ? Huh…
1 - It tells us nothing of what we’re trying to find from looking at it (Other than it’s an element inside a list). If you re-write to something in the lines of: //ol//a[contains(@class, 'post-link')] you will know in a simple glimpse, that you’re looking for a list of Posts links. This will, together with smart variable naming convention, save you hours and hours of maintenance work.
2 - That locator is extremely fragile. It’ll stop working at the slightest change in the UI of your Application. You want to find 1 or 2 main points(or anchors) that are unlikely to change and use them.

Consider for example:
Bad Locator://body//section[@class='main-montent']//form//div/button[1]
Good Locator://form//button[contains(@class, 'save-form')]

In the example above, if the UI changes, such as moving the ‘Save’ button from the right side to the left, or adding more content to the form, will cause your tests to break. The second example, on the other hand, will simply find a button that contains the save-form class, and at the same time belongs to the form. It is not depedentant to where the button is placed inside the form. That is how an xpath should look like.

Another very important tip from me is, if you’re using xpath, never use the “Copy Xpath” option found in all popular browsers. They will, most of the time, provide you with awful locators that are maintenance hell and difficult to read and understand. Take a little time to learn XPATH by yourself and write a meaningful locator !
It might take some time getting used to it, but it will save you countless hours in the long run.

Tip:
Always use Page Factory (ie: Selenium's @FindBy annotation), with the only 2 exceptions being:
1 - You want dynamic locators, for example, creating a method which receives an Username as a parameter, and the locator being dependant, such as driver.findElement(By.xpath(String.format("//p[contains(text(), '%s')]", user)))
2 - You have a WebElement and need to find its childs --> myWebElement.findElement(By.xpath(".//a[@class='test']")) - another common use case where Page Factory can't help you.


Warning: XPATH Versions

XPATH (XML Path Language) allows you to use expressions to search inside XML (in our case, HTML). It is not inherent or maintained by Selenium, instead, most WebDrivers (ChromeDriver, FirefoxDriver, etc) come bundled with a native XPATH of their own.
From the looks of it, most, if not all, the WebDrivers today are using XPATH 1.0, so, be careful when searching on the web and finding references to things such as lower-case, upper-case, starts-with because they are from newer versions of Xpath and might not work on your scripts. You can find a complete reference to XPATH 1.0 from the World Wide Web Consortium at XPATH 1.0.

Please take a look at my XPATH CHEATSHEET to learn more.

Having a difficult time with a rough element? Contact me and I’ll help you find it.

Thanks for reading, until the next time, Santiago.