Sep 08 2008

What’s wrong with current web testing approaches

Tag:Tag , , , , alvinsingh @ 21:42

Of late I have been taking a very keen interest in the area of web-testing (I wrote previously on automating testing with selenium ). It is not an easy task to tackle efficiently.

Of course you can get a team of QA’s to write up tests in your favourite web testing tool. How much time/effort/money does this cost? What about when your web application starts to change? What if I change some pages dramatically - how will my tests change?

From my experience the issue isn’t getting tests written up and working. It is more so the maintainability of the tests which determine the success of your web-testing strategy. I have looked at various products and frameworks which are available today in this space and there is a common approach to testing that 99% of them take or suggest to take (via their IDEs and docs).

The approach is quite simple and leverages aspects of the web ‘ecosystem’ -

  • html identifiers via ids and/or xpath expressions
  • wait times for page loads and ajax
  • user actions such as clicks, hover and typing

You write tests using a combination of the above to simulate what a user would do on your web application. To give an example (taken from my previous post) of how this would like look in Selenium -


	public class AuthenticationTests extends AbstractSeleniunTest {

		@Test
		public void testUserLogin() throws InterruptedException {
			selenium.open("/");
			selenium.click("link=Login");
			waitForElement("login-form_j_username", "Login not shown");
			selenium.type("login-form_j_username", "xxxx@xxxx.com");
			selenium.type("login-form_j_password", "xxxx");
			selenium.click("login-form_0");
			waitForElement("user-home-link", "User home link not shown");
			assertTrue(selenium.isElementPresent("user-home-link"));
		}
	}
  • Line 5: direct references are made to pages to open
  • Line 6: things to click by name
  • Line 7: wait for something
  • Line 12: check if some field is present identified by its id

There is, I think, a very big problem with writing tests in this way. The test cases are very tightly coupled to the html code of the web application. If this code changes - your tests will start to break. This may or may not be a problem for most people however if you are working on a dynamic web application - this is most likely a very big problem. As I alluded to earlier, this problem is not specific to Selenium which I have used to illustrate the example.

Page Object Pattern

A design pattern which is emerging that addresses this issue is the Page Object pattern. I first read about it here and have seen mentions of it else where. As with all design patterns, people are almost certainly already using it (that is where patterns come from) and now has been given a name. Also something to note is the items raised in this post and the pattern itself are not confined to web applications - it covers any GUI based testing.

The ‘Page Object’ pattern is akin to the Facade design pattern from the GoF. The PageObject is a layer of abstraction called which hides away the details of the page implementation. This object then exposes only actions/methods relevant to the tests (e.g. user actions, specific test methods).

The pattern very much follows the principle of seperation of concerns. The tests are not concerned about the ‘id’ of a particular ‘div’ or the name of a button, nor should they be. Instead they should only be concerned with actions a user may do on a page and additional operations required for the test cases (there are exceptions). In this way, the test cases should begin to develop an immunity to changes in the GUI.

Below is an example of the previous test rewritten using the Page Object pattern. I have left out the actual PageObject’s to keep this post to a sane length. Inside the PageObject, the actions and methods are driven by the same web constructs discussed in the beginning of the post (ids, clicks etc). In later posts I will show how to implement the actual PageObject’s and test cases using Selenium.


public class AuthenticationTests extends AbstractSeleniunTest {

	@Test
	public void testUserLogin() {
		IndexPage index = new IndexPage(); // Open home page
		LoginPage loginPage = index.loginPage(); // Go to login page
		index = loginPage.login("user", "password"); // Login entering user and password
		assertTrue(index.isLoggedIn()); // Ensure I am logged in
	}
}
  • Line 5: IndexPage PageObject opened (i.e. open “/”)
  • Line 6: Go to login page (LoginPage PageObject returned)
  • Line 7: Login by passing username and password to the login method
  • Line 8: check if I am logged in

Looking at the above, it is quite clear that even if the html of my page changes the test case will not break (not a single html related entity is referenced). Test cases won’t break in this case - the PageObject(s) will. This is a very important distinction because the majority of the effort and work will be in the test cases. The PageObject’s will be reused by many tests and should be relatively small classes with simple methods.

Another very important characteristic of the above test case is the language/constructs used in the above test case are ‘natural’ and intuitive. This allows the non-technical to define test cases in a manner very similar to the actual tests themselves. Given the right tools, it would be quite easy for testers and analysts to write test cases using this pattern which are maintainable and more resistant to lower level changes.

As mentioned earlier, I will follow up this post in the next week or so with an actual test case and implementation using Selenium.

Hopefully someone in the field is reading..

BlogMemes del.icio.us Digg DZone Facebook FeedMeLinks Google Google Reader Ask.com Yahoo! MyWeb Newsvine reddit Simpy SlashDot StumbleUpon Technorati

May 18 2008

Automate Website Testing with Selenium RC

Tag:Tag , , , , alvinsingh @ 23:59

One of the difficulties I face when we do a release is regression testing - specifically testing the front-end (website) to ensure everything is working/looking correct. At the moment we have a team of 2 testers who work almost feverishly when a release is getting close to ensure that it is indeed a good release. To address this issue I have been looking at Selenium and how we can use it to automate a large portion of this regression testing.

I come from a Java background where we use Continuous Integration so I wanted to integrate Selenium testing as part of our build cycle. Selenium comes in a number of forms - IDE, Core, RC and Grid (some of which work together). In this article I will be focusing on Selenium RC - to find what the others are about head over to the Selenium website.

Since the website I work on utilizes AJAX in quite a number of places, one of the requirements I had for Selenium was that it had to be able to deal with AJAX enabled sites. Although the Selenium website doesn’t specifically talk about it, I did find help regarding the subject in their user-forums.

Below is a short write-up on the steps I took to get some basic tests running via Selenium RC. Note that you should download the Selenium RC package to get the java client libs and the Selenium server - available here.

1. Selenium Test Case

First I setup a JUnit 4 abstract class to setup selenium before my actual test class runs. You can see below in my AbstractSeleniumTest class in the setUp method I construct a DefaultSelenium object by passing in parameter values obtained from the System - this is so that I can test different environments (local dev, integration, uat etc.) configured at runtime. Also to note are the use of the new JUnit4 annotations.


public abstract class AbstractSeleniumTest {

	protected static Selenium selenium;

	protected static String SELENIUM_SERVER_HOST="selenium.server.host";
	protected static String SELENIUM_SERVER_PORT="selenium.server.port";
	protected static String SELENIUM_BROWSER_STARTCOMMAND="selenium.browser.startCommand";
	protected static String SELENIUM_BROWSER_URL="selenium.browser.url";

    @BeforeClass
    public static void setUp() {
        selenium = new DefaultSelenium(System.getProperty(SELENIUM_SERVER_HOST),
        							   Integer.parseInt(System.getProperty(SELENIUM_SERVER_PORT)),
        							   System.getProperty(SELENIUM_BROWSER_STARTCOMMAND),
        							   System.getProperty(SELENIUM_BROWSER_URL));
        selenium.start();
    }

    @AfterClass
    public static void tearDown() {
        selenium.stop();
    }

    public void waitForElement(final String waitingElement, String timeoutMessage) {
        new Wait() {
            public boolean until() {
                return selenium.isElementPresent(waitingElement);
            }
        }.wait(timeoutMessage);
    }
}

Now my class which will test an AJAX’ified login box.


public class AuthenticationTests extends AbstractSeleniunTest {

    @Test
    public void testUserLogin() throws InterruptedException {
        selenium.open("/");
        selenium.click("link=Login");
        waitForElement("login-form_j_username", "Login not shown");
        selenium.type("login-form_j_username", "xxxx@xxxx.com");
        selenium.type("login-form_j_password", "xxxx");
        selenium.click("login-form_0");
        waitForElement("user-home-link", "User home link not shown");
        assertTrue(selenium.isElementPresent("user-home-link"));
    }
}

2. Setting up Selenium Server

Selenium server needs to be setup on the box on which you plan to run the tests from and this box should also have some web browsers installed. I use a mac so I am testing under firefox (note - that firefox3 beta does not seem to work yet so ensure you have firefox2 ).

Once you have extracted the Selenium RC download, run the server as below.
[sourcecode language='bash']
computer:selenium-server-1.0-beta-1 alvins$ pwd
~/selenium-remote-control-1.0-beta-1/selenium-server-1.0-beta-1
computer:selenium-server-1.0-beta-1 alvins$ java -jar selenium-server.jar -interactive
alvin-singhs-computer:selenium-server-1.0-beta-1 alvins$ java -jar selenium-server.jar -interactive
23:18:15.624 INFO - Java: Apple Inc. 1.5.0_13-119
23:18:15.625 INFO - OS: Mac OS X 10.5.2 i386
23:18:15.628 INFO - v1.0-beta-1 [2201], with Core v1.0-beta-1 [1994]
23:18:15.724 INFO - Version Jetty/5.1.x
23:18:15.725 INFO - Started HttpContext[/,/]
23:18:15.726 INFO - Started HttpContext[/selenium-server,/selenium-server]
23:18:15.727 INFO - Started HttpContext[/selenium-server/driver,/selenium-server/driver]
23:18:15.732 INFO - Started SocketListener on 0.0.0.0:4444
23:18:15.732 INFO - Started org.mortbay.jetty.Server@2a5330
Entering interactive mode… type Selenium commands here (e.g: cmd=open&1=http://www.yahoo.com)
[/sourcecode]
Note that if you are in a linux headless environment you must have X installed and run Xvfb before running the above.
[sourcecode language='bash']
computer:selenium-server-1.0-beta-1 alvins$ Xvfb :1
computer:selenium-server-1.0-beta-1 alvins$ export DISPLAY=:1
[/sourcecode]

3. Running the Test

I use eclipse for development and so to run the test, right click on the Test class–>Run As–>Open Run Dialog–>Arguments Tab as below. The arguements are quite self explanatory - they are fed into the DefaultSelenium class as can be seen in the code from Step 1. The third argument deserves a mention - this is the browser you wish to run your tests on - some supported browsers include iexplore, konqueror, firefox, safari and opera.



Once you are all set, simply click Run and what should happen is that the test will contact the selenium server, start firefox and start executing the tests. Of course if you are running in a headless environment you will see firefox launch but running in interactive mode you should still be able to see the selenium server logging its activity.

[sourcecode language='bash']
23:24:14.037 INFO - Checking Resource aliases
23:24:14.042 INFO - Command request: getNewBrowserSession[*chrome, http://xxxx.com] on session null
23:24:14.042 INFO - creating new remote session
23:24:14.118 INFO - Allocated session 15a43743285c4dba913a3ff0ab2b5dea for http://xxxx.com, launching…
23:24:14.160 INFO - Preparing Firefox profile…
23:24:18.520 INFO - Launching Firefox…
23:24:20.819 INFO - Got result: OK,15a43743285c4dba913a3ff0ab2b5dea on session 15a43743285c4dba913a3ff0ab2b5dea
23:24:20.830 INFO - Command request: open[/, ] on session 15a43743285c4dba913a3ff0ab2b5dea

[/sourcecode]

Depending on your build environment you will have to decide how you want to integrate these tests as part of your build. The Selenium Wiki has some great examples on different ways to do this. My company uses Bamboo (CI Server) from the great people at Atlassian and this runs the tests after successful builds.

Happy testing!

BlogMemes del.icio.us Digg DZone Facebook FeedMeLinks Google Google Reader Ask.com Yahoo! MyWeb Newsvine reddit Simpy SlashDot StumbleUpon Technorati