Thursday, 26 May 2011

Browser compatibility with Webdriver

Introduction

With selenium web driver you can write very powerful test scenarios
for your web application. The idea is that all applications need at some
point a kind of integration testing where you can see the application in
action on your screen. In the end, the whole goal of your development
effort is the web application running in your browser (if you are
developing a web application, of course). With the Selenium webdriver you
can control the browser interaction, like clicking on links and filling in
fields, from within the java code itself, there is no need any more for the
javascript scripts of selenium RC.

Beside the fact you need to know that your application works as
specified in the requirements, it is also important to know it will run in
more than one browser. This text explains how you can easily run the same
test in different browsers.


Typical Webdriver test
WebDriver driver = new HtmlUnitDriver();
driver.get("http://www.google.com");
WebElement element = driver.findElement(By.name("q"));
element.sendKeys("JSF Corner");
element.submit();

driver.findElement(By.linkText("JSF Corner")).click();

Assert.assertTrue("Rudy De Busscher".equals(driver.findElement(By.id("sub_header"))
   .getText()));

In a typical web driver test, you instantiate a specific driver and
then performs the commands to simulate the user and verifies if we have
the behavior that we want. But now our tests our bound to a certain type
of browser, in the case of the example, a non GUI version which tries to
emulate as best as it can a browser.

But when we have defined an enum with all the browsers (Firefox, IE
and Chrome are supported for the moment) and the requested browser is
available in a thread local variable somehow, we could dynamically specify
the browser. These kinds of drivers, actually starts up the browser program
and performs the commands and tests.

    private WebDriver driver;

    private static ThreadLocal<Browser> browser = new ThreadLocal<Browser>() {
        @Override
        protected Browser initialValue() {
            return Browser.FIREFOX;
        }
    };

    public void setupBrowser() {
        switch (browser.get()) {

            case FIREFOX:
                driver = new FirefoxDriver();
                break;
            case INTERNET_EXPLORER:
                driver = new InternetExplorerDriver();
                break;
            case CHROME:
                driver = new ChromeDriver();
                break;
        }
    }

This can be placed in an abstract super class. Now, we need a
way to define which browser we want to have.

JUnit runner

Within the JUnit framework, the Runner is responsible for executing
the test method and the required administrative tasks, like before and
after methods. So when we make a custom JUnit Runner, we should be able to
execute a certain test method multiple times. Once for each browser by
specifying another enum value for the thread local variable.

The interception point is the runChild method in the
BlockJUnit4ClassRunner class. This method is called for each test method
that is executed. Intercepting it and calling the overridden method
multiple times each time with a different browser in the thread local
variable,

    @Override
    protected void runChild(FrameworkMethod method, RunNotifier notifier) {
        Annotation a = method.getMethod().getDeclaringClass().
                          getAnnotation(BrowserToTest.class);
        Browser[] browsers = {Browser.FIREFOX};
        if (a != null) {
            browsers = ((BrowserToTest) a).value();
        }
        for (Browser browser : browsers) {
            AbstractMultipleBrowserTest.setBrowserToRun(browser);
            super.runChild(method, browser), notifier);
        }

        AbstractMultipleBrowserTest.setBrowserToRun(Browser.FIREFOX);
    }

The idea is quite simple. We retrieve the annotation
BrowserToTest which can be placed on the class and we specify there the
browsers (by using the enum value) we like to test. This results in an
array of Browser's. For each item in this array, we perform the original
functionality (by calling the super.runChild method) after we have set the
thread local variable with one of the array item values.

If we now create a test method where the class is annotated with
BrowserToTest and ask our specific junit runner, the test method is
performed multiple times, each time on another browser.

These basic concepts can be improved in various ways. We can write
more complex logic that the annotation BrowserToTest is also found on a
super class of our test class (so that we don't have to repeat this
information). Another improvement is creating a descendant class of
FrameworkMethod (the type of the first argument in the class to
super.runChild) so that we can have a different name for the test method
depending on the browser on which it is executed. And all kind of other
improvements can be found according to the specific project
requirements.

The code can be found here.

Conclusion

I hope that this text showed you how you can combine JUnit and
Selenium webdriver to realize some functional tests that allow you to
develop the web application according to the functional specifications. I
use it currently on a project in combination with DBUnit so that we can
put the database in a know state before each test and verify, if needed,
that the user actions lead to the correct records in the database.