Writing reliable locators for Selenium and WebDriver tests

By Zac Campbell

If you’ve come here looking for the perfect, unbreakable locator, then I’m afraid to tell you that there is no perfect locator. That HTML changes and locators become incompatible are realities of writing automated UI tests. You’ll have to get used to updating them as the development teams experiment with design, streamline HTML and fix bugs as long as your web app is evolving. Maintaining locators must be calculated as part of the cost of test maintenance.

However, the good news is that there is a difference between a good and poorly written locator. That means if you’re smart about your locators you can reduce the cost of maintenance and focus your time on more important tasks than debugging false negative results.

On the other hand, a failing locator is a good thing so don’t be afraid of it. The trusty NoSuchElementException, rather than an assertion failure, is often your first sign that there is a regression in your software.

In this guide I will assume that you know how to write a locator already and are familiar with the construction and syntax of CSS and Xpath locators. From here on we’ll dig into the differences between a good and bad locator in the context of writing Selenium tests.

IDs are king!

IDs are the safest locator option and should always be your first choice. By W3C standards, it should be unique in the page meaning you will never have a problem with finding more than one element matching the locator.

The ID is also independent of the element type and location in the tree and thus if the developer moves the element or changes its type WebDriver can still locate it.

IDs are often also used in the web page’s JavaScript so a developer will avoid changing an element’s ID to avoid having to change his JavaScript. That’s great for us testers!

If you have flexible developers or even an eye for the app source code you can always try and get extra IDs added into the code by buying them a beer on Friday evening, taking their sister on a date or just plain begging. However, sometimes adding IDs everywhere is impractical or not viable so we need to use CSS or Xpath locators.

CSS and Xpath locators

CSS and Xpath locators are conceptually very similar so I’ve put them together for this discussion.

These types of locators with combinations of tag name, descendant elements, CSS class or element attribute makes the matching pattern strict or loose, strict meaning that small HTML changes will invalidate it and lose meaning that it might match more than one HTML element.

When writing a CSS or Xpath locator it’s all about finding the balance between strict and loose; durable enough to work with HTML changes and strict enough to fail when the app fails.

Find an anchoring element

A good way to start a CSS or Xpath locator is to start with an element that you know is not likely to change much and use it as an ‘anchor’ in your locator. It may have an ID or stable location but not be the element you need to locate but is a reliable position to search from. Your anchoring element can be above or below the current element in the HTML tree, but most often it’s above.

<div id=”main-section”>
    <p>Introduction</p>
    <ul>
      <li> Option 1</li>
    </ul>
</div>

<div id=”main-section”> <p>Introduction</p> <ul> <li> Option 1</li> </ul> </div> In this example the <li> element that we want to locate does not have an ID or a CSS class making it difficult to locate. There is also the chance there is more than one list in the HTML. The div with the id “main-section” makes a good anchoring element from which to find the <li> element. It narrows down the HTML the locator is searching in.

When to use ‘index’ locators like nth-child() and [x]

nth-child(), first-child, [1] and such index-type locators should only be used if you are using it against a list itself. In this case the test should explicitly know it wants to pick an item at that index from the list, for example validating the first item of search results. Using an index-type locator to locate an element that is not index-placed is likely to cause you problems when the order of the elements is changed and thus should be avoided!

<menu>
  <button>Option 1</button>
  <button>Option 2</button>
  <button>Option 3</button>
</menu>

//menu/button[1] is a suitable locator only when you know you want to interact with the first menu item regardless of how the list of buttons is sorted. The order of the buttons may change and cause your test to fail. Would this be a legitimate failure or one that requires you to re-write the locator?

Depending upon the objective of your test a non-index based locator like //menu/*[text()=’Option 1’] might be more suitable. <menu> is the ideal anchoring element.

CSS class names often tell their purpose

Front end designers are actually humans too and will often give CSS classes names that represent their purpose.

We can take advantage of this and choose locators that are dependent upon the functionality rather than the styling because styling often changes.

<footer class="form-footer buttons">
  <div class="column-1">
      <a class="alt button cancel" href="#">Cancel</a>
  </div>
  <div class="column-2">
      <a class="alt button ok" href="#">Accept</a>
  </div>
</footer>

In this example ignore the class “column-1” and “column-2”. They refer to the layout and thus might be susceptible to changes if the development team decide to adjust the design. It will be more reliable to target the button directly. Although “button.ok” would be quite a ‘loose’ locator there could be more than one on the page. You can use the footer as your anchoring element, making “footer .ok” a good locator in this example.

Spotting future fragility

By observing the HTML you can spot potential future fragility. I’ve intentionally left 3 more things out of the locator in the previous example: the <a>’s tag name, the <a>’s content text and any direct descendents ( > ) between footer and a. In the HTML it looks like the dev team have already changed the text label and the tag from “ok” and “button” respectively. The class, text content and tag names are all mismatched!

If the dev team are indecisive or experimenting with UX and performance improvements, these might still change again so we will err on a slightly “looser” locator that will tolerate some changes in the HTML.

Direct descendents

CSS example: div > div > ul > li > span
Xpath example: //div/div/ul/li/span

A direct descendent refers to the parent to child relationship of HTML elements. A good example is the first <li> element inside a <ul>.

A long chain of direct descendents like in the locator example above might help you find an element where there are no classes or IDs but it is sure to be unreliable in the long term. A large block of content without IDs or classes is likely to be very dynamic too and probably move around and change HTML structure often. It only takes one element in the chain to change for your locator to come tumbling down.

If you absolutely must use direct descendents in your locators then try to only use a maximum of one in each locator.

Adjust it for purpose

<section id=”snippet”>
    <div>Blurb</div>
</section>

Only use as much of a locator you need. Less is more! If you are only capturing text then using a locator like “#snippet div” is unnecessary. WebDriver will return the same text content for the locator ‘#snippet and ‘#snippet > div’ but the latter locator would break if the div element were changed to a <p> or <span>.

Locating on element attributes

Locating on attributes is a lot like locating by CSS class – the attribute can be unique but also it can be re-used across many items so tread carefully. You’ll have to decide on a case by case basis.

Generally, it is best to avoid using attributes and focus on tags, CSS classes and IDs but modern HTML5 “data-” attributes are stable attributes to use because they are tightly integrated with the functionality of the web application.

Tag name, link text, name locating strategies

These locating strategies are just shortcuts to locating by attribute or by text string (Xpath: text()). The rules for using these apply to tag name, link text and name too.

In summary: when you are composing a locator look for an ID first and failing that, the next nearest element with an ID (to use as an anchoring element). From there, look at descendents and element attributes to narrow down to the element you want to locate.

Understand exactly the purpose of the locator – is it simply to navigate through the site or is it asserting the order of an element? Should the locator be able to cope if the element moves or should the test fail?

The purpose of the locator will decide how strict, loose and which techniques you will need to use in the locator.

Good luck, and code wisely to save yourself future test maintenance and false negative hassles!

32 Responses to Writing reliable locators for Selenium and WebDriver tests

  1. One caveat of using IDs is that some site development tools or server-side languages generate sort of structural IDs that are as liable to change as the structure itself — things like “contentBody_contentSection_contentSection_contentSection_txtUserName” instead of just “txtUsername”. While that’s arguably not a good way to use IDs and may reflect bad design of whatever is relying on the IDs to be generated that way, if you’re stuck testing such a site you might be no better off using such IDs directly than you would be using an xpath like “/html/body/div/div[3]/span[2]/input[2]”. One possible solution is to use modern CSS3 selectors to locate based on just the specific part of the ID that doesn’t reflect structure or procedural generation, e.g. “input[id$=’txtUsername’]” will match an input with any ID ending in “txtUsername”, including: “txtUsername”, “contentBody_contentSection_contentSection_txtUsername”, “contentBody_contentSection_contentForm_who_knows_what_txtUsername” etc. It’s no longer guaranteed to be unique at a technical level, but the odds of there being no duplicates on the right part of the ID are often better than the odds of the structural or procedurally generated portion of the ID not changing.

    • Thanks SS, I agree that it’s not a nice way to generate testable HTML content but your advice is great for making the most of a bad situation!

  2. hi,
    The article was good.
    I have a query, while selecting xpath in some websites facing some problems.
    Example :
    The xpath : //*[@id=’spanid_farea’]/em
    The above xpath was selected for Dropdown box. While running the program I am facing the problem as: org.openqa.selenium.support.ui.UnexpectedTagNameException: Element should have been “select” but was “em”.
    Note: Problems faced with ,,
    In such cases, how to select the exact xpath

    • Hi Krishna,

      Did u get the answer for your query. Can you please help me out.
      Presently we are working on the Projects kendoUI objects.
      Even iam facing same problem which u are facing.

  3. Question: Is it possible to have a web element (WebElement instance) ‘know’ its locator.
    Say, id if it exists, name if id does not exist but name does, etc.
    Alternatively, does anyone on this thread know of a tool that will accept as input a DOM and be able to transform it to a series of element tags with the corresponding locators?

    Much appreciated.

  4. Hi,

    i have a query related Chrome Browser. How to write code in eclipse to test the application in chrome browser using selenium server (RC).

  5. Pingback: String format, can't arrangement decimals - Zeoli Gyan

  6. Need Xpath expression for selecting Create Task option please help me with this


    Create Task

  7. Selenium tests are inherently flakey no matter what you do. Visibility checks, spin waiting, and angular calls do not account for asynchronous behavior of an application at a sub-human reaction speed level. This is a fact.

  8. i have some problem in my project.here three tabs is there .i am writing script for the three tabs individially its successfully executed.but come to combinations these three into single class the second tab not clicking and the the browsers is also closed.i am running JUnit and i am using java.it through some excepition like element not found exception and elemenet not visible.
    after these exceptions i am using some conditions like explicit conditions.

    last one is my code is executed some times in IE9 but IE10 and IE11 is not executed.Here sometimes forcefully the browsr is executed.

    so pls understand and tell me the reason which i phased and what is the solution.

    harishkumar

  9. I recommend all of you to use a framework like Codeception to write your automated tests directly in php.

    Not perfect, but very simple and efficient. The best I’ve tried so far.

    Combined with some browser plugins to find Xpath/CSSpath, you can do a lot.

  10. Great article, Zac! sorry so late to the discussion. 🙂
    I’ve always found that this discussion helps to build great, working relationships with QA Automation and Dev teams. It’s all in the approach.

  11. How to locate a element in situation like below:

    Filter:

    Filter:

  12. I have to read your blog. Your blogs is more Informative. If anyone interested to learn selenium training in Chennai, please refer this website.
    ios training in chennai

  13. i want to automate the tree based structure as shown in below attachment i want to identify the child element (Systems) under the parent(Systems),but both parent and child systems have the atribute class only with same value how can i do that pls help me

  14. When an element is referred, it’s reference is created in the code. Take a scenario, in which in a html page, a button click will close the html page itself. Now, if i create a reference to that button and invoke click even, the html page will not be cleaned up from memory as test code holds the reference to the button. How to work around this scenario?

  15. Valuable information thanks for sharing Selenium Online Training

  16. I was suggested this blog by my cousin. I’m not sure whether this post is written by him as no one else know such detailed about my difficulty. You are wonderful! Thanks! kggbkbedccbbgkfe

  17. Pretty great post. I just stumbled upon your blog and wanted kgeadcaedbbbbgek

  18. Qus: How to construct Xpath for below

    Test Text

  19. Very interesting subject , appreciate it for posting . All human beings should try to learn before they die what they are running from, and to, and why. by James Thurber. gefccgfdedekadek

  20. This actually answered my drawback, thank you! adbgddddeeaakccf

  21. I want to share with you valubale information regarding Creating Better Selenium Tests with WebdriverIO
    http://blog.testproject.io/2016/10/16/better-selenium-tests-webdriverio/

  22. The content regarding “Writing reliable locators for Selenium and WebDriver tests” was very much helpful.Thanks for sharing it.

  23. Hi there would you mind letting me know which webhost you’re utilizing? I’ve loaded your blog in 3 different web browsers and I must say this blog loads a lot quicker then most. Can you suggest a good web hosting provider at a honest price? Thanks a lot, I appreciate it! dfkbdgdefbcgdbkb

  24. Excellent sharing. Testing is a wonderful field for IT peoples. Want to learn Selenium Training reach GangBoard. Hadoop Training

  25. Very useful information in this article! its really useful in asp.net web development services

  26. I like this post, enjoyed this one regards for putting up. The goal of revival is conformity to the image of Christ, not imitation of animals. by Richard F. Lovelace. fddddeddcddkeeee

  27. Samsuddin Sikder

    </button

    what would be the best locator in this case

  28. Thanks for nice topic. Very useful information.Selenium Training in Chennai

Leave a Reply

Your email address will not be published. Required fields are marked *