Notes On Selenium WebDriver
Notes On Selenium WebDriver
Notes On Selenium WebDriver
Introduction
Selenium WebDriver (also called Selenium 2) is a second generation web application test facility. It is the result of
efforts of Google, ThoughtWorks, and other development groups, as well as prior technology such as WatiN.
Development on Selenium 2 started about two years ago with a different model of how to operate in the browser.
Selenium 2 is a combination of prior Selenium development plus a new tool called WebDriver. This removes the
need for starting up the Selenium server, and allows the instrumented browser to interact with the application.
The API for writing Selenium WebDriver tests is much more object-oriented, with objects for page elements and
entire pages. Hence, there isnt a big list of method calls on the main Selenium object, but a set of objects that are
created using methods on the Driver object (the equivalent of the main Selenium object), and then methods on those
objects to perform actions.
Selenium 2 came went GA in early-mid 2011, and is now at version 2.19.0, released early February 2012. It
includes a compatibility API with the prior Selenium API, which can help with migration, though that is not
discussed here. It is recommended that new development use Selenium 2, with either of the APIs but moving
toward Selenium 2.
Resources
No specific books as yet, you must rely on web-delivered documents.
Primary documentation is at http://seleniumhq.org/docs/03_webdriver.html
Good overview at http://code.google.com/p/selenium/wiki/GettingStarted
A Reference card for Selenium 2 is available at http://refcardz.dzone.com/refcardz/getting-started-selenium-20
Example of Use
The primary classes are WebDriver and WebElement.
Page 1
In this example, we create a WebDriver, then get a page, then find an element and perform operations on it.
Finally, we check that the page title has changed to contain the expected value.
Except for the WebElement class, you could perform similar steps with Selenium 1, however, there is much more
flexibility because of the various implementations of WebDriver (for different browsers), and the operations on the
WebElement provide a good abstraction. Both of these class structures are discussed in the sections below.
Driver Implementations
WebDriver class is available in several implementations. The most general is RemoteWebDriver, which allows for
the programmatic specification of the test browser and its runtime. See
http://code.google.com/p/selenium/wiki/RemoteWebDriver. Here is the API:
This enables you to set up Firefox or other runtime environments, including a headless front-end. The various
subclasses of RemoteWebDriver, such as FirefoxDriver are simply preconfigured versions of RemoteWebDriver.
Page 2
The headless front-end typically uses HtmlUnitDriver (see http://en.wikipedia.org/wiki/HtmlUnit). Please note
some of the restrictions on use of JavaScript in this (it is simply running Rhino as a JavaScript engine, which is not
entirely the same as current production browsers. See http://code.google.com/p/selenium/wiki/HtmlUnitDriver).
Version Compatibility
Apparently there is an update required to the WebDriver library for most updates to browsers such as Firefox. See
http://stackoverflow.com/questions/7733824/selenium-webdriver-firefox-7-0-1-incompatibility for a discussion.
Hence, we found that we often could not update to new browsers, such as Firefox 10 during early 2012. Instead, we
are using Firefox 4.0, which dates back to early 2011, but matches the 2.8.0 version of the WebDriver library.
Page 3
Plus, you can search for an element within a given elements scope: findElementBy(By by) and findElementsBy(By
by).
There are special operations to handle select elements on your web page:
Page 4
You can create an instance of a Select from a WebElement that is known to represent a select element:
There is a subclass of WebElement called RenderedWebElement, which also offers position information (size, click
at location, etc.).
Handling Waits
See http://seleniumhq.org/docs/04_webdriver_advanced.html This describes two types of waits:
An explicit waits is code you define to wait for a certain condition to occur before proceeding further in the code.
The worst case of this is Thread.sleep(), which sets the condition to an exact time period to wait. There are some
convenience methods provided that help you write code that will wait only as long as required. WebDriverWait in
combination with ExpectedCondition is one way this can be accomplished.
WebDriver driver = new FirefoxDriver();
driver.get("http://somedomain/url_that_delays_loading");
WebElement myDynamicElement = (new WebDriverWait(driver, 10))
.until(new ExpectedCondition<WebElement>(){
@Override
public WebElement apply(WebDriver d) {
return d.findElement(By.id("myDynamicElement"));
}});
These waits are needed for clicks on the page elements, as the WebDriver itself doesnt wait (since the click may be
an ajax update)
Page 5
You could also change the constructor to throw an exception if a required element is not present. This is similar to
the verifyPage() behavior in other page models.
A further refinement is that you can specify the lookup of the WebEements by annotation, rather than performing
init yourself, if the page is created by the PageFactorys initElements method (see below). For example:
public class GoogleSearchPage {
protected WebDriver driver;
@FindBy(id="q")
private WebElement searchField;
@FindBy(name="btnG")
private WebElement searchButton;
public AnnotatedGoogleSearchPage(WebDriver driver) {
this.driver = driver;
}
public void open(String url) {
driver.get(url);
}
public void close() {
driver.quit();
}
public String getTitle() {
return driver.getTitle();
}
public void searchFor(String searchTerm) {
searchField.sendKeys(searchTerm);
searchButton.click();
}
Page 6
By default, each element on the page is looked up each and every time a method is called upon it. To change this
behavior, simply annotate the field with the {@link CacheLookup}.
In the above example, the PageFactory will create the class (running the constructor), then fill out each of the
elements. So the constructor can carry out a set of direct element lookups as part of verify() sequence, then have the
PageFactory carry out additional setup and checking.
Question: there is a NoSuchElementException. When is this thrown? Answer: when findElement fails, either in a
direct call or in a call from the PageFactory.
Page 7
Page 8
If you have a table with known columns, you can use a TableElement to access the data inside. In this example, the
BookRow represents a single <tr> within the table. Here is the test:
@Test
publicvoidtestList(){
ListBooksPagelistBooksPage=webdriver.open('/books/list',ListBooksPage)
assertEquals(["Id","Title","Author","Type","Used"],
listBooksPage.books.columnHeaders)
assertEquals(1,listBooksPage.books.size())
assertEquals("Cryptonomicon",listBooksPage.books.rows[0].title)
}
Here is the page:
classListBooksPageextendsWebDriverPage{
staticexpectedTitle="BookList"
TableElement<BookRow>books
staticelements={
books(By.xpath("//table"))
}
}
classBookRowextendsWebDriverPageElement{
Stringid
Stringtitle
Stringauthor
Stringtype
Stringused
staticelements={
id(By.xpath("td[1]"))
title(By.xpath("td[2]"))
author(By.xpath("td[3]"))
type(By.xpath("td[4]"))
used(By.xpath("td[5]"))
}
}
Page 9
Page 10
@Test
public void userShow() {
int rowCount = userListPage.getRowCount();
for (int i = 0; i < Math.min(rowCount, 4); i++) {
UserShowPage userShowPage = userListPage.open(i);
assertNotNull(userShowPage.username)
assertNotNull(userShowPage.email)
userListPage = userShowPage.goToList()
}
Page 11
Page 12