Locating page elements using WebDriver August 31, 2011
Types of Locators
Locators have changed with Selenium 2 with the WebDriver API. Out of the box WebDriver supports several ways to locate elements on the page using the
By abstract class.
Finding elements by ID
ids are the preferred way to locate elements on a page for 2 main reasons:
- According to w3c ids are supposed to be unique on an html page. This makes ids a very explicit and reliable way to locate elements on the page.
- Also, all browsers also have highly efficient methods to get an object on the page using their ids. This makes
idlocators the fastest type of locator in Selenium.
Let us now look at how to use id locators using this html code as an example:
<form name="loginForm"> Login Username: <input id="username" name="login" type="text" /> Password: <input id="password" name="password" type="password" /> <input name="login" type="submit" value="Login" /> </form>
In the above code, the username and password text fields are can be set using their ids. The locators for them would be
Even though this is a great locator, it is not realistic for all elements on a page to have ids. The developers add ids to key elements on the page to better control the look and feel or provide the dynamic user interaction. However, ids should also be added to elements that are frequently interacted within tests … to make the pages more testable. Automated test script authors should consider adding, or requesting addition of, ids to these key elements on the page.
In some cases, the ids on an element cannot be reliably used in a test. For instance, if you are displaying objects stored in the database, the objects ids could contain the database id in it, for instance
book1347 could be the id for book who’s database id is 1347. In such situations it may not be possible to predict and use the ids and so you may need to use other ways described below to find elements.
Finding elements by name
Using the same example above, the way you would find the submit button would be:
There is one big difference between the
name attributes though … name attributes don’t have to be unique in a page. If there are multiple elements with the same name, then the first element in the page is selected. So, in this example, if another button or form named “login” was present of added later, it could cause the test to fail.
Finding links by text
This locator identifies links by the text in them. Let us look at an example:
<html> <body> ... <a href="signin.html">Sign In</a> to modify your account details. ... </body> </html>
To click this hyperlink using the anchor tag’s text, you can use the
If there are multiple elements with text “Sign In”, the first one is selected.
Btw, this is called
linkText because it is used for hyperlinks. In Selenium if you used the
link=textPattern locator, you could use it to locate other elements like div, span, td etc. In WebDriver, this locator works only for links.
Another common case is when we need to find links by a portion of the text it contains. In such cases you can find it by specifying the partial text. For example:
Finding elements by XPath
XPath is a very powerful language to express which element to identify. If you use it correctly, it can produce very reliable and low maintenance locators, but if you use it incorrectly, it can create very brittle test cases.
Let us see some examples:
<table name="cart"> <tr id="item1"> <td class="name item">Mp3 Download: foobar.mp3</td> <td class="name item"><input name="qty" class="name item formfield disabled" /></td> </tr> <tr id="item2"> <td class="name item">Mp3 Player</td> <td class="name item"><input id="item2_quantity" name="qty" class="name item formfield required" type="text" /></td> </tr> ... </table>
You can target the highlighted input field using the following XPath expressions:
//input[contains(@class,'required') and type='text']
And for the last option, your java call would look like:
driver.findElement(By.xpath("//input[contains(@class='required') and type='text']"));
As you can guess, some of these expressions will not be as reliable as others. Of these
//table/tr/td/input is the worst because it would break even with slightest modification to the page structure. It can take some time to learn XPath if you aren’t familiar with it, but it is worth the time if you plan to spend a lot of time writing UI automated tests. In any case do not rely on tools, including selenium IDE, to generate the right xpath expression for you. They can help you get started but they are usually bad at identifying the more reliable XPaths.
There are some special cases you should be aware of when you work with XPath, like when you are trying to interact with SVG. Like in the example here, the html looks something like this:
<div id="svgchart"> ... <svg xmlns="http://www.w3.org/2000/svg"> <g> <path .../> </g> ... </svg> ... </div>
Here, if you use XPath like:
you will get
ERROR org.openqa.selenium.NoSuchElementException: Unable to locate element. This is because the svg element is in a different namespace. You will have to specify your xpath with the namespace uri like this instead:
//*[local-name()='svg' and namespace-uri()='http://www.w3.org/2000/svg']
So, if XPath’s are so versatile, why doesn’t everyone prefer these? It’s because they are often the slowest, especially in older versions of IE! There are some ways you can make XPath’s faster, but they still are a few times slower than ids or names.
Finding elements by CSS
css locators can be used to identify a large number of elements on a page. Let us look at this html snippet for instance:
<table name="cart"> <tr id="item1"> <td class="label">Mp3 Download: foobar.mp3</td> <td class="item"><input name="qty" class="formfield disabled" /></td> </tr> <tr id="item2"> <td class="label">Mp3 Player</td> <td class="item"><input id="item2_quantity" name="qty" class="formfield required" type="text" /></td> </tr> ... </table>
In this case the
css locator variations possible for the highlighted input field are:
The css locator may not be as expressive as XPath, but it generally executes faster.
Finding elements by class
This is more of a convinience mechanism to identify elements. In the css example above, instead of using
you could use
Getting elements by DOM
DOM stands for Document Object Model. DOM is convention for representing objects in HTML documents.
<form id="loginForm"> Login Username: <input id="username" name="username" type="text" /> Password: <input name="password" type="password" /> <input name="login" type="submit" value="Log in" /> </form>
In this page, the dom expression for the highlighted input field would be:
For those who have used Selenium 1 API, you would expect to find a By.dom() equivalent api, but it doesn’t exist. However, you still can get a handle to these elements using dom expressions by using the following code snippet instead:
The three key aspects of this code that you should note,
- The driver can be casted to
- You can create your own implementation of
Some things to note for Selenium 1 users
- You will notice that Implicit locators are not supported in WebDriver.
- When using name in Selenium 1, you could add other attributes in the expressions to filter the results. For example
name=login type=textwas valid. You cannot do that any more with the By.name() call.
As you might have noticed in the examples above, the
By locators were used within
findElements methods. The purpose of these methods is to return a
WebElement, in case of
findElement, and a list of
WebElements in the case of
WebElement represents an html element on the page. In the next section you will see that it is this
WebElement object that you would interact with or read attributes of for validations etc.
When you use these methods on the
WebDriver object, the scope within with the elements are located is the entire page.
Getting child elements
WebElement interface also has
findElements() methods. In this case, it is within the scope of this parent element that child elements are located. This is useful when you want to narrow the scope to find several elements that you would interact with. In most cases, narrowing the scope improves the execution times for locating the elements.