WebDriver: subclass the By class to define own locators

WebDiver (alias Selenium 2) uses the By class to hold the mechanism used to locate elements within a document. It’s fluent API allows to write nice code like this:

driver.findElement(By.id("someId"));

WebDriver’s API offers currently eight locators allowing to retrieve elements by id, name attribute, tag name, complete or partial link text, XPath, class name, and css selector. These location mechanisms are (always?) backed directly by the driver implementation what ensures that the best execution method is used for each browser.

These locators are very powerful but often too low level. Luckily it is easy to write your own ones.

Locate input fields by associated label

Do you know the HTML tag <label …/>? Even if it is quite an old tag as it was introduced in HTML 4.0 back in 1998, it is not as widely used as it should. Besides its ability to improve accessibility  it can be a wonderful help when writing test code.
The <label …/> tag allows to associate some content (the label) to a form control. When a label element is clicked, it passes the event to the associated control.
This means that you don’t need to precisely target the small box/circle area to check/uncheck a checkbox (or a radio button) but rather the, mostly larger, associated description. For text fields, a click on the label will bring the focus to the field, allowing to enter content. Just nice.

Example:

 <label for='firstName'>First name</label> <input name='firstname' id='firstName'>

This clear association between label text and input field can be used for test automation.
Instead of

driver.findElement(By.id("firstName"))

or

driver.findElement(By.name("firstName"))

with a bit XPath experience, you can write:

driver.findElement(By.xpath("id(//label[text() = 'First name']/@for)"));

but it would be nicer to write

driver.findElement(By.label("First name")); // doesn't compile!

The bad news is that there is no such a label method in WebDriver’s By class. The good news is that it is quite easy to implement your own By subclass and to pack it in a static method:

public static By byLabel(final String label)
{
  return new By() {
    @Override
    public List<WebElement> findElements(final SearchContext context)
    {
      final String xpath =
         "//*[@id = //label[text() = \"" + label + "\"]/@for]";
      return ((FindsByXPath) context).findElementsByXPath(xpath);
    }

    @Override
    public WebElement findElement(final SearchContext context)
    {
      String xpath =
        "id(//label[text() = \"" + label + "\"]/@for)";
      return ((FindsByXPath) context).findElementByXPath(xpath);
    }

    @Override
    public String toString()
    {
      return "ByLabel: " + label;
    }
  };
}

Now we can retrieve the input field in a short and readable way, relying only on information directly visible in the rendered HTML page rather than on (sometime ugly and volatile) name or id attributes:

driver.findElement(byLabel("First name"));

Write custom locators tailored for YOUR application

I encourage my customers to write custom locators. WebDriver is not targetted to any particular web framework or page layout neither does it know anything about
your application domain therefore it can only contain general purpose locators.
When you write tests, you know about the application under test and you can easily leverage WebDriver’s API to write dedicated locators. This allows you to avoid duplication of the location’s complexity by centralizing it in the locators and makes your code easier to read and to maintain.
Candidates for custom locators are for instance menu item or tab by name, spreadsheet cell by column and row index, or product quantity field by product description.

4 Comments

  1. December 9, 2011 at 8:15 am

    This logic works on xpath so it will not work for webdriver for browsers chrome and opera, I understand correctly?

    • Marc Guillemot said,

      December 9, 2011 at 8:35 am

      Why shouldn’t this work with Chrome or Opera? All drivers have XPath support, even if it’s not necessary based on native implementation like in IEDriver.

  2. Neko said,

    March 7, 2013 at 3:25 pm

    Different browsers render the DOM slightly differently. You have to do research to write it so it is cross browser compatible.

  3. raj said,

    December 14, 2015 at 4:19 pm

    can you explain how these xpaths to interpret? why added @for at the end and how to read @id= and id() ?

    “//*[@id = //label[text() = \”” + label + “\”]/@for]”;
    “id(//label[text() = \”” + label + “\”]/@for)”;


Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

%d bloggers like this: