Typesof 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.
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 id locators
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
driver.findElement(By.id(username));
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.
Generally ids are added to elements when they want
to be referenced from css or javascript and names are added to form fields.
When referencing element from javascript either can be used. From a test
automation standpoint, whenever id is not available/ usable, you should try to
use the name instead.
Using the same example above, the way you would find
the submit button would be:
driver.findElement(By.name("login"));
There is one big difference between the id and 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.
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 By.linkText() locator:
driver.findElement(By.linkText("Sign
In"));
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:
driver.findElement(By.partialLinkText("Sign"));
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.
As you can guess, some of these expressions will not
be as reliable as others. Of these //table/tr[2]/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:
//svg
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.
css locators can be used to identify a large number
of elements on a page.
</table>
In this case the css locator variations possible for
the highlighted input field are:
input.required
input[class~='required']
input.required[type='text']
#item2_quantity
input#item2_quantity
The css locator may not be as expressive as XPath,
but it generally executes faster.
This is more of a convinience mechanism to identify
elements. In the css example above, instead of using
driver.findElements(By.css("input[class~='required']"));
you could use
driver.findElements(By.class("required"));
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:
document.forms[0].elements[0]
document.forms['loginForm'].elements['username']
document.forms['loginForm'].username
document.getElementById('username')
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:
driver.findElement(byDom("document.forms[0].elements[0]"));
public By byDom(String domExpression) {
final
Object o = ((JavascriptExecutor) driver).executeScript("return " +
domExpression + ";");
if (o
instanceof WebElement) {
return
new By() {
@Override
public List<WebElement> findElements(SearchContext searchContext)
{
return new ArrayList<WebElement>() {
{
add((WebElement) o);
}
};
}
};
}
}
The three key aspects of this code that you should
note,
The driver can be casted to JavaScript Executor and
other interfaces which will provide additional capabilities.
execute Script() in JavaScript Executor returns an
object which can be casted to a WebElement if the JavaScript expression returns
a dom element.
You can create your own implementation of By
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=text was valid. You cannot do that any more with the By.name() call.
findElement() and findElements()
As you might have noticed in the examples above, the
By locators were used within findElement and 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 findElements.
The 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.
The WebElement interface also has findElement() and
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.
No comments:
Post a Comment