114
Chapter 6. Selenium 1 (Selenium RC)
CHAPTER
SEVEN
TEST
DESIGN CONSIDERATIONS
7.1
Introducing Test Design
We’ve provided in
this chapter information that will be useful to both those new to test
automation and
for the experienced
QA professional. Here we describe the most common types of automated tests.
We also describe
‘design patterns’ commonly used in test automation for improving the
maintenance
and extensibily of
your automation suite. The more experienced reader will find these interesting
if not
already using these
techniques.
7.2
Types of Tests
What parts of your
application should you test? That depends on aspects of your project: user
expectations,
time allowed for
the project, priorities set by the project manager and so on. Once the project
boundaries are
defined though, you, the tester, will certainly make many decisions on what to
test.
We’ve created a few
terms here for the purpose of categorizing the types of test you may perform on
your web
application. These terms are by no means standard, although the concepts we
present here are
typical for
web-application testing.
7.2.1
Testing Static Content
The simplest type
of test, a content test, is a simple test for the existence of a
static, non-changing, UI
element. For
instance
• Does each page
have its expected page title? This can be used to verify your test found an
expected
page after
following a link.
• Does the
application’s home page contain an image expected to be at the top of the page?
• Does each page of
the website contain a footer area with links to the company contact page,
privacy policy, and
trademarks information?
• Does each page
begin with heading text using the <h1> tag? And, does each page have the correct
text within that
header?
You may or may not
need content tests. If your page content is not likely to be affected then it
may
be more efficient
to test page content manually. If, for example, your application involves files
being
moved to different
locations, content tests may prove valuable.
115
Selenium
Documentation, Release 1.0
7.2.2
Testing Links
A frequent source
of errors for web-sites is broken links or missing pages behind links. Testing
involves
clicking each link
and verifying the expected page. If static links are infrequently changed then
manual
testing may be
sufficient. However if your web designers frequently alter links, or if files
are occasionally
relocated, link
tests should be automated.
7.2.3
Function Tests
These would be
tests of a specific function within your application, requiring some type of
user input,
and returning some
type of results. Often a function test will involve multiple pages with a
formbased
input page
containing a collection of input fields, Submit and Cancel operations, and one
or more
response pages.
User input can be via text-input fields, check boxes, drop-down lists, or any
other
browser-supported
input.
Function tests are
often the most complex tests you’ll automate, but are usually the most
important.
Typical tests can
be for login, registration to the site, user account operations, account
settings changes,
complex data
retrieval operations, among others. Function tests typically mirror the
user-scenarios used
to specify the
features and design or your application.
7.2.4
Testing Dynamic Elements
Often a web page
element has a unique identifier used to uniquely locate that element within the
page.
Usually these are
implemented using the html tag’s ‘id’ attribute or its ‘name’ attribute. These
names
can be a static,
i.e unchanging, string constant. They can also be dynamically generated values
that vary
each instance of
the page. For example, some web servers might name a displayed document doc3861
one instance of a
page, and ‘doc6148’ on a different instance of the page depending on what
‘document’
the user was
retrieving. A test script verifying that a document exists may not have a
consistent identifier
to use for locating
that document. Often, dynamic elements with varying identifiers are on some
type of
result page based
on a user action. This though certainly depends on the function of the web
application.
Here’s an example.
<input type=
"checkbox" value= "true"
id= "addForm:_ID74:_ID75:0:_ID79:0:
checkBox" />
This shows an HTML
tag for a check box. Its ID (addForm:_ID74:_ID75:0:_ID79:0:checkBox) is a
dynamically
generated value. The next time the same page is opened it will likely be a
different value.
7.2.5
Ajax Tests
Ajax is a
technology which supports dynamically changing user interface elements which
can dynamically
change without the
browser having to reload the page, such as animation, RSS feeds, and real-time
data updates among
others. There’s a countless ways Ajax can be used to update elements on a web
page. But, the easy
way to think of this is that in Ajax-driven applications, data can retrieved
from the
application server
and then displayed on the page without reloading the entire page. Only a
portion of
the page, or
strictly the element itself is reloaded.
116
Chapter 7. Test Design Considerations
Selenium
Documentation, Release 1.0
7.3
Validating Results
7.3.1
Assert vs. Verify
When should you use
an assert command and when should you use a verify command? This is up to
you. The difference
is in what you want to happen when the check fails. Do you want your test to
terminate, or to
continue and simply record that the check failed?
Here’s the
trade-off. If you use an assert, the test will stop at that point and not run
any subsequent
checks. Sometimes,
perhaps often, that is what you want. If the test fails you will immediately
know the
test did not pass.
Test engines such as TestNG and JUnit have plugins for commonly used
development
environments (Chap
5) which conveniently flag these tests as failed tests. The advantage: you have
an
immediate visual of
whether the checks passed. The disadvantage: when a check does fail, there are
other checks which
were never performed, so you have no information on their status.
In contrast, verify
commands will not terminate the test. If your test uses only verify commands
you are
guaranteed
(assuming no unexpected exceptions) the test will run to completion whether the
checks find
defects or not. The
disadvantage: you have to do more work to examine your test results. That is,
you
won’t get feedback
from TestNG or JUnit. You will need to look at the results of a console
printout or
a log output. And
you will need to take the time to look through this output every time you run
your
test. If you are
running hundreds of tests, each with its own log, this will be time-consuming,
and the
immediate feedback
of asserts will be more appropriate. Asserts are more commonly used then
verifys
due to their
immediate feedback.
7.3.2
Trade-offs: assertTextPresent, assertElementPresent, assertText
You should now be
familiar with these commands, and the mechanics of using them. If not, please
refer
to Chapter 3 first.
When constructing your tests, you will need to decide
• Do I only check
that the text exists on the page? (verify/assertTextPresent)
• Do I only check
that the HTML element exists on the page? That is, the text, image, or other
content is not to
be checked, only the HTML tag is what is relevant. (verify/assertElementPresent)
• Must I test both,
the element and its text content? (verify/assertText)
There is no right
answer. It depends on the requirements for your test. Which, of course, depend
on the
requirements for
the application you’re testing. If in doubt, use assertText since
this is the strictest type
of checkpoint. You
can always change it later but at least you won’t be missing any potential
failures.
Verify/assertText
is the most specific test type.
This can fail if either the HTML element (tag) OR the
text is not what
your test is expecting. Perhaps your web-designers are frequently changing the
page
and you don’t want
your test to fail every time they do this because the changes themselves are
expected
periodically.
However, assume you still need to check that something is
on the page, say a paragraph,
or heading text, or
an image. In this case you can use verify/assertElementPresent.
It will ensure that a
particular type of
element exists (and if using XPath can ensure it exists relative to other objects
within
the page). But you
don’t care what the content is. You only care that a specific element, say, an
image,
is at a specific
location.
Getting a feel for
these types of decisions will come with time and a little experience. They are
easy
concepts, and easy
to change in your test.
7.3.
Validating Results 117
Selenium
Documentation, Release 1.0
7.4
Location Strategies
7.4.1
Choosing a Location Strategy
There are multiple
ways of selecting an object on a page. But what are the trade offs of each of
these
locator types?
Recall we can locate an object using
• the element’s ID
• the element’s
name attribute
• an XPath
statement
• by a links text
• document object
model (DOM)
Using an element ID
or name locator is the most efficient in terms of test performance, and also
makes
your test code more
readable, assuming the ID or name within the page source is well-named. XPath
statements take
longer to process since the browser must run its XPath processor. XPath has
been known
to be especially
slow in Internet Explorer version 7. Locating via a link’s text is often
convenient and
performs well. This
technique is specific to links though. Also, if the link text is likely to
change
frequently,
locating by the <a> element would be the better choice.
Sometimes though,
you must use an XPath locator. If the page source does not have an ID or name
attribute you may
have no choice but to use an XPath locator. (DOM locators are no longer
commonly
used since XPath
can do everything they can and more. DOM locators are available simply to
support
legacy tests.)
There is an
advantage to using XPath that locating via ID or name attributes do not have.
With XPath
(and DOM) you can
locate an object with respect to another object on the page. For example, if
there is
a link that must
occur within the second paragraph within a <div> section, you can use
XPath to specify
this. With ID and
name locators, you can only specify that they occur on the page that is,
somewhere
on the page. If you
must test that an image displaying the company logo appears at the top of the
page
within a header
section XPath may be the better locator.
7.4.2
Locating Dynamic Elements
As was described
earlier in the section on types of tests, a dynamic element is a page element
whose
identifer varies
with each instance of the page. For example,
<a class=
"button" id= "adminHomeForm"
onclick= "return oamSubmitForm(’adminHomeForm’,
’adminHomeForm:_ID38’);"
href= "#" >View
Archived Allocation Events</a>
This HTML anchor
tag defines a button with an ID attribute of “adminHomeForm”. It’s a fairly
complex
anchor tag when
compared to most HTML tags, but it is still a static tag. The HTML will be the
same
each time this page
is loaded in the browser. Its ID remains constant with all instances of this
page. That
is, when this page
is displayed, this UI element will always have this Identifier. So, for your
test script
to click this
button you simply need to use the following selenium command.
click adminHomeForm
Or, in Selenium 1.0
118
Chapter 7. Test Design Considerations
Selenium
Documentation, Release 1.0
selenium.click( "adminHomeForm"
);
Your application,
however, may generate HTML dynamically where the identifier varies on different
instances of the
webpage. For instance, HTML for a dynamic page element might look like this.
<input type=
"checkbox" value= "true"
id= "addForm:_ID74:_ID75:0:_ID79:0:checkBox"
name= "addForm:_ID74:_ID75:0:_ID79:0:checkBox"
/>
This defines a
checkbox. Its ID and name attributes (both
addForm:_ID74:_ID75:0:_ID79:0:checkBox)
are dynamically
generated values. In this case, using a standard locator would look something
like the
following.
click
addForm:_ID74:_ID75:0:_ID79:0:checkBox
Or, again in
Selenium-RC
selenium.click( "addForm:_ID74:_ID75:0:_ID79:0:checkBox"
);
Given the
dynamically generated Identifier, this approach would not work. The next time
this page is
loaded the
Identifier will be a different value from the one used in the Selenium command
and therefore,
will not be found.
The click operation will fail with an “element not found” error.
To correct this, a
simple solution would be to just use an XPath locator rather than trying to use
an ID
locator. So, for
the checkbox you can simply use
click //input
Or, if it is not
the first input element on the page (which it likely is not) try a more
detailed XPath
statement.
click //input[3]
Or
click
//div/p[2]/input[3]
If however, you do
need to use the ID to locate the element, a different solution is needed. You
can
capture this ID from
the website before you use it in a Selenium command. It can be done like this.
String[] checkboxids
= selenium.getAllFields();
// Collect all input IDs on page.
for(String
checkboxid:checkboxids) {
if(checkboxid.contains( "addForm"
)) {
selenium.click(expectedText);
}
}
This approach will
work if there is only one check box whose ID has the text ‘expectedText’
appended
to it.
7.4.
Location Strategies 119
Selenium
Documentation, Release 1.0
7.4.3
Locating Ajax Elements
As was presented in
the Test Types subsection above, a page element implemented with Ajax is an
element that can be
dynamically refreshed without having to refresh the entire page. The best way
to
locate and verify
an Ajax element is to use the Selenium 2.0WebDriver API. It was specifically
designed
to address testing
of Ajax elements where Selenium 1 has some limitations.
In Selenim 2.0 you
use the waitFor() method to wait for a page element to become available. The
parameter is a By
object which is howWebDriver implements locators. This is explained in detail
in the
WebDriver chapters.
To do this with
Selenium 1.0 (Selenium-RC) a bit more coding is involved, but it isn’t
difficult. The
approach is to
check for the element, if it’s not available wait for a predefined period and
then again
recheck it. This is
then executed with a loop with a predetermined time-out terminating the loop if
the
element isn’t
found.
Let’s consider a
page which brings a link (link=ajaxLink) on click of a button on page (without
refreshing
the page) This
could be handled by Selenium using a for loop.
// Loop initialization.
for (int
second = 0;; second++)
{
// If loop is reached
60 seconds then break the loop.
if (second
>= 60) break;
// Search for element
"link=ajaxLink" and if available then break loop.
try { if (selenium.isElementPresent( "link=ajaxLink"
)) break; } catch
(Exception e) // Pause for 1 second.
Thread.sleep(1000);
}
This certainly
isn’t the only solution. Ajax is a common topic in the user forum and we
recommend
searching previous
discussions to see what others have done.
7.5
Wrapping Selenium Calls
As with any
programming, you will want to use utility functions to handle code that would
otherwise be
duplicated
throughout your tests. One way to prevent this is to wrap frequently used
selenium calls with
functions or class
methods of your own design. For example, many tests will frequently click on a
page
element and wait
for page to load multiple times within a test.
selenium.click(elementLocator);
selenium.waitForPageToLoad(waitPeriod);
Instead of
duplicating this code you could write a wrapper method that performs both
functions.
/**
* Clicks and Waits for
page to load.
*
* param elementLocator
* param waitPeriod
*/
120
Chapter 7. Test Design Considerations
Selenium
Documentation, Release 1.0
public
void clickAndWait(String
elementLocator, String waitPeriod) {
selenium.click(elementLocator);
selenium.waitForPageToLoad(waitPeriod);
}
7.5.1
‘Safe Operations’ for Element Presence
Another common
usage of wrapping Selenium methods is to check for presence of an element on
page
before carrying out
some operation. This is sometimes called a ‘safe operation’. For instance, the
following method
could be used to implement a safe operation that depends on an expected element
being present.
/**
* Selenum-RC -- Clicks
on element only if it is available on page.
*
* param elementLocator
*/
public
void safeClick(String
elementLocator) {
if(selenium.isElementPresent(elementLocator))
{
selenium.click(elementLocator);
} else {
// Using the TestNG API
for logging
Reporter.log( "Element:
" +elementLocator+ ", is not available on
page - +selenium.getLocation());
}
}
This example uses
the Selenium 1 API but Selenium 2 also supports this.
/**
* Selenium-WebDriver --
Clicks on element only if it is available on page.
*
* param elementLocator
*/
public
void safeClick(String
elementLocator) {
WebElement webElement = getDriver().findElement(By.XXXX(elementLocator));
if(webElement
!= null) {
selenium.click(elementLocator);
} else {
// Using the TestNG API
for logging
Reporter.log( "Element:
" +elementLocator+ ", is not available on
page - + getDriver().getUrl());
}
}
In this second
example ‘XXXX’ is simply a placeholder for one of the multiple location methods
that
can be called here.
Using safe methods
is up to the test developer’s discretion. Hence, if test execution is to be
continued,
even in the wake of
missing elements on the page, then safe methods could be used, while posting a
message to a log
about the missing element. This, essentially, implements a ‘verify’ with a
reporting
mechanism as
opposed to an abortive assert. But if element must be available on page in
order to be
able to carry out
further operations (i.e. login button on home page of a portal) then this safe
method
technique should
not be used.
7.5.
Wrapping Selenium Calls 121
Selenium
Documentation, Release 1.0
7.6
UI Mapping
A UI map is a
mechanism that stores all the locators for a test suite in one place for easy
modification
when identifiers or
paths to UI elements change in the AUT. The test script then uses the UI Map
for
locating the
elements to be tested. Basically, a UI map is a repository of test script
objects that correspond
to UI elements of
the application being tested.
What makes a UI map
helpful? Its primary purpose is making test script management much easier.
When a locator
needs to be edited, there is a central location for easily finding that object,
rather than
having to search
through test script code. Also, it allows changing the Identifier in a single
place, rather
than having to make
the change in multiple places within a test script, or for that matter, in
multiple test
scripts.
To summarize, a UI
map has two significant advantages.
• Using a
centralized location for UI objects instead of having them scattered throughout
the script.
This makes script
maintenance more efficient.
• Cryptic HTML
Identifiers and names can be given more human-readable names improving the
readability of test
scripts.
Consider the
following, difficult to understand, example (in java).
public
void testNew() throws
Exception {
selenium.open( "http://www.test.com"
);
selenium.type( "loginForm:tbUsername"
, "xxxxxxxx" );
selenium.click( "loginForm:btnLogin"
);
selenium.click( "adminHomeForm:_activitynew"
);
selenium.waitForPageToLoad( "30000"
);
selenium.click( "addEditEventForm:_IDcancel"
);
selenium.waitForPageToLoad( "30000"
);
selenium.click( "adminHomeForm:_activityold"
);
selenium.waitForPageToLoad( "30000"
);
}
This script would
be hard to follow for anyone not familiar with the AUT’s page source. Even
regular
users of the
application might have difficulty understanding what thus script does. A better
script could
be:
public
void testNew() throws
Exception {
selenium.open( "http://www.test.com"
);
selenium.type(admin.username, "xxxxxxxx"
);
selenium.click(admin.loginbutton);
selenium.click(admin.events.createnewevent);
selenium.waitForPageToLoad( "30000"
);
selenium.click(admin.events.cancel);
selenium.waitForPageToLoad( "30000"
);
selenium.click(admin.events.viewoldevents);
selenium.waitForPageToLoad( "30000"
);
}
Now, using some
comments and whitespace along with the UI Map identifiers makes a very readable
script.
122
Chapter 7. Test Design Considerations
Selenium
Documentation, Release 1.0
public
void testNew() throws
Exception {
// Open app url.
selenium.open( "http://www.test.com"
);
// Provide admin
username.
selenium.type(admin.username, "xxxxxxxx"
);
// Click on Login
button.
selenium.click(admin.loginbutton);
// Click on Create New
Event button.
selenium.click(admin.events.createnewevent);
selenium.waitForPageToLoad( "30000"
);
// Click on Cancel
button.
selenium.click(admin.events.cancel);
selenium.waitForPageToLoad( "30000"
);
// Click on View Old
Events button.
selenium.click(admin.events.viewoldevents);
selenium.waitForPageToLoad( "30000"
);
}
There are various
ways a UI Map can be implemented. One could create a class or struct which only
stores public
String variables each storing a locator. Alternatively, a text file storing key
value pairs
could be used. In
Java, a properties file containing key/value pairs is probably best method.
Consider a property
file prop.properties which assigns as ‘aliases’ reader-friendly
identifiers for UI
elements from the
previous example.
admin.username =
loginForm:tbUsername
admin.loginbutton =
loginForm:btnLogin
admin.events.createnewevent
= adminHomeForm:_activitynew
admin.events.cancel =
addEditEventForm:_IDcancel
admin.events.viewoldevents
= adminHomeForm:_activityold
The locators will
still refer to html objects, but we have introduced a layer of abstraction
between the
test script and the
UI elements. Values are read from the properties file and used in the Test
Class to
implement the UI
Map. For more on Java properties files refer to the following link.
7.7
Page Object Design Pattern
Page Object is a
Design Pattern which has become popular in test automation for enhancing test
maintenance
and reducing code
duplication. A page object is an object-oriented class that serves as an
interface
to a page of your
AUT. The tests then use the methods of this page object class whenever they
need to
interact with that
page of the UI. The benefit is that if the UI changes for the page, the tests
themselves
don’t need to
change, only the code within the page object needs to change. Subsequently all
changes
to support that new
UI are located in one place.
The Page Object
Design Pattern provides the following advantages.
1. There is clean
separation between test code and page specific code such as locators (or their
use if
you’re using a UI
map) and layout.
7.7.
Page Object Design Pattern 123
Selenium
Documentation, Release 1.0
2. There is single
repository for the services or operations offered by the page rather than
having these
services scattered
through out the tests.
In both cases this
allows any modifications required due to UI changes to all be made in one
place.
Useful information
on this technique can be found on numerous blogs as this ‘test design pattern’
is
becoming widely used.
We encourage the reader who wishes to know more to search the
internet for
blogs on this
subject. Many have written on this design pattern and can provide useful
tips beyond the
scope of this user
guide. To get you started, though, we’ll illustrate page objects with a simple
example.
First, consider an
example, typical of test automation, that does not use a page object.
/***
* Tests login feature
*/
public
class Login {
public
void testLogin() {
selenium.type( "inputBox"
, "testUser" );
selenium.type( "password"
, "my supersecret password" );
selenium.click( "sign-in"
);
selenium.waitForPageToLoad( "PageWaitPeriod"
);
Assert.assertTrue(selenium.isElementPresent( "compose
button" ),
"Login was
unsuccessful" );
}
}
There are two
problems with this approach.
1. There is no
separation between the test method and the AUTs locators (IDs in this example);
both
are intertwined in
a single method. If the AUT’s UI changes its identifiers, layout, or how a
login
is input and
processed, the test itself must change.
2. The id-locators
would be spread in multiple tests, all tests that had to use this login page.
Applying the page
object techniques this example could be rewritten like this in the following
example
of a page object
for a Sign-in page.
/**
* Page Object
encapsulates the Sign-in page.
*/
public
class SignInPage {
private
Selenium selenium;
public
SignInPage(Selenium selenium) {
this.selenium
= selenium;
if(!selenium.getTitle().equals( "Sign
in page" )) {
throw
new IllegalStateException( "This
is not sign in page, current +selenium.getLocation());
}
}
/**
* Login as valid user
*
124
Chapter 7. Test Design Considerations
Selenium
Documentation, Release 1.0
* @param userName
* @param password
* @return HomePage
object
*/
public
HomePage loginValidUser(String
userName, String password) {
selenium.type( "usernamefield"
, userName);
selenium.type( "passwordfield"
, password);
selenium.click( "sign-in"
);
selenium.waitForPageToLoad( "waitPeriod"
);
return
new HomePage(selenium);
}
}
and page object for
a Home page could look like this.
/**
* Page Object
encapsulates the Home Page
*/
public
class HomePage {
private
Selenium selenium;
public
HomePage(Selenium selenium) {
if (!selenium.getTitle().equals( "Home
Page of logged in user" )) {
throw
new IllegalStateException( "This
is not Home Page of logged "is: " +selenium.getLocation());
}
}
public
HomePage manageProfile() {
// Page encapsulation
to manage profile functionality
return
new HomePage(selenium);
}
/*More methods offering
the services represented by Home Page
of Logged User. These
methods in turn might return more Page Objects
for example click on
Compose mail button could return ComposeMail class object*/
}
So now, the login
test would use these two page objects as follows.
/***
* Tests login feature
*/
public
class TestLogin {
public
void testLogin() {
SignInPage signInPage = new SignInPage(selenium);
HomePage homePage = signInPage.loginValidUser( "userName"
, "password" );
Assert.assertTrue(selenium.isElementPresent( "compose
button" ),
"Login was
unsuccessful" );
}
}
7.7.
Page Object Design Pattern 125
Selenium
Documentation, Release 1.0
There is a lot of
flexibility in how the page objects may be designed, but there are a few basic
rules
for getting the
desired maintainability of your test code. Page objects themselves should never
be make
verifications or
assertions. This is part of your test and should always be within the test’s
code, never in
an page object. The
page object will contain the representation of the page, and the services the
page
provides via
methods but no code related to what is being tested should be within the page
object.
There is one,
single, verification which can, and should, be within the page object and that
is to verify
that the page, and
possibly critical elements on the page, were loaded correctly. This
verification should
be done while
instantiating the page object. In the examples above, both the SignInPage and
HomePage
constructors check
that the expected page is available and ready for requests from the test.
A page object does
not necessarily need to represent an entire page. The Page Object design
pattern
could be used to
represent components on a page. If a page in the AUT has multiple components,
it may
improved
maintainability if there was a separate page object for each component.
There are other
design patterns that also may be used in testing. Some use a Page Factory for
instantiating
their page objects.
Discussing all of these is beyond the scope of this user guide. Here, we merely
want
to introduce the
concepts to make the reader aware of some of the things that can be done. As
was
mentioned earlier,
many have blogged on this topic and we encourage the reader to search for blogs
on
these topics.
7.8
Data Driven Testing
Data Driven Testing
refers to using the same test (or tests) multiple times with varying data.
These
data sets are often
from external files i.e. .csv file, text file, or perhaps loaded from a database.
Data
driven testing is a
commonly used test automation technique used to validate an application against
many
varying input. When
the test is designed for varying data, the input data can expand, essentially
creating
additional tests,
without requiring changes to the test code.
In Python:
# Collection of String
values
source = open(
" input_file.txt " , " r " )
values = source.readlines()
source.close()
# Execute For loop for
each String in the values array
for search
in values:
sel.open(
" / " )
sel.type(
" q " , search)
sel.click(
" btnG " )
sel.waitForPageToLoad(
" 30000 " )
self.failUnless(sel.is_text_present(
" Results * for " + search))
The Python script
above opens a text file. This file contains a different search string on each
line. The
code then saves
this in an array of strings, and iterates over the array doing a search and
assert on each
string.
This is a very
basic example, but the idea is to show that running a test with varying data
can be done
easily with a
programming or scripting language. For more examples, refer to the Selenium
RC wiki
for examples of
reading data from a spreadsheet or for using the data provider capabilities of
TestNG.
Additionally, this
is a well-known topic among test automation professionals including those who
don’t
use Selenium so
searching the internet on “data-driven testing” should reveal many blogs on
this topic.
126
Chapter 7. Test Design Considerations
Selenium
Documentation, Release 1.0
7.9
Database Validation
Another common type
of testing is to compare data in the UI against the data actually stored in the
AUT’s database.
Since you can also do database queries from a programming language, assuming
you
have database
support functions, you can use them to retrieve data and then use the data to
verify what’s
displayed by the
AUT is correct.
Consider the
example of a registered email address to be retrieved from a database and then
later compared
against the UI. An
example of establishing a DB connection and retrieving data from the DB could
look like this.
In Java:
// Load Microsoft SQL
Server JDBC driver.
Class.forName( "com.microsoft.sqlserver.jdbc.SQLServerDriver"
);
// Prepare connection
url.
String url = "jdbc:sqlserver://192.168.1.180:1433;DatabaseName=TEST_DB"
;
// Get connection to
DB.
public
static Connection con =
DriverManager.getConnection(url, "username"
, "password" );
// Create statement
object which would be used in writing DDL and DML
// SQL statement.
public
static Statement stmt = con.createStatement();
// Send SQL SELECT
statements to the database via the Statement.executeQuery
// method which returns
the requested information as rows of data in a
// ResultSet object.
ResultSet result = stmt.executeQuery
( "select
top 1 email_address from user_register_table" );
// Fetch value of
"email_address" from "result" object.
String emailaddress = result.getString( "email_address"
);
// Use the emailAddress
value to login to application.
selenium.type( "userID"
, emailaddress);
selenium.type( "password"
, secretPassword);
selenium.click( "loginButton"
);
selenium.waitForPageToLoad(timeOut);
Assert.assertTrue(selenium.isTextPresent( "Welcome
back" +emailaddress), "Unable to log This
is a simple Java example of data retrieval from a database.
7.9.
Database Validation 127
Selenium
Documentation, Release 1.0
128
Chapter 7. Test Design Considerations
CHAPTER
EIGHT
SELENIUM-GRID
Please refer to the
Selenium Grid website
http://selenium-grid.seleniumhq.org/how_it_works.html
This section is
not yet developed. If there is a member of the community who is experienced in
Selenium-
Grid, and would
like to contribute, please contact the Documentation Team. We would love to
have you
contribute.
129
Selenium
Documentation, Release 1.0
130
Chapter 8. Selenium-Grid
CHAPTER
NINE
USER-EXTENSIONS
NOTE: This
section is close to completion, but it has not been reviewed and edited.
9.1
Introduction
Extending Selenium
by adding your own actions, assertions and locator-strategies can be quite
simple.
Add JavaScript
methods to the Selenium object prototype and the PageBot object prototype. On
startup,
Selenium will
automatically look through methods on these prototypes, using name patterns to
recognize
which ones are
actions, assertions and locators. The following examples give an indication of
how
Selenium can be
extended with JavaScript.
9.2
Actions
All methods on the
Selenium prototype beginning with “do” are added as actions. For each action
foo
there is also an
action fooAndWait registered. An action method can take up to two parameters,
which
will be passed the
second and third column values in the test. Example: Add a “typeRepeated”
action to
Selenium, which
types the text twice into a text box.
Selenium.prototype.doTypeRepeated
= function(locator, text)
{
// All
locator-strategies are automatically handled by "findElement"
var element
= this.page().findElement(locator);
// Create the text to
type
var valueToType
= text + text;
// Replace the element
text with the new text
this.page().replaceText(element, valueToType);
};
9.3
Accessors/Assertions
All getFoo
and isFoo methods on the Selenium prototype are added as accessors
(storeFoo). For each
accessor there is
an assertFoo, verifyFoo and waitForFoo registered. An assert method can take up
to 2 parameters,
which will be passed the second and third column values in the test. You can
also
define your own
assertions literally as simple “assert” methods, which will also auto-generate
“verify”
and “waitFor”
commands. Example: Add a valueRepeated assertion, that makes sure that the
element
131
Selenium
Documentation, Release 1.0
value consists of
the supplied text repeated. The 2 commands that would be available in tests
would be
assertValueRepeated
and verifyValueRepeated.
Selenium.prototype.assertValueRepeated
= function(locator, text)
{
// All
locator-strategies are automatically handled by "findElement"
var element
= this.page().findElement(locator);
// Create the text to
verify
var expectedValue
= text + text;
// Get the actual
element value
var actualValue
= element.value;
// Make sure the actual
value matches the expected
Assert.matches(expectedValue, actualValue);
};
9.3.1
Prototype generates additional commands
All getFoo
and isFoo methods on the Selenium prototype automatically result in the
availability of store-
Foo, assertFoo,
assertNotFoo, verifyFoo, verifyNotFoo, waitForFoo, and waitForNotFoo commands.
Example, if you add
a getTextLength() method, the following commands will
automatically
be available: storeTextLength, assertTextLength, assertNotTextLength,
verifyTextLength, verifyNotTextLength, waitForTextLength,
and
waitForNotTextLength commands.
Selenium.prototype.getTextLength
= function(locator, text)
{
return
this.getText(locator).length;
};
Also note that the assertValueRepeated
method described above could have been implemented
using isValueRepeated,
with the added benefit of also automatically getting assertNotValueRepeated,
storeValueRepeated,
waitForValueRepeated and waitForNotValueRepeated.
9.4
Locator Strategies
All
locateElementByFoo methods on the PageBot prototype are added as
locator-strategies. A locator
strategy takes 2
parameters, the first being the locator string (minus the prefix), and the
second being the
document in which
to search. Example: Add a “valuerepeated=” locator, that finds the first
element a
value attribute
equal to the the supplied value repeated.
// The
"inDocument" is a the document you are searching.
PageBot.prototype.locateElementByValueRepeated
= function(text, inDocument)
{
// Create the text to
search for
var expectedValue
= text + text;
// Loop through all
elements, looking for ones that have
// a value === our
expected value
var allElements
= inDocument.getElementsByTagName( "*" );
for (var i = 0; i <
allElements.length; i++) {
var testElement
= allElements[i];
if (testElement.value
&& testElement.value === expectedValue)
{
132
Chapter 9. User-Extensions
Selenium
Documentation, Release 1.0
return
testElement;
}
}
return
null;
};
9.5
Using User-Extensions With Selenium-IDE
User-extensions are
very easy to use with the selenium IDE.
1. Create your user
extension and save it as user-extensions.js. While this name isn’t technically
necessary, it’s
good practice to keep things consistent.
2. Open Firefox and
open Selenium-IDE.
3. Click on Tools,
Options
4. In Selenium Core
Extensions click on Browse and find the user-extensions. js file. Click on OK.
5. Your
user-extension will not yet be loaded, you must close and restart Selenium-IDE.
6. In your empty
test, create a new command, your user-extension should now be an options in the
Commands dropdown.
9.6
Using User-Extensions With Selenium RC
If you Google
“Selenium RC user-extension” ten times you will find ten different approaches
to using
this feature.
Below, is the official Selenium suggested approach.
9.6.1
Example
C#
1. Place your user
extension in the same directory as your Selenium Server.
2. If you are using
client code generated by the Selenium-IDE you will need to make a couple
of small edits.
First, you will need to create an HttpCommandProcessor object
with class scope
(outside the SetupTest method, just below private StringBuilder
verificationErrors;)
HttpCommandProcessor
proc;
1. Next,
instantiate that HttpCommandProcessor object
as you would the
DefaultSelenium object.
This can be done in the test setup.
proc = new HttpCommandProcessor(
"localhost" , 4444, "*iexplore" ,
"http://google.ca/" );
1. Instantiate the
DefaultSelenium object using the HttpCommandProcessor object
you created.
9.5.
Using User-Extensions With Selenium-IDE 133
Selenium
Documentation, Release 1.0
selenium = new DefaultSelenium(proc);
1. Within your test
code, execute your user-extension by calling it with the DoCommand()
method
of HttpCommandProcessor.
This method takes two arguments: a string to identify the userextension
method you want to
use and string array to pass arguments. Notice that the first letter
of your function is
lower case, regardless of the capitalization in your user-extension. Selenium
automatically does
this to keep common JavaScript naming conventions. Because JavaScript is
case sensitive,
your test will fail if you begin this command with a capital. inputParams is
the
array of arguments
you want to pass to the JavaScript user-extension. In this case there is only
one string in the
array because there is only one parameter for our user extension, but a longer
array will map each
index to the corresponding user-extension parameter. Remember that user
extensions designed
for Selenium-IDE will only take two arguments.
string[]
inputParams = { "Hello World" };
proc.DoCommand(
"alertWrapper" , inputParams);
1. Start the test
server using the -userExtensions argument and pass
in your
user-extensions.js file.
java -jar
selenium-server.jar -userExtensions user-extensions.js
using
System;
using
System.Text;
using
System.Text.RegularExpressions;
using
System.Threading;
using
NUnit.Framework;
using
Selenium;
namespace
SeleniumTests
{
[TestFixture]
public
class NewTest
{
private
ISelenium selenium;
private
StringBuilder verificationErrors;
private
HttpCommandProcessor proc;
[SetUp]
public
void SetupTest()
{
proc = new HttpCommandProcessor(
"localhost" , 4444, "*iexplore"
selenium = new DefaultSelenium(proc);
//selenium = new
DefaultSelenium("localhost", 4444, "*iexplore", selenium.Start();
verificationErrors = new StringBuilder();
}
[TearDown]
134
Chapter 9. User-Extensions
Selenium
Documentation, Release 1.0
public
void TeardownTest()
{
try
{
selenium.Stop();
}
catch
(Exception)
{
// Ignore errors if
unable to close the browser
}
Assert.AreEqual(
"" , verificationErrors.ToString());
}
[Test]
public
void TheNewTest()
{
selenium.Open(
"/" );
string[]
inputParams = { "Hello World" ,};
proc.DoCommand(
"alertWrapper" , inputParams);
}
}
}
Appendixes:
9.6.
Using User-Extensions With Selenium RC 135
Selenium
Documentation, Release 1.0
136
Chapter 9. User-Extensions
CHAPTER
TEN
SELENIUM
WEBDRIVER CHEAT
SHEET
10.1
Role Based Interfaces in Selenium WebDriver
One of the
differences between Selenium RC and SeleniumWebDriver is that theWebDriver APIs
make
extensive use of
“role-based interfaces” to allow users to determine whether a particular driver
supports a
feature. This can
make it hard to know what features are available without first knowing which
interface
to try and use. The
key interfaces are listed below.
Interface Role
Documentation
HasCapabilities
Provides access to
the capabilities supported by this driver. Java HasCapabilities.
java
JavascriptExecutor
Allows the
execution of arbitrary JS commands. Java JavascriptExecutor.
java
Rotatable Indicates
whether the driver supports rotating the display
(mostly just mobile
drivers).
Java
Rotatable.java
TakesScreenshot
Provides a
mechanism for taking screenshots. Java TakesScreenshot.
java
137
Selenium
Documentation, Release 1.0
138
Chapter 10. Selenium WebDriver Cheat Sheet
CHAPTER
ELEVEN
HOW
TO INSTALL THE ANDROID
DRIVER
This is a
placeholder.
139
Selenium
Documentation, Release 1.0
140
Chapter 11. How to Install the Android Driver
CHAPTER
TWELVE
.NET
CLIENT DRIVER
CONFIGURATION
.NET client Driver
can be used with Microsoft Visual Studio. To Configure it with Visual Studio do
as
Following.
• Launch Visual
Studio and navigate to File > New > Project.
• Select Visual C#
> Class Library > Name your project > Click on OK button.
141
Selenium
Documentation, Release 1.0
• A Class (.cs) is
created. Rename it as appropriate.
• Under right hand
pane of Solution Explorer right click on References > Add References.
142
Chapter 12. .NET client driver configuration
Selenium
Documentation, Release 1.0
• Select following
dll files - nmock.dll, nunit.core.dll, nunit.framework.dll,ThoughtWorks.
Selenium.Core.dll,
ThoughtWorks.Selenium.IntegrationTests.dll, Thought-
Works.Selenium.UnitTests.dll
and click on Ok button
143
Selenium
Documentation, Release 1.0
With This Visual
Studio is ready for Selenium Test Cases.
144
Chapter 12. .NET client driver configuration
CHAPTER
THIRTEEN
IMPORTING
SEL2.0 PROJECT INTO
ECLIPSE
USING MAVEN
Once you have
created your pom.xml file in your project, you can have maven autogenerate the
project
files necessary for
eclipse with a simple command:
mvn eclipse:eclipse
Then open eclipse.
Choose your workspace or create a new one. Once the Eclipse IDE loads, do the
following:
# File ->
Import... # General -> Existing Projects into Workspace # Click next # Next
to
“Select root
Directory:” click “Browse” button # locate the project folder containing your
pom.xml and click
ok. # Your project should appear in the “Projects” box already # click
finish
If you haven’t
already, install the m2eclipse plugin then
right click on your project and select Maven ->
Enable Dependency
Management.
145
Selenium
Documentation, Release 1.0
146
Chapter 13. Importing Sel2.0 Project into Eclipse using Maven
CHAPTER
FOURTEEN
IMPORTING
SEL2.0 PROJECT INTO
INTELLIJ
USING MAVEN
We are
currently working on this appendix. The information provided here is accurate,
although it may
not be
finished.
In this appendix we
provide the steps, including screen captures, showing how to create a Selenium
2.0 java
client-driver project in IntelliJ IDEA. These steps assume you have already
used maven with a
pom.xml file to set
up the project. This process is described in the Selenium 2.0 chapter. You must
have
followed that
process before you can perform these steps. This appendix then shows you how to
import
the maven-created
Selenium 2.0 java project into IntelliJ.
First, open
IntelliJ and from the entry page, click Create New Project.
From the New
Project dialog select Import Project from External Model.
147
Selenium
Documentation, Release 1.0
From the list of
project types, select maven.
Now you will see a
dialog allowing you to set project options including the project’s root
directory.
148
Chapter 14. Importing Sel2.0 Project into IntelliJ Using Maven
Selenium
Documentation, Release 1.0
Click the ‘...’
button to set the root folder.
Now the settings
dialog will show the directory you just selected.
149
Selenium
Documentation, Release 1.0
This next dialog
shows the name of your maven project as specified in the pom.xml file. Select
your
maven project and
continue.
Enter a name for
your project.
150
Chapter 14. Importing Sel2.0 Project into IntelliJ Using Maven
Selenium
Documentation, Release 1.0
Once your project
has been imported it should look like this in IntelliJ.
The maven project
download many dependencies (libraries) when you originally ran ‘mvn install’.
Now
in IntelliJ you can
see all these libraries. These next two screen captures shows the libraries you
should
now have in your
project.
151
Selenium
Documentation, Release 1.0
Before you can
start writing Selenium code, you still need to create a module and at least one
Java class
(a .java file).
First select the Project’s root in IntelliJ and right click.
152
Chapter 14. Importing Sel2.0 Project into IntelliJ Using Maven
Selenium
Documentation, Release 1.0
And select Create
Module.
In the dialog
select the radio button Create Module From Scratch.
153
Selenium
Documentation, Release 1.0
Select Java Module
and enter a name for the new module.
154
Chapter 14. Importing Sel2.0 Project into IntelliJ Using Maven
Selenium
Documentation, Release 1.0
And next, you must
create a folder for the source code. By convention this is almost always named
‘src’.
155
Selenium
Documentation, Release 1.0
Now we’re on the
last dialog. Typically you don’t need to select any ‘technollogies’ here.
Unless you
know for a fact you
will be using Groovy or some other technology.
156
Chapter 14. Importing Sel2.0 Project into IntelliJ Using Maven
Selenium
Documentation, Release 1.0
Now that the module
is created, your project should show the following structure.
157
Selenium
Documentation, Release 1.0
Finally, you need
to create a .java file with a corresponding java class.
Enter the class
name.
The .java file
should now be created. It should look like this in your project.
158
Chapter 14. Importing Sel2.0 Project into IntelliJ Using Maven
Selenium
Documentation, Release 1.0
If your project now
looks like the one displayed above, you’re done, congrats! And hope you enjoy
coding your first
Selenium automation!
159
Selenium
Documentation, Release 1.0
160
Chapter 14. Importing Sel2.0 Project into IntelliJ Using Maven
CHAPTER
FIFTEEN
SELENIUM
1.0 JAVA CLIENT DRIVER
CONFIGURATION
In General
configuration of Selenium-RC with any java IDE would have following steps:
• Download
Selenium-RC from the SeleniumHQ downloads page
• Start any java
IDE
• Create new
project
• Add
“selenium-java-<version-number>.jar” to your project classpath
• Record your test
from Selenium-IDE and translate it to java code (Selenium IDE has automatic
translation feature
to generate tests in variety of languages)
• Run selenium
server from console
• Run your test in
the IDE
These points have
been delineated below with reference to Eclipse and IntelliJ:
15.1
Configuring Selenium-RC With Eclipse
Eclipse is
a multi-language software development platform comprising an IDE and a plug-in
system to
extend it. It is
written primarily in Java and is used to develop applications in this language
and, by
means of the
various plug-ins, in other languages as well as C/C++, Cobol, Python, Perl, PHP
and more.
Following lines
describes configuration of Selenium-RC with Eclipse - Version: 3.3.0. (Europa
Release).
It should not be
too different for higher versions of Eclipse
• Launch Eclipse.
• Select File >
New > Other.
161
Selenium
Documentation, Release 1.0
• Java > Java
Project > Next
162
Chapter 15. Selenium 1.0 Java Client Driver Configuration
Selenium
Documentation, Release 1.0
• Provide Name to
your project, Select JDK in ‘Use a project Specific JRE’ option (JDK 1.5
selected
in this example)
> click Next
15.1.
Configuring Selenium-RC With Eclipse 163
Selenium
Documentation, Release 1.0
• Keep ‘JAVA
Settings’ intact in next window. Project specific libraries can be added here.
(This
described in detail
in later part of document.)
164
Chapter 15. Selenium 1.0 Java Client Driver Configuration
Selenium
Documentation, Release 1.0
• Click Finish >
Click on Yes in Open Associated Perspective pop up window.
15.1.
Configuring Selenium-RC With Eclipse 165
Selenium
Documentation, Release 1.0
This would create
Project Google in Package Explorer/Navigator pane.
166
Chapter 15. Selenium 1.0 Java Client Driver Configuration
Selenium
Documentation, Release 1.0
• Right click on
src folder and click on New > Folder
15.1.
Configuring Selenium-RC With Eclipse 167
Selenium
Documentation, Release 1.0
Name this folder as
com and click on Finish button.
• This should get
com package insider src folder.
168
Chapter 15. Selenium 1.0 Java Client Driver Configuration
Selenium
Documentation, Release 1.0
• Following the
same steps create core folder inside com
15.1.
Configuring Selenium-RC With Eclipse 169
Selenium
Documentation, Release 1.0
SelTestCase class
can be kept inside core package.
Create one more
package inside src folder named testscripts.
This is a place holder for test scripts.
Please notice
this is about the organization of project and it entirely depends on
individual’s choice /
organization’s
standards. Test scripts package can further be segregated depending upon the
project
requirements.
170
Chapter 15. Selenium 1.0 Java Client Driver Configuration
Selenium
Documentation, Release 1.0
• Create a folder
called lib inside project Google. Right click on Project name > New >
Folder. This
is a place holder
for jar files to project (i.e. Selenium client driver, selenium server etc)
15.1.
Configuring Selenium-RC With Eclipse 171
Selenium
Documentation, Release 1.0
This would create
lib folder in Project directory.
172
Chapter 15. Selenium 1.0 Java Client Driver Configuration
Selenium
Documentation, Release 1.0
• Right click on lib
folder > Build Path > Configure build Path
15.1.
Configuring Selenium-RC With Eclipse 173
Selenium
Documentation, Release 1.0
• Under Library tab
click on Add External Jars to navigate to directory where jar files are saved.
Select the jar
files which are to be added and click on Open button.
174
Chapter 15. Selenium 1.0 Java Client Driver Configuration
Selenium
Documentation, Release 1.0
After having added
jar files click on OK button.
15.1.
Configuring Selenium-RC With Eclipse 175
Selenium
Documentation, Release 1.0
Added libraries
would appear in Package Explorer as following:
176
Chapter 15. Selenium 1.0 Java Client Driver Configuration
Selenium
Documentation, Release 1.0
15.2
Configuring Selenium-RC With Intellij
• Watch this
excellent tutorial by Simon Stewart on starting a Selenium project
with IntelliJ
15.2.
Configuring Selenium-RC With Intellij 177
Selenium
Documentation, Release 1.0
178
Chapter 15. Selenium 1.0 Java Client Driver Configuration
CHAPTER
SIXTEEN
PYTHON
CLIENT DRIVER
CONFIGURATION
• Download
Selenium-RC from the SeleniumHQ downloads page
• Extract the file selenium.py
• Either write your
Selenium test in Python or export a script from Selenium-IDE to a python file.
• Add to your
test’s path the file selenium.py
• Run Selenium
server from the console
• Execute your test
from a console or your Python IDE
The following steps
describe the basic installation procedure. After following this, the user can
start
using the desired
IDE, (even write tests in a text processor and run them from command line!)
without
any extra work (at
least on the Selenium side).
• Installing Python
Note: This
will cover python installation on Windows and Mac only, as in most linux
distributions
python is already pre-installed by default.
– Windows
1. Download Active
python’s installer from ActiveState’s official site:
http://activestate.com/Products/activepython/index.mhtml
2. Run the
installer downloaded (ActivePython-x.x.x.x-win32-x86.msi)
179
Selenium
Documentation, Release 1.0
• Mac
The latest Mac OS X
version (Leopard at this time) comes with Python pre-installed. To install
an extra Python,
get a universal binary at http://www.pythonmac.org/ (packages
for Python 2.5.x).
180
Chapter 16. Python Client Driver Configuration
Selenium
Documentation, Release 1.0
You will get a .dmg
file that you can mount. It contains a .pkg file that you can launch.
• Installing the
Selenium driver client for python
1. Download the
last version of Selenium Remote Control from the downloads page
2. Extract the
content of the downloaded zip file
3. Copy the module
with the Selenium’s driver for Python (selenium.py) in the folder
C:/Python25/Lib
(this will allow you to import it directly in any script you
write).
You will find the
module in the extracted folder, it’s located inside seleniumpython-
driver-client.
Congratulations,
you’re done! Now any python script that you create can import selenium and
start
interacting with
the browsers.
181
Selenium
Documentation, Release 1.0
182
Chapter 16. Python Client Driver Configuration
CHAPTER
SEVENTEEN
LOCATING
TECHNIQUES
17.1
Useful XPATH patterns
17.1.1
text
Not yet written -
locate elements based on the text content of the node.
17.1.2
starts-with
Many sites use
dynamic values for element’s id attributes, which can make them difficult to
locate.
One simple solution
is to use XPath functions and base the location on what you do know about the
element. For
example, if your dynamic ids have the format <input
id="text-12345" /> where
12345 is
a dynamic number you could use the following XPath: //input[starts-with(@id,
’text-’)]
17.1.3
contains
If an element can
be located by a value that could be surrounded by other text, the contains
function
can be used. To
demonstrate, the element <span class="top heading bold"> can
be located
based on the
‘heading’ class without having to couple it with the ‘top’ and ‘bold’ classes
using the following
XPath: //span[contains(@class,
’heading’)]. Incidentally, this would be much
neater (and probably
faster) using the CSS locator strategy css=span.heading
17.1.4
siblings
Not yet written -
locate elements based on their siblings. Useful for forms and tables.
17.2
Starting to use CSS instead of XPATH
17.2.1
Locating elements based on class
In order to locate
an element based on associated class in XPath you must consider that the
element
could have multiple
classes and defined in any order. However with CSS locators this is much
simpler
(and faster).
• XPath: //div[contains(@class,
’article-heading’)]
183
Selenium
Documentation, Release 1.0
• CSS: css=div.article-heading
184
Chapter 17. Locating Techniques
CHAPTER
EIGHTEEN
MIGRATING
FROM SELENIUM RC TO
SELENIUM
WEBDRIVER
18.1
How to Migrate to Selenium WebDriver
A common question
when adopting Selenium 2 is what’s the correct thing to do when adding new
tests
to an existing set
of tests? Users who are new to the framework can begin by using the new
WebDriver
APIs for writing
their tests. But what of users who already have suites of existing tests? This
guide is
designed to
demonstrate how to migrate your existing tests to the new APIs, allowing all
new tests to be
written using the
new features offered by WebDriver.
The method
presented here describes a piecemeal migration to the WebDriver APIs without
needing to
rework everything
in one massive push. This means that you can allow more time for migrating your
existing tests,
which may make it easier for you to decide where to spend your effort.
This guide is
written using Java, because this has the best support for making the migration.
As we
provide better
tools for other languages, this guide shall be expanded to include those
languages.
18.2
Why Migrate to WebDriver
Moving a suite of
tests from one API to another API requires an enormous amount of effort. Why
would you and your
team consider making this move? Here are some reasons why you should consider
migrating your
Selenium Tests to use WebDriver.
• Smaller, compact
API. WebDriver’s API is more Object Oriented than the original Selenium RC
API. This can make
it easier to work with.
• Better emulation
of user interactions. Where possible, WebDriver makes use of native events in
order to interact
with a web page. This more closely mimics the way that your users work with
your site and apps.
In addition,WebDriver offers the advanced user interactions APIs which allow
you to model
complex interactions with your site.
• Support by
browser vendors. Opera, Mozilla and Google are all active participants
inWebDriver’s
development, and
each have engineers working to improve the framework. Often, this means that
support for
WebDriver is baked into the browser itself: your tests run as fast and as
stably as
possible.
185
Selenium
Documentation, Release 1.0
18.3
Before Starting
In order to make
the process of migrating as painless as possible, make sure that all your tests
run
properly with the
latest Selenium release. This may sound obvious, but it’s best to have it said!
18.4
Getting Started
The first step when
starting the migration is to change how you obtain your instance of Selenium.
When
using Selenium RC,
this is done like so:
Selenium selenium = new DefaultSelenium(
"localhost" , 4444, "*firefox"
, "http://www.yoursite.com" );
selenium.start();
This should be
replaced like so:
WebDriver driver = new FirefoxDriver();
Selenium selenium = new WebDriverBackedSelenium(driver, "http://www.yoursite.com"
);
Once you’ve done
this, run your existing tests. This will give you a fair idea of how much work
needs
to be done. The
Selenium emulation is good, but it’s not completely perfect, so it’s completely
normal
for there to be
some bumps and hiccups.
18.5
Next Steps
Once your tests
execute without errors, the next stage is to migrate the actual test code to
use the Web-
Driver APIs.
Depending on how well abstracted your code is, this might be a short process or
a long
one. In either
case, the approach is the same and can be summed up simply: modify code to use
the new
API when you come
to edit it.
If you need to
extract the underlying WebDriver implementation from the Selenium instance, you
can
simply cast it to
WrapsDriver:
WebDriver driver =
((WrapsDriver) selenium).getWrappedDriver();
This allows you to
continue passing the Selenium instance around as normal, but to unwrap the Web-
Driver instance as
required.
At some point,
you’re codebase will mostly be using the newer APIs. At this point, you can
flip the
relationship, using
WebDriver throughout and instantiating a Selenium instance on demand:
Selenium selenium = new WebDriverBackedSelenium(driver, baseUrl);
18.6
Common Problems
Fortunately, you’re
not the first person to go through this migration, so here are some common
problems
that others have
seen, and how to solve them.
186
Chapter 18. Migrating From Selenium RC to Selenium WebDriver
Selenium
Documentation, Release 1.0
18.6.1
Clicking and Typing is More Complete
A common pattern in
a Selenium RC test is to see something like:
selenium.type( "name"
, "exciting tex" );
selenium.keyDown( "name"
, "t" );
selenium.keyPress( "name"
, "t" );
selenium.keyUp( "name"
, "t" );
This relies on the
fact that “type” simply replaces the content of the identified element without
also firing
all the events that
would normally be fired if a user interacts with the page. The final direct
invocations
of “key*” cause the
JS handlers to fire as expected.
When using the
WebDriverBackedSelenium, the result of filling in the form field would be
“exciting
texttt”: not what
you’d expect! The reason for this is that WebDriver more accurately emulates
user
behavior, and so
will have been firing events all along.
This same fact may
sometimes cause a page load to fire earlier than it would do in a Selenium 1
test.
You can tell that this
has happened if a “StaleElementException” is thrown by WebDriver.
18.6.2
WaitForPageToLoad Returns Too Soon
Discovering when a
page load is complete is a tricky business. Do we mean “when the load event
fires”,
“when all AJAX
requests are complete”, “when there’s no network traffic”, “when
document.readyState
has changed” or
something else entirely?
WebDriver attempts
to simulate the original Selenium behavior, but this doesn’t always work
perfectly
for various
reasons. The most common reason is that it’s hard to tell the difference
between a page load
not having started
yet, and a page load having completed between method calls. This sometimes
means
that control is
returned to your test before the page has finished (or even started!) loading.
The solution to
this is to wait on something specific. Commonly, this might be for the element
you want
to interact with
next, or for some Javascript variable to be set to a specific value. An example
would be:
Wait<WebDriver>
wait = new WebDriverWait(driver, 30);
WebElement element= wait.until(visibilityOfElementLocated(By.id( "some_id"
)));
Where
“visibilityOfElementLocated” is implemented as:
public
ExpectedCondition<WebElement>
visibilityOfElementLocated(final
By locator) {
return
new ExpectedCondition<WebElement>()
{
public
WebElement apply(WebDriver
driver) {
WebElement toReturn = driver.findElement(locator);
if (toReturn.isDisplayed())
{
return
toReturn;
}
return
null;
}
};
}
This may look
complex, but it’s almost all boiler-plate code. The only interesting bit is
that the “ExpectedCondition”
will be evaluated
repeatedly until the “apply” method returns something that is neither
“null” nor
Boolean.FALSE.
18.6.
Common Problems 187
Selenium
Documentation, Release 1.0
Of course, adding
all these “wait” calls may clutter up your code. If that’s the case, and your
needs are
simple, consider
using the implicit waits:
driver.manage().timeouts().implicitlyWait(30, TimeUnit.SECONDS);
By doing this,
every time an element is located, if the element is not present, the location
is retried until
either it is
present, or until 30 seconds have passed.
18.6.3
Finding By XPath or CSS Selectors Doesn’t Always Work, But It Does In
Selenium
1
In Selenium 1, it
was common for xpath to use a bundled library rather than the capabilities of
the
browser itself.
WebDriver will always use the native browser methods unless there’s no
alternative. That
means that complex
xpath expressions may break on some browsers.
CSS Selectors in
Selenium 1 were implemented using the Sizzle library. This implements a
superset
of the CSS Selector
spec, and it’s not always clear where you’ve crossed the line. If you’re using
the
WebDriverBackedSelenium
and use a Sizzle locator instead of a CSS Selector for finding elements, a
warning will be
logged to the console. It’s worth taking the time to look for these,
particularly if tests
are failing because
of not being able to find elements.
18.6.4
There is No Browserbot
Selenium RC was
based on Selenium Core, and therefore when you executed Javascript, you could
access bits of
Selenium Core to make things easier. As WebDriver is not based on Selenium
Core, this
is no longer
possible. How can you tell if you’re using Selenium Core? Simple! Just look to
see if your
“getEval” or
similar calls are using “selenium” or “browserbot” in the evaluated Javascript.
You might be using
the browserbot to obtain a handle to the current window or document of the
test. Fortunately,
WebDriver always
evaluates JS in the context of the current window, so you can use “window”
or “document”
directly.
Alternatively, you
might be using the browserbot to locate elements. In WebDriver, the idiom for
doing
this is to first
locate the element, and then pass that as an argument to the Javascript. Thus:
String name = selenium.getEval(
"selenium.browserbot.findElement(’id=foo’,
browserbot.getCurrentWindow()).tagName" );
becomes:
WebElement element = driver.findElement(By.id( "foo"
));
String name = (String)
((JavascriptExecutor) driver).executeScript(
"return
arguments[0].tagName" , element);
Notice how the
passed in “element” variable appears as the first item in the JS standard
“arguments”
array.
18.6.5
Executing Javascript Doesn’t Return Anything
WebDriver’s
JavascriptExecutor will wrap all JS and evaluate it as an anonymous expression.
This
means that you need
to use the “return” keyword:
188
Chapter 18. Migrating From Selenium RC to Selenium WebDriver
Selenium
Documentation, Release 1.0
String title = selenium.getEval( "browserbot.getCurrentWindow().document.title"
);
becomes:
((JavascriptExecutor) driver).executeScript( "return
No comments:
Post a Comment