Capturing screenshots on Selenium test failures

Capturing screenshots of the browser via Selenium is fairly easy to achieve (see a sample method here), but triggering a screenshot only when a test fails is useful for both debugging and result reporting, however this requires a little more code and some modifications to your tests.

JUnit5

For Junit5, add the following class file into your test project:

package com.example;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.Optional;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.junit.jupiter.api.extension.TestWatcher;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;

/**
 *
 * @author steve
 *
 * to use, add the following to your JUnit5 tests:
 *
 * @RegisterExtension 
 * ScreenshotWatcher5 watcher = new ScreenshotWatcher5(driver, "target/surefire-reports");
 *
 */
public class ScreenshotWatcher5 implements TestWatcher {

    WebDriver driver;
    String path;

    public ScreenshotWatcher5(WebDriver driver, String path) {
        this.driver = driver;
        this.path = path;
    }

    @Override
    public void testAborted(ExtensionContext context, Throwable throwable) {
        // do something
    }

    @Override
    public void testDisabled(ExtensionContext context, Optional<String> optional) {
        // do something
    }

    @Override
    public void testFailed(ExtensionContext context, Throwable throwable) {
        // do something
        captureScreenshot(driver, context.getDisplayName());
    }

    @Override
    public void testSuccessful(ExtensionContext extensionContext) {
        // do something
    }

    public void captureScreenshot(WebDriver driver, String fileName) {
        try {
            new File(path).mkdirs();
            try ( FileOutputStream out = new FileOutputStream(path + File.separator + "screenshot-" + fileName + ".png")) {
                out.write(((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES));
            }
        } catch (IOException | WebDriverException e) {
            System.out.println("screenshot failed:" + e.getMessage());
        }
    }

}

And the add the following to the tests themselves:

    @RegisterExtension
    ScreenshotWatcher5 watcher = new ScreenshotWatcher5(driver, "target/surefire-reports");

On failure, this will generate a file named after the failed test, in the designated relative path.

JUnit4

For JUnit4, add the following class file into your test project:

package com.example;

import org.junit.rules.MethodRule;
import org.junit.runners.model.FrameworkMethod;
import org.junit.runners.model.Statement;
import org.openqa.selenium.*;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

/**
 *
 * @author steve
 *
 * to use, add the following to your JUnit4 tests:
 *
 * @Rule 
 * public ScreenshotRule4 rule = new ScreenshotRule4(driver, "target/surefire-reports");
 *
 */
public class ScreenshotRule4 implements MethodRule {

    WebDriver driver;
    String path;

    public ScreenshotRule4(WebDriver driver, String path) {
        this.driver = driver;
        this.path = path;
    }

    @Override
    public Statement apply(final Statement statement, final FrameworkMethod method, Object target) {
        return new Statement() {
            @Override
            public void evaluate() throws Throwable {
                try {
                    statement.evaluate();
                } catch (Throwable throwable) {
                    captureScreenshot(driver, method.getName());
                    throw throwable;
                }
            }

            public void captureScreenshot(WebDriver driver, String fileName) {
                try {
                    new File(path).mkdirs();
                    try ( FileOutputStream out = new FileOutputStream(path + File.separator + "screenshot-" + fileName + ".png")) {
                        out.write(((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES));
                    }
                } catch (IOException | WebDriverException e) {
                    System.out.println("screenshot failed:" + e.getMessage());
                }
            }

        };
    }

}

And the add the following to the tests themselves:

    @Rule
    public ScreenshotRule4 rule = new ScreenshotRule4(driver, "target/surefire-reports");

On failure, this will generate a file named after the failed test, in the designated relative path.

TestNG

For TestNG, add the following class file into your test project:

package com.example;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;

import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebDriverException;

/**
 *
 * @author steve
 *
 * to use, add the following to your TestNG tests:
 *
 * @AfterTest 
 * public void tearDown(ITestResult result) { 
 *   if (!result.isSuccess()) { 
 *     Screenshot.capture(driver, "target/surefire-reports", result.getName()); 
 *   } 
 * }
 *
 *
 */
public class Screenshot {

    static WebDriver driver;

    public static final void capture(WebDriver driver, String path, String fileName) {
        try {
            new File(path).mkdirs();
            try ( FileOutputStream out = new FileOutputStream(path + File.separator + "screenshot-" + fileName + ".png")) {
                out.write(((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES));
            }
        } catch (IOException | WebDriverException e) {
            System.out.println("screenshot failed:" + e.getMessage());
        }
    }

}

And the add the following to the tests themselves:

    @AfterMethod
    public void tearDown(ITestResult result) {
        if (!result.isSuccess()) {
            Screenshot.capture(driver, "target/surefire-reports", result.getName());
        }
    }

On failure, this will generate a file named after the failed test, in the designated relative path.