Vous êtes sur la page 1sur 12

Notes on Selenium WebDriver

Created 12/15/10
Updated 02/24/11, Updated 04/16/11, Updated 05/01/11, Updated 06/17/11, Updated 09/23/11, Updated 02/07/12,
Updated 02/15/12, Updated 02/27/12, Updated 03/23/12, Updated 05/12/13, Updated 10/19/14, Updated 12/30/14

http://stackoverflow.com/questions/6829465/getting-element-in-webdriver-not-by-xpath

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 in 2010 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.44.0, released October 2014. 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.

Browser specific drivers


The WebDrivers are browser-specific implementations of the Selenium 2 API. Unlike Selenium 1.x, which relied on
a single JavaScript implementation for all browsers to interact with their page elements, and so was limited by what
could be achieved through JavaScript, the WebDrivers interact with the browsers in the best way possible for that
browser.

What this in turn allows is:


1. Native keyboard and mouse interactions. This mimics the end user interactions with the browser much
better than what earlier versions of Selenium provided.
2. Problems related to same origin policy in Selenium 1 are now gone.
3. Better support for handling popups and dialogs. This includes some big problem items like upload and
downloads.

Mobile web support


Selenium 2 now supports running tests in mobile devices. IPhone and Android are the two platforms supported.
The way it works is that you have to install an app (a mini selenium server) on the device. This listens for
commands which then drive the web browser within the app.

This mechanism however will not allow us to test native apps in these devices.

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

Page 1
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.

public void theUserShouldBeAbleToTypeInQueryTerms() {


WebDriver driver = new FirefoxDriver();
driver.get("http://www.google.com");
WebElement queryField = driver.findElement(By.name("q"));
queryField.sendKeys("cats");
queryField.submit();

assertThat(driver.getTitle(), containsString("cats"));
}

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:

With the Capabilities interface defined as:

Page 2
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.

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 Interaction Model


Built around instances of WebElement. These can be created by locating an element on the page.

Locators include id, css, class, xpath, and link text.

Methods on WebElement include:

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"));
}});

Note that ExpectedCondition depends upon the Google Guava library.

An implicit wait is to tell WebDriver to poll the DOM for a certain amount of time when trying to find an element or
elements if they are not immediately available. The default setting is 0. Once set, the implicit wait is set for the life
of the WebDriver object instance.

WebDriver driver = new FirefoxDriver();


driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS);
driver.get("http://somedomain/url_that_delays_loading");
WebElement myDynamicElement = driver.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 Object Model


A WebElement is an element in the DOM of a page, and ways to find these elements include by name, by id, and by
css. This API is ideal for writing Page objects, as you can look up the WebElements as the page is opened. For
instance:

public class GoogleSearchPage {

protected WebDriver driver;


private WebElement q;
private WebElement btnG;

public GoogleSearchPage(WebDriver driver) {


this.driver = driver;
this.q = driver.findElement(By.name("q"));
this.btnG = driver.findElement(By.name("btnG"));
}

Page 5
public void open(String url) {
driver.get(url);
}

public void close() {


driver.quit();
}

public String getTitle() {


return driver.getTitle();
}

public void searchFor(String searchTerm) {


q.sendKeys(searchTerm);
btnG.click();
}

public void typeSearchTerm(String searchTerm) {


q.sendKeys(searchTerm);
}

public void clickOnSearch() {


btnG.click();
}
}

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
public void typeSearchTerm(String searchTerm) {
searchField.sendKeys(searchTerm);
}

public void clickOnSearch() {


searchButton.click();
}
}

Would be created using

GoogleSearchPage page = PageFactory.initElements(driver, GoogleSearchPage.class);

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.

Tips and Suggestions


There are no specific Page Object superclasses provided, so create your own.

The PageFactory is your friend, as it handles the initialization of those objects for which the findElement processing
is useful. This includes all pages with specific named, idd, or classed elements.

Using WebDriver through the WebDriver Grails Plugin


The current version of the plug-in is 0.3.3, released November 2011. The plug-in provides a class that is a wrapper
for the page driver, called WebDriverPage. For each page that you want to interact with in a test, you create a Page
class, extending from WebDriverPage.

The command to run functional tests is:


grailstestappfunctional

To request the Firefox browser instead of HtmlUnit, use:


grailsDwebdriver.browser=firefoxtestappfunctional

The Grails environment (as of Grails 1.3.7) supports jUnit 4.8.1.

Support for JavaScript when using HtmlUnit


See http://stackoverflow.com/questions/7531818/javascript-processing-in-selenium-and-htmlunit

That points out that little JavaScript support exists.

To disable JavaScript when using HtmlUnit, code the following:

@Before
public void openXXXPage() {
WebDriver driver = webdriver.driver;
if (driver instanceof HtmlUnitDriver) {
((HtmlUnitDriver) driver).setJavascriptEnabled(false);
}

Page 7
xxxPage = webdriver.open('/', XXXPage.class)
}

Page Abstraction Object Model


WebDriverPage is a very handy class as is supports a number of page-definition and validation features that you
would otherwise have to write yourself. It supports an expectedTitle value, and an elements specification.

Subclasses of WebDriverPage have properties that allow access to form fields and text on the page and methods that
you call to jump to other pages. The tests then just interact with these page objects, making assertions and
navigating around your application. If you change your HTML around (change the ID of an element, or add an extra
div for example) you only have to modify a specific part of the page object and not every test that interacted with
that element.

Essentially, using a subclass of WebDriverPage is like always instantiating your page with the PageFactory at the
Java level. Reviewing the source of WebDriverPage showed us how it organizes the checks for title, the checks for
elements, etc.

However, you could define a constructor for the page, and carry out specific findElement calls to create
WebElements. For verification, you can override verify(boolean newPage), and call the superclass method then
code your verification steps.

WebElement Object Model


Provides a set of classes for the WebElements and provides an automatic way to create pages such that the Driver is
passed in and the PageFactory is called. This provides an even higher-level API (or at least more syntactically
shorter) to the WebDriver and page objects.

For instance, LinkElement offers the clickTo() method which takes a class object. The clickTo method will click the
link, a return an instance of the specified page class (which would be for the destination of the link).

For documentation on the various specific subclasses of web element, see https://bitbucket.org/refactor/grails-
webdriver/src/9fad4997c4cc/test/unit/pages/test/TestOnePage.groovy. The full list of such classes is shown below:

Page 8
Most of these are subclasses of WebDriverPageElement.

NavElements include Links, and implement the following:

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
}

Here is the source for TableElement:


packageorg.codehaus.groovy.grails.plugins.webdriver

importorg.openqa.selenium.By
importorg.openqa.selenium.WebElement

publicclassTableElement<T>extendsWebDriverPageElement{

List<String>columnHeaders
List<T>rows

staticelements={
columnHeaders(By.xpath("thead/tr/th"))
rows(find:By.xpath("tbody/tr"),listElement:
{config.tableRowElement?:config.genericTypes?.get(0)?:WebElement})
}

publicintsize(){
returngetRowCount()
}

publicintgetRowCount(){
rows.size()
}

publicTgetAt(introw){
rows.get(row)
}

publicWebElementgetCell(introw,intcol){
defcells=webElement.findElements(By.xpath("tbody/tr[${row+1}]/td[${col+1}]"))
if(cells.size()==1){
returncells[0]
}
intsize=size()
if(size==0){
thrownewIllegalArgumentException("Thetablehasnorows")
}
if(row<0||row>size1){
thrownewIllegalArgumentException
("Thetabledoesn'thaverow${row}.Validrowsare0..${size1}")
}
Listcolumns=webElement.findElements(By.xpath("tbody/tr[${row+1}]/td"))
if(columns.size()==0){
thrownewIllegalArgumentException("Row${row}hasnocolumns")
}
if(col<0||col>columns.size()1){
thrownewIllegalArgumentException
("Row${row}doesn'thavecolumn${col}.Validcolumnsare0..${columns.size()1}")
}
thrownewIllegalArgumentException("Can'tfindacellat${row},${col}inthetable")
}

publicWebElementgetLink(introw,intcol){
getCell(row,col).findElement(By.tagName("a"))
}

publicListgetCells(){
getRows().collect{WebElementrow>
row.findElements(By.xpath("td"))
}
}
}

Page 10
Example Test Class
The test classes should extend from GroovyTestCase.

class UserTests extends GroovyTestCase {


@Rule
public WebDriverHelper webdriver = new WebDriverHelper()

HomePage homePage
UserListPage userListPage

@Before
public void openHomePage() {
WebDriver driver = webdriver.driver;
if (driver instanceof HtmlUnitDriver) {
((HtmlUnitDriver) driver).setJavascriptEnabled(false);
}

homePage = webdriver.open('/', HomePage.class)


userListPage = homePage.openUserList()
}

@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()
}
}
}

Creating Abstract/Base Classes for Grails Pages


Unlike the Grails Plug-in for Selenium, the Grails Plug-in for WebDriver has no built-in classes for the standard
Grails pages (i.e., list, show, edit, create). So we have implemented a version of them. Here are the design notes:

AbstractPage
Simply extends WebDriverPage

AbstractFormPage (extends AbstractPage)


- get field names returns list of strings
- get field value (given the field name)
- set field value (given the field name)
o must have some special support for dates and selects

BaseListPage (extends AbstractPage)


- get number of rows
- get number of cols
- get a cell value
- change sort column
- page forward
- page backward

BaseShowPage (extends AbstractPage)


- get field names returns list of strings
- get field value (given the field name)

Page 11
o returns the value simply as a string

BaseEditPage (extends AbstractFormPage)


- offers a submit

BaseCreatePage (extends AbstractFormPage)


- offers a submit

Page 12

Vous aimerez peut-être aussi