Selenium The Movie / Directors Cut

Selenium being driven from JUnit is my preferred mode of operation. I've made a movie of Selenium testing the PetSoar app under JUnit control. The other mode "standalone" or "in-browser", is the original mode of operation. It has to be said that the driven mode is a little slower, but I prefer it greatly as I think it overcomes some glass ceilings I perceive with the other.

The movie was shot on the Mac in Firefox, but don't forget that Selenium is designed for Web Browsers anywhere.  It is 10Mb, and requires quicktime. It is from the next version of Selenium, which should be 0.4, testing the 'PetSoar' app from Joe Walnes' (and friends) Java Open Source Programming book.

        download here

The movie is only a few minutes long, starts with a screen shot of the test source, switches to a shell to see Ant:
  1. Kick off a build (compile, make webapp etc)
  2. Launch the webapp in its container (Jetty)
  3. Launch or switch to Firefox (look for the gratuitous advert for ThoughtWorks - our page for hiring open source developers) 
  4. Starts the Selenium tests.
Last of all, Ant is returned to the front to see the JUnit results in the command log.  Imagine the usual JUnit reports.

During the tests the PetStore app is instantiated and torn down several times. Once for each test method, in fact, as is usual for JUnit tests. If the test were reworked to use  a OneTimeSetup the same web server instance could be used, but that might introduce side effects into the testing.  The blankish testing screen that appears after testsComplete() and the delay before the first PetStore page appears, because JSP pages are compiling again after the web server restarts. That happens five times during the test as there are, shock horror, five test methods.

selenium oview preview

The TestCase

package com.paulhammant.petstore;
import com.thoughtworks.selenium.DefaultSelenium;
import com.thoughtworks.selenium.Selenium;
import com.thoughtworks.selenium.embedded.jetty.DirectoryStaticContentHandler;
import com.thoughtworks.selenium.embedded.jetty.JettyCommandProcessor;
import com.thoughtworks.selenium.launchers.SystemDefaultBrowserLauncher;
import junit.framework.TestCase;
import java.io.File;

public class FooTestCase extends TestCase {
    Selenium selenium;

    protected void setUp() throws Exception {
        super.setUp();
        File codeRoot = getCodeRoot();
        selenium = new DefaultSelenium(
                        new JettyCommandProcessor(new File(codeRoot, "war-for-selenium"), DefaultSelenium.DEFAULT_SELENIUM_CONTEXT,
                                new DirectoryStaticContentHandler(new File(codeRoot, "selenium"))),
                        new SystemDefaultBrowserLauncher()
                );
        selenium.start();
    }


    private File getCodeRoot() throws Exception {
        File codeRoot;
        String codeRootProperty = System.getProperty("code_root");
        if (codeRootProperty == null) {
            throw new Exception("'code_root' not specified");
        } else {
            codeRoot = new File(codeRootProperty);
            if (!codeRoot.exists()) {
                throw new Exception("'code_root' not a dir");
            }
        }
        return codeRoot;
    }

    protected void tearDown() throws Exception {
        selenium.testComplete();
        Thread.sleep(2 * 1000);
        selenium.stop();
    }

    public void testLogin() {
        selenium.setContext("Log into PetStore");
        goToFrontPage();
        attemptLogin("duke","duke");
        selenium.verifyTextPresent("Welcome: duke");
    }

    private void attemptLogin(String user, String passwd) {
        selenium.open("/login.jsp");
        selenium.type("username", user);
        selenium.type("password", passwd);
        selenium.clickAndWait("Login");
    }

    public void testBogusLogin() {
        selenium.setContext("Log into PetStore");
        goToFrontPage();
        attemptLogin("lord","lady");
        selenium.verifyTextPresent("Invalid username or password");
        goToFrontPage();
        selenium.clickAndWait("inventory");
        selenium.verifyLocation("/login.jsp:");
    }

    public void testSearch() {
        selenium.setContext("Test of Searching For Pets");
        selenium.open("/storefront/listpets.action");
        selenium.verifyTextPresent("Pets In Stock");
        selenium.type("query", "Cat");
        selenium.clickAndWait("Submit");
        selenium.verifyTextPresent("Tiger");
    }

    public void testInventory() {
        selenium.setContext("Test to check pet inventory");
        attemptLogin("duke","duke");
        goToFrontPage();
        selenium.clickAndWait("inventory");
        selenium.clickAndWait("ListPets");
        selenium.verifyTextPresent("Lizard");
        selenium.verifyTextPresent("Snake");
        selenium.verifyTextPresent("Pigeon");
        selenium.verifyTextPresent("Seagul");
        selenium.verifyTextPresent("Rexio");
        selenium.clickAndWait("PetStore");
    }

    private void goToFrontPage() {
        selenium.open("/");
        selenium.verifyTextPresent("Welcome to PetStore");
    }

    public void testButAPet() {
        selenium.setContext("Test to buy a pet from the inventory");
        attemptLogin("duke","duke");
        goToFrontPage();
        selenium.clickAndWait("Pets");
        selenium.verifyTextPresent("Lizard");
        selenium.clickAndWait("pet-Lizard");
        selenium.clickAndWait("addToCart");
        selenium.verifyTextPresent("Pet Added to Cart");
        selenium.clickAndWait("checkOut");
        selenium.verifyTextPresent("Credit Card Information");
        selenium.clickAndWait("viewCart");
        selenium.verifyTextPresent("$13.75");
        selenium.clickAndWait("del-Lizard");
        selenium.verifyTextPresent("Pet Removed from Cart");
        selenium.verifyTextPresent("This Phrase Is Not In The Page");
        selenium.clickAndWait("Logout");
        selenium.clickAndWait("PetStore");
    }

}

With a procedural programming language, of course, you can use loops and conditionals.  All that is demonstrated above is some reusable methods - goToFrontPage() and attemptLogin().

Next, I am going to try doing some pull the rug from under the feet of the app tests.  e.g. Delete Lizard from the DB after putting a lizard in a cart and before going to checkout.  Joe, Mike Cannon-Brookes, Ara and Pat Lightbody probably did not test that explicitly, but their are such good coders I think the PetSoar/Petstore app will  gracefully indicate outage.

Also, in the next version I'm going to show something that is thought to make Selenium testing more accessible to non-coders (potentially many in the testing community). The thing in question would be Groovy test scripts.

Thus  ..

    public void testLogin() {
        selenium.setContext("Log into PetStore");
        goToFrontPage();
        attemptLogin("duke","duke");
        selenium.verifyTextPresent("Welcome: duke", "");
    }


 .. would become ..

    void testLogin() {
        setContext("Log into PetStore")
        goToFrontPage()
        attemptLogin("duke","duke")
        verifyTextPresent("Welcome: duke")
    }

May 7, 2005.