Coming Up for Air

Grabbing Screenshots of Failed Selenium Tests

For the GlassFish Administration Console, we have quite a few tests (about 133 at last count). Given the nature and architecture of the application, we’ve chosen Selenium to drive our tests. One of the problems we’ve faced, though, is understanding why a test failed due to the length of time the tests take (roughly 1.5 hours to run the whole suite). Sometimes, we can look at the log and know exactly what failed, but not the why. Did the screen render correctly? Did, perhaps, the click, etc. not get performed (we’ve seen instances of that) leaving the application in a state not expected by the test? Since I usually start the tests and move on to something else, we had no way of knowing. Until now. I finally sat down and figured out how to grab a screen shot when a test fails. I’ve distilled that technique down to its essentials, which I’ll share here.

In this example, we’re going make sure example.com works correctly. Sort of. : ) What we need to do first, though, is set up our project, which we’ll configure (for simplicity’s sake) as a simple Maven-based Java application. The important pom.xml elements are these:

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.8.2</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.seleniumhq.selenium</groupId>
            <artifactId>selenium-server</artifactId>
            <version>2.14.0</version>
            <exclusions>
                <exclusion>
                    <groupId>org.testng</groupId>
                    <artifactId>testng</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
    </dependencies>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>2.3.2</version>
                <configuration>
                    <source>1.6</source>
                    <target>1.6</target>
                    <showDeprecation>true</showDeprecation>
                </configuration>
            </plugin>
        </plugins>
    </build>

Simple enough. Now, the test:


public class ScreenshotDemoTest {
    private static WebDriver driver;
    private static Selenium selenium;
    @Rule
    public ScreenshotTestRule screenshotTestRule = new ScreenshotTestRule();

    @BeforeClass
    public static void beforeClass() {
        driver = new FirefoxDriver();
        selenium = new WebDriverBackedSelenium(driver, "http://example.com");
    }

    @AfterClass
    public static void afterClass() {
        selenium.close();
    }

    @Test
    public void testThatSucceeds() {
        selenium.open("/");
        assertTrue(selenium.isTextPresent("As described"));
    }

    @Test
    public void testThatFails() {
        selenium.open("/");
        assertTrue(selenium.isTextPresent("Your test should fail here"));
    }
}

This is an extremely simple test that should make sense to those familiar with Selenium. The interesting part here is lines 4 and 5. What’s that Rule? That little nugget is a means of extending JUnit in an AOP fashion. In our case, that’s where the magic is going to happen, so let’s take a look at ScreenshotTestRule:

    class ScreenshotTestRule implements MethodRule {
        public Statement apply(final Statement statement, final FrameworkMethod frameworkMethod, final Object o) {
            return new Statement() {
                @Override
                public void evaluate() throws Throwable {
                    try {
                        statement.evaluate();
                    } catch (Throwable t) {
                        captureScreenshot(frameworkMethod.getName());
                        throw t; // rethrow to allow the failure to be reported to JUnit
                    }
                }

                public void captureScreenshot(String fileName) {
                    try {
                        new File("target/surefire-reports/").mkdirs(); // Insure directory is there
                        FileOutputStream out = new FileOutputStream("target/surefire-reports/screenshot-" + fileName + ".png");
                        out.write(((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES));
                        out.close();
                    } catch (Exception e) {
                        // No need to crash the tests if the screenshot fails
                    }
                }
            };
        }
    }

Implementations of MethodRule act as an interceptor for your tests. You can do all the usual types of things you might do in an interceptor (in fact, in GlassFish, we use this is allow us to run only specific test methods, e.g., mvn -Dtest=MyTest -Dmethod=testMethod1,testMethod3). Here, though, we want to run every test, but, in the case of failures, which present themselves as Exceptions, we wan’t to capture the screenshot. Once we’ve saved the image to a file (note the assumption that we’re running under Maven in captureScreenshot()), we rethrow the Throwable to make sure the failure is reported.

If you run these tests, you should see one success and one failure, and you should see target/surefire-reports/screenshot-testThatFails.png. How easy was that?! : )

Full source code can be found here. I hope this helps you as much as it has me. : )

Popularity: 3% [?]

2 Comments to Grabbing Screenshots of Failed Selenium Tests

  1. January 31, 2012   #

    Nice example; I wrote something similar for our test suite. Note that more recent versions of JUnit have deprecated MethodRule in favor of TestRule. The latter supports the @ClassRule annotation, for use with *static* fields. This means that you can then also take a screenshot if some @BeforeClass or @AfterClass code fails.

  2. February 20, 2012   #

    Could you please add a description of how to instantiate the driver object (line 18 in ScreenshotTestRule)? Thanks.

You can use these HTML tags and attributes: <b> <blockquote cite=""> <code> <i> <strike> <em style=""> <strong style=""> <span style=""> <p align="" style=""> <a href="" title="" rel="" rev="" target="" name=""> <div align="" style=""> <font color="" face="" size="" style=""> <u align="" style=""> <li align="" style=""> <ul align="" style=""> <ol align="" style="">

Comments will be closed on May 23, 2012.