Automated functional testing with WebDriver
There is nothing nicer than having a functional test suite that checks if your application is still working as it should. It is even nicer to run these tests and see what is happening, while the tests are being executed. Two of the tools that give you this opportunity are Selenium RC and WebDriver.
In the project that I’m working on I use WebDriver to create functional tests. In this post I will try to give you an impression of why we switched from using Selenium RC to WebDriver and how we used WebDriver to test a Content Management System (CMS) and helped us making understandable tests.
Switching from Selenium RC to WebDriver
As I mentioned in the introduction, we switched from using Selenium to WebDriver. You might think why would you write your tests while you can record them with Selenium IDE. This is a great tool, but was not useful in our situation, because the CMS uses auto generated id’s for their elements. This means that a recorded script with Selenium IDE would not work in a continuous build. So in order to create tests we had to write them our self. And when writing your tests WebDriver has a few advantages over Selenium.
With Selenium you need to start a server to execute the integration tests, but with WebDriver you only need to pick a driver and you are good to go. You basically just create a unit test without any dependencies of a server.
One of the other advantages of WebDriver is the clear API. In the next sections I will explain how the API of WebDriver works.
Scenario
Before I continue about WebDriver, I want to explain the test case scenario that I’m using in this post.
In the project we create content that always needs to be present at a specific place in the CMS. An example could be a help or contact page. As the content is expect to be in a specific place, we want to test if the navigation to this content is as it should be. But this is not enough and therefore we would also like to test if the content is present.
Drivers
When you start with WebDriver you have to make a choice which driver you want to use. WebDriver currently supports four different drivers: HtmlUnitDriver, FirefoxDriver, InternetExplorerDriver and the SafariDriver.
The HtmlUnitDriver is really fast, but does not allow you to see what is actually happening. This can become interesting when you just want to execute the test and get an overview of the results. I will not go over the differences between these drivers, but when you want to display the steps that are executed you have to chose one of the other three drivers. In our project we have used the FirefoxDriver as we interacted with a Linux environment.
So the first thing that is needed to create a test is an instance of one of the drivers:
WebDriver driver = new FirefoxDriver();
Now we have to tell the driver to navigate to a page. In our case this will be the login page:
driver.get("http://localhost:8080/cms/login");
When the page is loaded the driver can do a few things to interact with the page. One of the things is that it can find elements on that page. The WebDriver documentation provides an example that fits the situation very well. As most of the login pages it contains a password and username input field. In order to find the password element on our login page WebDriver provides the following options:
WebElement element; element = driver.findElement(By.id("passwd-id")); element = driver.findElement(By.name("passwd")); element = driver.findElement(By.xpath("//input[@id='passwd-id']"));
One of the common parts for each of our tests was the login procedure. As everyone can understand that you don’t want to duplicate this code, WebDriver introduces the Page Objects.
Page Objects
Page Objects simply models the UI areas that you want to interact with in your test. This reduces the amount of duplicated code and means that if the UI changes, the fix need only be applied in one place.
For our tests we introduced a LoginPage object that allows you to login with a specific username and password.
LoginPage loginPage = new LoginPage(); CmsHomePanel homePanel = loginPage.login("username", "password");
The implementation of the login method looks like this:
public CmsHomePanel login(String username, String password) { WebElement usernameElement = driver.findElement(By.name("username")); usernameElement.sendKeys(username); WebElement passwordElemement = driver.findElement(By.name("password")); passwordElemement.sendKeys(password); WebElement submitButton = driver.findElement(By.name(":submit")); submitButton.submit(); return new CmsHomePanel(helper); }
As you may have noticed in the code above a different Page Object is returned. After each login we always end up on the home panel of the CMS. We named this Page Object a panel because the CMS is based on panels.
There is also a different way of lookup up an element and that is via annotations. The annotation based lookup uses the PageFactory to initialize the PageObject. If we would look up the username element in de previous code snippet via annotations it would look like this:
@FindBy(how = How.NAME, using = "username") private WebElement username
Because the CMS uses generated id’s for their elements it becomes quite difficult to find the elements based on an id or name. WebDriver offers a solution by finding the elements based on xpath. Going further with the example test, we now want to test the navigation.
CmsDocumentPanel documentPanel = homePanel.clickOnDocumentTab(); assertTrue(documentPanel.isFolderPresent("navigation")); documentPanel.clickOnFolder("navigation"); assertTrue(documentPanel.isFolderPresent("main-navigation")); documentPanel.clickOnFolder("main-navigation");
Before we can test the navigation we have to click on the document tab, so that we could navigation through the folders. This returns once again a Page Object, namely CmsDocumentPanel. This object has a method that indicates if a folder is present and a method to click on the folder. The implementation of the isFolderPresent(String folderName) and the clickOnFolder(String folderName) method is done with xpath.
Now that we have tested that the navigation folders are in place, it is time to check if the content is actually present. This is also done via a method on the documentPanel Page Object:
assertTrue(documentPanel.isDocumentPresent("Contact")); assertTrue(documentPanel.isDocumentPresent("Help"));
The Page Objects are also responsible checking the state of the page. As you can see in the example code this is done by the methods isFolderPresent() and isDocumentPresent(). These methods gives you the possibility to make assertions in the test. Notice that the assertions are made in the test and not in the Page Object!
Conclusion
I have explained why we preferred to used WebDriver instead of Selenium RC and the way we used the WebDriver API, which resulted in clear and readable tests.
WebDriver offers a lot of possibilities for creating functional test. Although sometimes we had to pull off some tricks to find the correct element, WebDriver made it possible. By making good use of the API it will help you to test your web application in nice and understandable way. Even for other people than developers.