John is an experienced consultant specialising in Enterprise Java, Web Development, and Open Source technologies, currently based in Sydney, Australia. Well known in the Java community for his many published articles, and as author of Java Power Tools and Jenkins: The Definitive Guide, and founder of the open source Thucydides Automated Acceptance Test Library project, John helps organisations to optimize their Java development processes and infrastructures and provides training and mentoring in agile development, automated testing practices, continuous integration and delivery, and open source technologies in general. John is the CEO of Wakaleo Consulting, and runs several Training Courses on open source Java development tools and best practices. John is a DZone MVB and is not an employee of DZone and has posted 125 posts at DZone. You can read more from them at their website. View Full User Profile

Selenium 2/Web Driver - the Land Where Page Objects are King!

08.10.2010
| 11615 views |
  • submit to reddit

In the world of automated web testing, Selenium 2/WebDriver is the new kid on the block, but it is also arguably the most compelling web testing tool around at the moment. Selenium 2/WebDriver is the result of the merging of two popular open source web testing frameworks: Selenium 1 and WebDriver, in an effort to learn the lessons gleaned from both of these older libraries. And the teams have learned their lessons well, and come up with a slick, elegant and functional testing framework.

Selenium 2, like Selenium, is a cross-browser tool - it is equally happy to run through both Firefox and Internet Explorer. But it also works with HTMLUnit which simulates a browser by sending HTTP requests and analyzing the HTML responses. You can pick from several drivers, including HTMLUnit (which great for fast testing of more conventional web applications), as well as browser-based drivers for Firefox, Chrome and InternetExplorer. The basic API is identical, though milage will vary from one driver to another, particularly if you are testing AJAX-based applications - for example tests for some AJAX apps will work with the Firefox browser, but not with the HTMLUnit browser. The good news is that it is trivially simple to switch between drivers.

Setting up Selenium 2 in a Maven project is easy: just add the following dependency:

<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium</artifactId>
<version>2.0a5</version>
</dependency>
This will give you the Selenium 2 core libraries plus all of the drivers.

In Selenium 2, a simple web test might look like this:

@Test
public void theUserShouldBeAbleToTypeInQueryTerms() {
WebDriver driver = new FirefoxDriver();
driver.get("http://www.google.com");
WebElement queryField = driver.findElement(By.name("q"));
queryField.sendKeys("cats");
queryField.submit();
assertThat(driver.getTitle(), containsString("cats"));
}

In this test, Webdriver will start an instance of Firefox and open up a connection to Google. Next it will type the word "cats" into the query field (whose name attribute is 'q'). Finally we submit the enclosing form, and check that the page title now contains the word "cats".

Selenium users will notice that the API is much more object-oriented and reads more smoothly than the older Selenium 1 API. It is also simpler and easier to learn than the sizable Selenium API.

As mentioned earlier, the API is virtually identical, no matter what browser you are using. If you want to use HTMLUnit instead, you just change the driver:

@Test
public void theUserShouldBeAbleToTypeInQueryTerms() {
WebDriver driver = new HTMLUnitDriver();
driver.get("http://www.google.com");
WebElement queryField = driver.findElement(By.name("q"));
queryField.sendKeys("cats");
queryField.submit();
assertThat(driver.getTitle(), containsString("cats"));
}
Now, in the real world, there are exceptions, and at times you may want to use some of the more advanced features that only a browser-based driver such as the FirefoxDriver can offer: these include some AJAX features, drag-and-drop, and figuring out whether an object on the page is actually visible or not. But, in general, the API is effectively cross browser.

Another nice feature, when compared to Selenium 1, is the lack of ceremony required to execute the test. This test will work as-is, with no extra plumbing, infrastructure or hidden wires required. For an equivalent Selenium 1 test, you would need to start up a Selenium-RC server before running the test, which is a relatively slow and sometimes fickle process. In Selenium 2, this is no longer required - you can write this test in Eclipse and run it in isolation, and it will work just fine.

The findElement pattern that we saw earlier turns out to be an extremely powerful way of modeling and verifying web pages, and you can do things that you could only dream of in Selenium 1. For example, the following results table comes from an ICEFaces JSF application:

<table class="iceForm:iceTable">
<tr>
</tr>
<tr class="bidTableRow oddRow">
<td class="column1">ICEsoft Ice Sailor</td>
<td class="column2">$10,000.00</td>
<td class="column3">0</td>
...
</tr>
<tr class="bidTableRow evenRow">...</tr>
<tr class="bidTableRow oddRow">...</tr>
<tr class="bidTableRow evenRow">...</tr>
</table>

Suppose we wanted to verify in detail the contents of this table. In Selenium 2, findElement is a great to do this. You see, the findElement() method returns a WebElement object: once you have it, you can also use the findElement() method on this WebElement object, to drill down into the HTML contained within. For example, the following code extracts column values for each row in the table shown above:
WebElement table = driver.findElement(By.id("iceForm:iceTable"));
List rows = table.findElements(By.className("bidTableRow"));
for(WebElement row : rows) {
String name = row.findElement(By.className("column1")).getText();
String price = row.findElement(By.className("column2")).getText();
String bids = row.findElement(By.className("column3")).getText();
// Do something with the row data
}

But where Selenium 2 really shines is in its support for Web Objects. Web Objects are a technique that involves modeling your user interface in the form of classes, with meaningfully-named fields and methods. This makes the tests more readable by isolating them from the implementation details, and also allows a better use of a business-friendly vocabulary. Here is a simple Page Object, which models the Google search page:

public class GoogleSearchPage {

protected WebDriver driver;

private WebElement q;

private WebElement btnG;

public GoogleSearchPage(WebDriver driver) {
this.driver = driver;
}

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

public void close() {
driver.quit();
}

public String getTitle() {
return driver.getTitle();
}

public void searchFor(String searchTerm) {
q.sendKeys(searchTerm);
btnG.click();
}

public void typeSearchTerm(String searchTerm) {
q.sendKeys(searchTerm);
}

public void clickOnSearch() {
btnG.click();
}
}

Although this class is not of much use in isolation, Selenium 2 provides a way to map it to a real web page. The PageFactory class provides a convenient way of initalizing the Page Object fields.

public class WhenAUserSearchesOnGoogle {

private GoogleSearchPage page;

@Before
public void openTheBrowser() {
page = PageFactory.initElements(new FirefoxDriver(), GoogleSearchPage.class);
page.open("http://google.co.nz/");
}

@After
public void closeTheBrowser() {
page.close();
}

@Test
public void whenTheUserSearchesForCatsTheResultPageTitleShouldContainCats() {
page.searchFor("cats");
assertThat(page.getTitle(), containsString("cats") );
}
}

By default, it will map Page Object properties to fields with matching ids or names, so the example given here will work fine out of the box. But sometimes we need more control over identifying elements in the HTML page and mapping them to our Page Object fields. One way to do this is to use the @FindBy annotation, as shown in the following code:

public class GoogleSearchPage {

protected WebDriver driver;

@FindBy(id="q")
private WebElement searchField;

@FindBy(name="btnG")
private WebElement searchButton;

public AnnotatedGoogleSearchPage(WebDriver driver) {
this.driver = driver;
}

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

public void close() {
driver.quit();
}

public String getTitle() {
return driver.getTitle();
}

public void searchFor(String searchTerm) {
searchField.sendKeys(searchTerm);
searchButton.click();
}

public void typeSearchTerm(String searchTerm) {
searchField.sendKeys(searchTerm);
}

public void clickOnSearch() {
searchButton.click();
}
}

Selenium 2/Web Driver provides a host of powerful features in a very clean API - in fact, we've just scratched the surface in this article. In future articles, I will discuss testing AJAX applications with WebDriver, and writing your own test DSLs with WebDriver and BDD tools like easyb. So stay tuned! In the meantime, if you are serious about automated web testing, you really should try it out.

If you want to learn more about Selenium 2/WebDriver, be sure to check out the upcoming TDD/BDD training workshops (coming up in Canberra, Wellington, and Sydney), and the Java Power Tools bootcamps (coming up soon in London and Canberra) - both of these courses include brand-new Selenium 2/WebDriver. We are also running this module on request as a full-day course. Check it out!

From http://weblogs.java.net/blog/johnsmart/archive/2010/08/09/selenium-2web-driver-land-where-page-objects-are-king

Published at DZone with permission of John Ferguson Smart, author and DZone MVB.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

King Sam replied on Fri, 2012/02/24 - 10:48am

Excellent article! Please, you have some news of the day or planning for the final release of Selenium 2 ? I'll be waiting for your next articles Thanks,

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.