Canoo Webtest, Groovy, and Maintaining Session with Tests

The open source Canoo Webtest is a powerful tool for automating functional and regression tests for your web application. You can script your test steps in XML or in Groovy (or a mix of both), and you can build a comprehensive library of test steps that you combine and reorder into your tests.

The documentation for Webtest is a little sparse when it comes to scripting it with Groovy, and I burned some cycles getting the solution, so I’m documenting it here.

With Webtest tests written in XML, the executable test is the “webtest” Ant task. Repeatable sequences of steps are saved in XML files, and included into that test. For example, here’s a login sequence saved as includeLoginSequence.xml:

<group description="Successfully Log in">
    <invoke description="Go to home page" url="${path}"/>
    <selectForm name="login"/>
    <setInputField description="set user name" name="username" value="myname"/>
    <setInputField description="set user password" name="password" value="mypassword"/>
    <clickButton name="login" description="Click the login button"/>
</group>

And that sequence of steps gets included into the Webtest test:

<webtest name="Log in and go to page 2">
    &includeLoginSequence;
    <invoke description="Go to page 2" url="/page2"/>
</webtest>

Now, this isn’t all you need to run the test, but the key points here are the mechanism for saving and including “chunks” of repeatable step sequences, and the fact that all steps contained within the “webtest” ant task are executed within the same session. That last point is crucial: in order to maintain the session across all test steps, they must be in the same webtest container.

For Webtest tests written in Groovy, the paradigm is a little different. The tests are in a class that extends grails.util.WebTest and ends with the word “Test”, with the steps in a method that begins with the word “test”. For example:

class LoginSuccessfulTest extends grails.util.WebTest
{
    protected def loginSuccessful(path="/home") {
        webtest(name:"Log in and go to page 2") {
            group(description: "Successfully Log in") {
                invoke(description:"Go to home page", url:"${path}")
	        selectForm(name:"login")
                setInputField(description:"set user name", name:"username", value:"myname")
                setInputField(description:"set user password", name:"password", value:"mypassword")
 	        clickButton(name:"login", description:"Click the login button")
                invoke(description:"Go to page 2", url:"/page2")
            }
        }
    }
}

In order to get a sequence of test steps that is repeatable and can maintain session, you need an inherited class that contains a method with the desired sequence. So start with a class that has the sequence but will not be executed as a test itself:

public class LoginBase extends grails.util.WebTest
{
    protected def loginSuccessful(path="/home") {
        group(description: "Successfully Log in") {
	    invoke(description:"Go to home page", url:"${path}")
	    selectForm(name:"login")
            setInputField(description:"set user name", name:"username", value:"myname")
            setInputField(description:"set user password", name:"password", value:"mypassword")
 	    clickButton(name:"login", description:"Click the login button")
        }
    }
}    

Then extend that class into a class that will be executed as a test:

class LoginSuccessfulTest extends LoginBase
{
    testSuccessfulLogin() {
        webtest(name:"Log in and go to page 2") {
            loginSuccessful()
            group(description: "Go to page 2") {
                invoke(description:"Go to page 2", url:"/page2")
            }
        }
    }
}

As you can see, the method loginSuccessful() from the parent class LoginBase gets called in the test method testSuccessfulLogin(). Again, this is not everything you need to execute the test, but it demonstrates the relationship necessary to get a repeatable sequence of steps included in a way that maintains the session.