Sunday, October 9, 2011

Selenium Object Repository - Part 1

On the contrary to popular belief, Selenium (http://seleniumhq.org)  is not a test automation tool. By definition , Selenium is a web browser automation tool. Hence, we might not see rich features inherited to Test Automation tools such as QTP. However, Selenium works really well as a browser automation tool.  It's light weight, portable and fairly easy to run, and it works well with many web application development platforms such as Java, PHP, Python etc... Another reason for its immense popularity is that it works really well with different browsers.

As a browser automation tool, selenium can be used in conjunction with an application testing framework (such as Junit for Java) to automate web application testing.  It has been used like this as an alternative for expensive and  complex test automation tools. However, one of the key things that was missing is an object repository. Popular test automation tools such as QTP provides a comprehensive Object Repository where we can represent application elements as objects and reuse them within test cases.

By utilizing the page object pattern which represent the elements of your web application as a series of objects, we can create an Object Repository which can be used with selenium. This will provide us a huge advantage of not having to duplicate browser automation code in order to test different aspects of the application. For example in order to test an application with a user login, we might need to login to the application initially in every test. In the conventional way we will have to duplicate the code for login inside every test case. With selenium page object pattern we can encapsulate login method of the application within a "LoginArea " object which we'll be able to re-use in every test case.

Using page object pattern we can de-couple selenium logic from the test case. This is really important since test logic has nothing to do with selenium operations. Below example will clearly demonstrate the difference and advantage of using selenium page objects.
Code Listing below shows a typical traditional selenium and Junit test to test a web application. The objective of the code was to test Gmail login  and "Move To Spam" operations.

package com.selenium.standard.tests;

/**
 * URL of the application will be created by concatenating the APP_HOST And APP_PATH
 */
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import com.thoughtworks.selenium.SeleneseTestBase;

public class GmailTest extends SeleneseTestBase {
 public static final String APP_HOST = "http://www.gmail.com/";
 public static final String APP_PATH = "/";

 public static final String TEST_BROWSER = "*iexplore";
 public static final int SELENIUM_PORT = 4444;
 public static final String HOST = "localhost";
 public static final String MAX_TIME_OUT = "1000000000";

 @Before
 public void initialzeTests() throws Exception {
  setUp(APP_HOST, TEST_BROWSER, SELENIUM_PORT);
  selenium.open(APP_PATH);
  selenium.windowMaximize();
 }

 @Test
 public void testGmailLoginFail() throws Exception {
  // Enter a wrong login details first
  selenium.type("Email", "webappcenter");
  selenium.type("Passwd", "webappcenter123sds");
  selenium.click("signIn");
  String waitForXPath = "The username or password you entered is incorrect";
  selenium.waitForCondition("selenium.isTextPresent(\"" + waitForXPath
    + "\")", MAX_TIME_OUT);
  assertTrue(selenium
    .isTextPresent("The username or password you entered is incorrect."));
 }

 @Test
 public void testGmailLoginSucess() throws Exception {

  // login to gmail with correct logins
  selenium.type("Email", "webappcenter");
  selenium.type("Passwd", "webappcenter123");
  selenium.click("signIn");
  String waitForXPath = "//html/body/div/div[2]/div/div[2]/div/div/div/div[3]/div/div/div/div/div";
  selenium.waitForCondition("selenium.isElementPresent(\"" + waitForXPath
    + "\")", MAX_TIME_OUT);

  // test Login sucessfull
  assertTrue(selenium
    .isElementPresent("//html/body/div/div[2]/div/div/div[4]/div/div[2]/div/table/tbody/tr/td[2]/div[3]"));
 }

 @Test
 public void testMoveToSpan() throws Exception {
  // login to gmail with correct logins
  selenium.type("Email", "webappcenter");
  selenium.type("Passwd", "xxxpasswordxxx");
  selenium.click("signIn");
  String waitForXPath = "//html/body/div/div[2]/div/div[2]/div/div/div/div[3]/div/div/div/div/div";
  selenium.waitForCondition("selenium.isElementPresent(\"" + waitForXPath
    + "\")", MAX_TIME_OUT);

  // Get the UI to basic view
  selenium.click("//html/body/div/div[2]/div/div[2]/div/div[2]/div[2]/div[5]/div/div/a");
  waitForXPath = "You are currently viewing Gmail in basic HTML";
  selenium.waitForCondition("selenium.isTextPresent(\"" + waitForXPath
    + "\")", MAX_TIME_OUT);
  // Move first two items to span
  selenium.click("//html/body/table[3]/tbody/tr/td[2]/table/tbody/tr/td[2]/form/table[2]/tbody/tr/td/input");
  selenium.click("//html/body/table[3]/tbody/tr/td[2]/table/tbody/tr/td[2]/form/table[2]/tbody/tr[3]/td/input");
  selenium.click("//html/body/table[3]/tbody/tr/td[2]/table/tbody/tr/td[2]/form/table/tbody/tr/td/input[2]");
  waitForXPath = "2 conversations have been marked as spam.";
  selenium.waitForCondition("selenium.isTextPresent(\"" + waitForXPath
    + "\")", MAX_TIME_OUT);
  assertTrue(selenium
    .isTextPresent("2 conversations have been marked as spam."));
 }

 @After
 public void releaseTestResources() throws Exception {
  selenium.stop();
  selenium = null;
 }

}


As you can see, we clearly has mixed selenium operation logic with the test logic, this violate the single responsibility principle of good object oriented programming practices. Let's see if we can make it better. Below is the same test but with applied page object pattern.

package com.selenium.pageobject.tests;

import org.junit.Test;

import com.selenium.pageobject.libs.GmailEmailList;
import com.selenium.pageobject.libs.GmailLogin;
import com.selenium.pageobject.libs.GmailTopButtonPanel;
import com.selenium.pageobject.libs.common.PageObjectBaseTest;

public class GmailPageObjectTest extends PageObjectBaseTest {

 private GmailLogin loginPage; // This is the login page object
 private GmailEmailList emailListPanel; // This is the email list object
 private GmailTopButtonPanel buttonPanel; // This is the button Panel Object

 public GmailPageObjectTest() {
  loginPage = new GmailLogin(localSelenium);
  emailListPanel = new GmailEmailList(localSelenium);
  buttonPanel = new GmailTopButtonPanel(localSelenium);
 }

 @Test
 public void testGmailLoginFail() throws Exception {
  // Test for failed login
  loginPage.login("webappcenter", "adadjsdksdk");
  assertTrue(loginPage.verifyLoginFailure());
 }

 @Test
 public void testGmailLoginSuccess() throws Exception {

  // Test for successful login
  loginPage.login("webappcenter", "webappcenter123");
  assertTrue(loginPage.verifyLoginSuccess());
 }

 @Test
 public void testMoveToSpan() throws Exception {
  if (loginPage.loginBasicView("webappcenter", "xxxpasswordxxx")) {
   emailListPanel.selectFirstTwoEmails();
   buttonPanel.reportSpam();
   assertTrue(buttonPanel.verifyReportSpan(2));
  } else {
   fail("Could not login to basic view");
  }
 }
}


Firstly, we have 3 page objects defined (GmailLogin, GmailEmailList and GmailTopButtonPanel) and all the selenium logic to locate and handle these objects are now moved to the relevant object itself.  This approach has considerably improved our test by separating the selenium and test logic and improving the readability as well. Another advantage is that in most cases application changes are not affected to the test case. For example, say Google introduce a new login window  and IDs of text fields were changed, in the first code listing we'll have to change the test case itself in order to update this change. However, in the second example we only have update GmailLogin page object and our test case itself is not aware of this change at all.

During this series of articles we'll develop a fully functional  object repository for selenium which can be reused in any project. I'll be using  Java and related technologies as the development platform,  but with little changes you can implement the same for other platforms as well.
Having a clear understanding of the advantages of using a Object Repository, in the next article we'll start to implement our very own selenium Object Repository.

NB: It has been over a year since I've written the original article. Sorry I've never got the time to write the followup of this. However, the time isn't wasted. I have created a fully functional Selenium Object Repository in Java which you can use in your projects. Work of this framework is now completed. I'm in the process of documenting it. Please bare with me, I'll have all the details of using it in the next followup article to this.