Total Pageviews

Monday 28 May 2012

Selenium Documentation

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