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.
Станислав Волков said,
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.