Notes on Selenium WebDriver

chulavistajuniorΚινητά – Ασύρματες Τεχνολογίες

10 Δεκ 2013 (πριν από 3 χρόνια και 8 μήνες)

179 εμφανίσεις

Page
1

Notes on
Selenium

WebDriver

Created 12/1
5/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

Introduction

Selenium

WebDriver
(also called Selenium 2)
is a second generation
web ap
plication tes
t facility
. It is the
result

of
efforts of Google, Tho
ughtWorks, 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 b
rowser.
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 Sele
nium WebDriver tests is much more object
-
oriented, with objects for page elements and
entire pages. Hence, there isn’t a big list of method calls on the main Selenium object, but a set of objects that are
created using methods on the Driver ob
ject (the eq
uivalent 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 AP
I
, which can help with migration, though that is not
discussed here
. It is recommended that new development use Selenium 2, with either of the API’s 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 browse
rs 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 sam
e 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


A Reference card for Selenium 2 is available at
http
://refcardz.dzone.com/refcardz/getting
-
started
-
selenium
-
20

Page
2

Example of Use

The primary
classes

are WebDriver and WebElement.


public void theUserShouldBeAbleToTypeInQueryTerms() {


WebDriver driver = new FirefoxDriver();


driver.get("http://www.google.co
m");


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 ele
ment and perform operations on it.


Finally, we check that the pa
ge 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 v
arious 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 sever
al 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/wik
i/RemoteWebDriver
. Here is the API:




With the
Capabilities
interface defined as:


Page
3

This enables you to set up Firefox or other run
time environments, including a h
eadless 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 Java
Script

in this (it is simply running R
hino as a JavaScript engine, which is not
entirely the same as current production browsers. See
http://code.google.com/p/selenium/wiki/HtmlUnitDriv
er
).

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. I
nstead, we
are using Firef
ox 4.0, which dates

back to early 2011, but matches the 2.8.0 version of the WebDriver library.

Page Interaction Model

Built around instance
s

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
4



Plus, you can search for an element within a given element’s scope: findElementBy(By by) and findElementsBy(By
by).


There are special operations to handle select elements on your web page:


Page
5


You can create an instance o
f 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.
Th
e 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 ExpectedCondit
ion 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>(){


@Overrid
e


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().i
mplicitlyWait(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 does
n’t wait (since the click may be
an ajax update)

Page Object Model

A WebElement is an element in the DOM of a page, and w
ays to find
these elements
include by name, by id, and by
css.

This
API

is ideal for writing
P
age objects, as you can look up the WebE
lements 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.findElem
ent(By.name("q"));



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


}



Page
6


public void open(String url) {



driver.get(url);


}



public void close() {



driver.quit();


}




public String getTitle() {



return driver.getTitle();


}



public void searchFo
r(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 exce
ption if a required element is not present. This is similar to
the verifyPage() behavior in other page models.


A further refinement i
s that you can specify the lookup of the WebE
ements by annotation, rather
than performing
init yourself, if the page is c
reated by the PageFactory’s 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 searchF
or(String searchTerm) {



searchField.sendKeys(searchTerm);



searchButton.click();


}

Page
7



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 {@lin
k 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
PageFa
ctory 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, a
s it handles the initialization of those objects for which the findElement processing
is useful. This includes all pages with specific named, id’d, or classed elements.

U
sing WebDriver through the
WebDriver
Grails Plugin

The current version
of the plug
-
in
is 0.3.3, released November 2011.
The plug
-
in p
rovides a class that i
s a wrapper
for the page driver
, called WebDriverPage. F
or each page that you want to interact with

in a test, you create a Page
class, extending from WebDriverPage.


The c
ommand
to run functional tests
is:

grails test
-
app

functional


To request the

Firefox browser

instead of HtmlUnit
, use:

grails
-
Dwebdriver.browser=firefox test
-
app

functional


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/753181
8/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

openXXX
Page() {


WebDriver driver =
webdriver
.
driv
er
;


if

(driver
instanceof

HtmlUnitDriver) {


((HtmlUnitDriver) driver).setJavascriptEnabled(
false
);


}

Page
8



xxx
Page = webdriver.open(
'/'
, XXX
Page.
class
)


}

Page Abstraction Object Model

WebDriverP
a
ge
is a very handy cla
ss as is supports a number of page
-
definition and validation features that you
would otherwise have to write yourself. It
supports a
n

expectedTitle value, and an elements specification.


Subclasses of WebDriverPage
have properties that allow access to for
m 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, o
r 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 t
o create
WebElements. Fo
r 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 c
lickTo(
)
method
which takes a class object. The c
lickTo 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
9


Most of these are subclas
ses 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


public

void

testList
()

{


ListBooksPage

listBooksPage

=

webdriver
.
open
(
'
/
books
/
list
'
,

ListBooksPage
)



assertEquals
([
"Id"
,

"Title"
,

"Author"
,

"Type"
,

"Used"
],




listBooksPage
.
books
.
colum
nHeaders
)


assertEquals
(
1
,

listBooksPage
.
books
.
size
())


assertEquals
(
"Cryptonomicon"
,

listBooksPage
.
books
.
rows
[
0
].
title
)


}


Here is the page:


class

ListBooksPage

extends

WebDriverPage

{


static

expectedTitle

=

"Book List"



TableEl
ement
<
BookRow
>

books



static

elements

=

{


books
(
By
.
xpath
(
"//table"
))


}

}


class

BookRow

extends

WebDriverPageElement

{


String

id


String

title


String

author


String

type


String

used



static

elements

=

{


id
(
By
.
x
path
(
"td[1]"
))


title
(
By
.
xpath
(
"td[2]"
))


author
(
By
.
xpath
(
"td[3]"
))


type
(
By
.
xpath
(
"td[4]"
))


used
(
By
.
xpath
(
"td[5]"
))


}

}


Page
10

Here is the source for TableElement
:


package org.codehaus.groovy.grails.plugins.webdriver


import or
g.openqa.selenium.By

import org.openqa.selenium.WebElement


public class TableElement<T> extends WebDriverPageElement {



List<String> columnHeaders


List<T> rows



static elements = {


columnHeaders(By.xpath("thead/tr/th"))


rows(fi
nd: By.xpath("tbody/tr"), listElement:


{config.tableRowElement ?: config.genericTypes?.get(0) ?: WebElement})


}



public int size() {


return getRowCount()


}



public int getRowCount() {


rows.size()


}



pu
blic T getAt(int row) {


rows.get(row)


}



public WebElement getCell(int row, int col) {


def cells = webElement.findElements(By.xpath("tbody/tr[${row + 1}]/td[${col + 1}]"))


if (cells.size() == 1) {


return cells[0]


}


int size = size()


if (size == 0) {


throw new IllegalArgumentException("The table has no rows")


}


if (row < 0 || row > size
-

1) {


throw new IllegalArgumentException


("The table
doesn't have row ${row}. Valid rows are 0..${size
-

1}")


}


List columns = webElement.findElements(By.xpath("tbody/tr[${row + 1}]/td"))


if (columns.size() == 0) {


throw new IllegalArgumentException("Row ${row} has no colu
mns")


}


if (col < 0 || col > columns.size()
-

1) {


throw new IllegalArgumentException


("Row ${row} doesn't have column ${col}. Valid columns are 0..${columns.size()
-

1}")


}


throw new IllegalArgumentE
xception("Can't find a cell at ${row},${col} in the table")


}



public WebElement getLink(int row, int col) {


getCell(row, col).findElement(By.tagName("a"))


}



public List getCells() {


getRows().collect { WebElement row
-
>



row.findElements(By.xpath("td"))


}


}

}

Example Test Class

The test classes should extend from GroovyTestCase.


class

UserTests
extends

GroovyTestCase {

Page
11


@Rule


public

WebDriverHelper
webdriver

=
new

WebDriverHelper()



HomePa
ge
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 select
s


Base
ListPage

(extends AbstractPage)

-

get number of rows

-

get number of cols

-

get a cell value

-

change sort column

-

page forward

-

page backward


Base
ShowPage

(extends AbstractPage)

-

get field names returns list of strings

-

get field value (given the field name)

o

returns the value simply as a string


Base
EditPage (extends AbstractFormPage)

-

offers a submit


Page
12

Base
CreatePage (extends AbstractFormPage)

-

offers a submit