Archive

Archive for the ‘selenium’ Category

Taking Screenshots with Selenium

December 22, 2006 16 comments

I had written about using Selenium, TestNG, Cargo and Maven to write an automated smoke test suite. It would be useful if we could get a screenshot of the application under test at every stage of the test – the screenshots would be useful to diagnose test failures. In this post I describe a crude screenshotting mechanism that I have developed to take screenshots for tests running in Selenium.

Note: The code used for this post can be downloaded here. Due to WordPress restrictions, I had to rename it with extension .jpg. Kindly rename it to .tar.gz. Also, the code is a continuation from my previous post.

The basic idea is to extend com.thoughtworks.selenium.DefaultSelenium class such that it takes a screenshot after each significant step.


public class ScreenshottingSelenium extends DefaultSelenium {
    // ..

    @Override
    public void click(String locator) {
        super.click(locator);
        takeAScreenShotOfTheApp();
    }

    @Override
    public void open(String url) {
        super.open(url);
        takeAScreenShotOfTheApp();
    }

    @Override
    public void type(String locator, String value) {
        super.type(locator, value);
        takeAScreenShotOfTheApp();
    }

    // ..
}

To take a screenshot, we need to make sure that the application window is the in the foreground and is maximized, and then take a screen capture.

To take a screen capture, we use the java.awt.Robot class:


public void takeAScreenShotOfTheApp() {
    Dimension screenDim = Toolkit.getDefaultToolkit().getScreenSize();
    Rectangle screenBounds = new Rectangle(0, 0, screenDim.width, screenDim.height);

    Robot robot = new Robot();
    BufferedImage image =  robot.createScreenCapture(screenBounds);

    File screenshotFile = new File("target" + File.separator
                                   + "image" + System.currentTimeMillis() + ".png");
    ImageIO.write(image, "png", screenshotFile);
}

By default, Selenium shows the application under test in a browser frame. This frame, of course, cannot be maximized to occupy the entire screen. If Selenium Remote Control server is launched in multiWindow mode, the application under test is launched in a seperate browser window. To launch Selenium Remote Control server in multiWindow mode:


@BeforeSuite
public void startSeleniumServer() throws Exception {
    seleniumServer = new SeleniumServer(4444, false, true);
    seleniumServer.start();
}

To make sure that the application under test is shown in a maximized browser window:


public class ScreenshottingSelenium extends DefaultSelenium {
    // ..

    @Override
    public void start() {
        super.start();
        windowMaximize("");
    }

    // ...
}

To make sure that the application browser is on the foreground just before taking the
screenshot:


public class ScreenshottingSelenium extends DefaultSelenium {
    // ..

    @Override
    public void click(String locator) {
        windowFocus("");
        super.click(locator);
        takeAScreenShotOfTheApp();
    }

    // ...
}

Now, if we use ScreenshottingSelenium instead of DefaultSelenium, we would get screenshots of the application in the target directory. But we would like to go one step further – we want to generate a nice HTML report of the screenshots. For this we create a Reporter class:


public class Reporter {
    void startTest() {
        // Called when a test is started; creates the directory
        // for saving screenshot images of this test
    }

    void endTest() {
        // Called when a test ends; creates an HTML summary
        // of all screenshot images of this test
    }

    void recordStep(String description) {
        // Creates a screenshot and saves the image to a file
    }

    public void close() {
        // Called after all tests are run; creates an HTML summary
        // of all tests run
    }
}

The implementation of startTest(), endTest(), and close() are quite simple that I do not describe them here. recordStep is essentially the same as takeAScreenShotOfTheApp() earlier. The HTML reports are generated using couple of FreeMarker templates.

The ScreenshottingSelenium would be configured with a Reporter object; the ScreenshottingSelenium calls methods on the Reporter object at appropriate times. The startTest() method is called from the start() method. The start() method opens a browser window and this we assume signals the start of a test. The endTest() method is called from the stop() method. The stop() method closes the browser window, which we assume signals the end of a test. Method like click(), type(), open() etc call recordStep() method of the reporter object with a text description of the action just performed.


public class ScreenshottingSelenium extends DefaultSelenium {
    private Reporter reporter;

    public ScreenshottingSelenium(CommandProcessor processor, Reporter reporter) {
        super(processor);
        this.reporter = reporter;
    }

    public ScreenshottingSelenium(String serverHost, int serverPort,
            String browserStartCommand, String browserURL, Reporter reporter) {
        super(serverHost, serverPort, browserStartCommand, browserURL);
        this.reporter = reporter;
    }

    @Override
    public void start() {
        reporter.startTest();
        super.start();
        windowMaximize("");
    }

    @Override
    public void stop() {
        super.stop();
        reporter.endTest();
    }

    @Override
    public void click(String locator) {
        windowFocus("");
        super.click(locator);
        reporter.recordStep("Clicked");
    }

    @Override
    public void open(String url) {
        windowFocus("");
        super.open(url);
        reporter.recordStep("Opened " + url);
    }

    @Override
    public void type(String locator, String value) {
        windowFocus("");
        super.type(locator, value);
        reporter.recordStep("Typed '" + value + "'");
    }

    @Override
    public void waitForPageToLoad(String timeout) {
        windowFocus("");
        super.waitForPageToLoad(timeout);
        reporter.recordStep("Page loaded");
    }
}

The Reporter object needs to be created before the testsuite starts execution. Also, the close() method on the reporter object should be called after all tests in the suite are executed. We make use to @BeforeSuite and @AfterSuite TestNG annotations for this purpose:


public class SeleniumSetup {
    private static Reporter reporter;

    @BeforeSuite
    public void setupReporter() throws Exception {
    	reporter = new Reporter(new File("target" + File.separator + "screenshots"));
    }

    @AfterSuite
    public void cleanupReporter() throws Exception {
    	reporter.close();
    }

    public static Reporter getReporter() {
    	return reporter;
    }
}

//..

public class FirstTest {
    private Selenium selenium;

    @BeforeMethod
    @Parameters({ "browser" })
    public void openBrowser(String browser) {
        selenium = new ScreenshottingSelenium("localhost", 4444,
                                              "*"  + browser, "http://localhost:8080/",
                                              SeleniumSetup.getReporter());
        selenium.start();
    }

    // ..
}

To see these ideas in action:
1. Download this code bundle. Rename the .jpg file to selenium-screenshots.tar.gz.
2. The bundle contains a small web application and its smoke testsuite. Uncompress the archive to some place on your disk.
3. Install the web application on your local Maven repository.


> cd selenium-screenshots
selenium-screenshots> mvn install

4. Run the integration testsuite:


selenium-screenshots> cd functest
functest> mvn integration-test

This will download Tomcat from the web, install Tomcat server, deploy our web application onto the server, open Firefox and IE browsers and test drives the web application on the browsers. The screenshots from these would be generated in the functest/target/screenshots directory.

Acknowledgements: This technique was inspired by a forum post by Dan Fabulich.

Update: I fixed a small bug in the instructions given to run the code bundle – thanks to davidm for pointing it out in the comments. I have also updated the code bundle with the URL pointing to a newer version of Tomcat. Some versions of Selenium have trouble launching some versions of Firefox. Hence, I have commented out the FireFox tests; instead the tests run on IE.

Advertisements
Categories: maven, selenium, tech