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!

37 comments on “Writing reliable locators for Selenium and WebDriver tests”

Post a comment

  1. ss wrote on

    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.

    Reply

    1. Zac Campbell wrote on

      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!

      Reply

  2. krishna baba wrote on

    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

    Reply

    1. Sandeep wrote on

      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.

      Reply

  3. Bey wrote on

    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.

    Reply

  4. Sunaina wrote on

    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).

    Reply

  5. Ping from String format, can't arrangement decimals - Zeoli Gyan on

    […] elements, controlling locators before seeking these kind of question. Go by these: Selenium Docs, Mozilla Blogs. Many such resources are accessible on […]

    Reply

  6. divya wrote on

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


    Create Task

    Reply

  7. divya wrote on


    Create Task

    Reply

  8. Alex C wrote on

    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.

    Reply

  9. harish wrote on

    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

    Reply

  10. Rafael wrote on

    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.

    Reply

  11. Phil Amador wrote on

    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.

    Reply

  12. Shivank wrote on

    How to locate a element in situation like below:

    Filter:

    Filter:

    Reply

    1. Ramandeep wrote on

      Share the HTML

      Reply

  13. harish wrote on

    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

    Reply

  14. prasad wrote on

    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

    Reply

  15. Ritesh wrote on

    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?

    Reply

  16. praveeen wrote on

    Valuable information thanks for sharing Selenium Online Training

    Reply

  17. Smithk839 wrote on

    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

    Reply

  18. Smithe461 wrote on

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

    Reply

  19. Ravi wrote on

    Qus: How to construct Xpath for below

    Test Text

    Reply

  20. Smithd234 wrote on

    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

    Reply

  21. Smithf815 wrote on

    This actually answered my drawback, thank you! adbgddddeeaakccf

    Reply

  22. debby wrote on

    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/

    Reply

  23. santhoshbesant wrote on

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

    Reply

  24. Smithe699 wrote on

    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

    Reply

  25. GangBoard wrote on

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

    Reply

  26. hani wrote on

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

    Reply

  27. Smithk956 wrote on

    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

    Reply

  28. Samsuddin Sikder wrote on

    </button

    what would be the best locator in this case

    Reply

  29. CREDO SYSTEMZ wrote on

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

    Reply

  30. Darren Hwang wrote on

    XPath should be avoided as it is 1) a performance killer 2) easy to create brittle Xpath with too much hierarchy information 3) harder to convince developer to help create testable DOM.

    Reply

    1. Brian Higashide wrote on

      These criticisms are often tossed around about xpath, I used to feel the same way however now I’m working in a environment where the IDs are constantly updated and rarely written to be human readable (ie: “jd_id123:0:45”).

      The performance of my tests depends much more on how quickly the page loads than which locator is used. I have to use implicit waits whether I’m identifying by ID, CSS or Xpath in the instances where my code attempts to execute before the element has fully loaded. Xpath might add a few fractions of a second longer to locate objects, not enough to create a noticeable difference IMO.

      In my environment every ID is brittle and prone to failure after developement, it’s possible to abuse both CSS and Xpath if you don’t know what you’re doing. After a quick google search I found this golden parchment http://www.cheat-sheets.org/saved-copy/Locators_table_1_0_2.pdf
      and it has opened my eyes to how versatile XPath is.

      It’s equally difficult to try to convince developers to hardcode static IDs when they’re working with a framework that generates them procedurally as changes are made to the code.

      As a rule of thumb it’s typically recommended to use IDs as a locator but that’s only in the most ideal circumstances. In my case IDs are not an option if I want to create tests that don’t need to be updated every single time a change is committed.

      The one shortcoming I have found with xpath is that unlike identifying by CSS selector, ID, linktext, etc. you’re not able to append the find_by_xpath function on an existing webelement identified by some other means because xpath identifiers are only relative or absolute to the entire page.

      Whereas I can get a list of child elements by calling this would output only elements with the ‘tr’ tag which are child elements of an element whose id=’home_table’

      However <driver.find_element_by_id('home_table').find_elements_by_xpath('//tr') will always search the entire document.

      css selectors, ids, link_text etc. can be used on an object located by xpath, but xpath can't be used on top of an element identified by other means.

      Reply

  31. vijay wrote on

    Nice information about the web qa and really it will be useful for the people. selenium training in chennai

    Reply

  32. isabella wrote on

    wow.it is very nice information.Thank you so much for sharing…….. Selenium Training in Chennai

    Dot Net Training in Chennai
    Dot Net Training in Chennai

    Reply

  33. Xpath in Selenium Webdriver wrote on

    But sometimes we will not be left with any other options, so that tim we have to use xpath only.

    We should always try to write better xpath in selenium to find the element

    Reply

Leave a Reply

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