Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Yes. Selenium can capture a browser image after a JUnit test fails. Cast the live WebDriver to TakesScreenshot, call getScreenshotAs(OutputType.FILE), and copy the temporary file into your build’s artifact directory. JUnit supplies the lifecycle hook: use a TestWatcher/TestRule in JUnit 4 or an extension callback registered with @ExtendWith or @RegisterExtension in JUnit 5. The callback must run before teardown quits the driver.

How Selenium and JUnit divide the work

Selenium WebDriver controls the browser and exposes the screenshot API. JUnit runs tests and invokes rules or extensions at defined points in the test lifecycle. Neither product automatically wires the other together: your hook must detect a failure, ask the still-running driver for an image, and save that image where the build server can publish it.

The Java API is TakesScreenshot#getScreenshotAs(OutputType<X>). It can return a file, byte array, or other supported output type, and Selenium may throw WebDriverException if the browser session cannot capture the page. Treat the image as diagnostic evidence, not as a second assertion that should replace the original failure.

JUnit 4: capture with TestWatcher

Complete example

This rule captures only failed tests, creates the destination directory, and preserves the original assertion if capture itself fails.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import org.apache.commons.io.FileUtils;
import org.junit.Rule;
import org.junit.rules.TestRule;
import org.junit.rules.TestWatcher;
import org.junit.runner.Description;
import org.openqa.selenium.*;

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

public class CheckoutTest {
  private WebDriver driver;

  @Rule
  public TestRule screenshotOnFailure = new TestWatcher() {
    @Override
    protected void failed(Throwable error, Description description) {
      if (driver == null) return;

      try {
        File source = ((TakesScreenshot) driver)
            .getScreenshotAs(OutputType.FILE);
        File destination = new File(
            "target/screenshots/"
                + safe(description.getClassName()) + "_"
                + safe(description.getMethodName()) + ".png");
        FileUtils.copyFile(source, destination);
      } catch (WebDriverException | IOException captureError) {
        // Log captureError; do not mask the test's original failure.
      }
    }
  };

  private static String safe(String value) {
    return value == null ? "unknown" : value.replaceAll("[^a-zA-Z0-9._-]", "_");
  }

  // Create driver in setup and quit it in teardown.
}

The DZone Selenium refcard demonstrates the same TestWatcher.failed pattern: use the failed test’s class and method in the filename, then copy the file returned by Selenium. Apache Commons IO is used above only for convenient copying; java.nio.file.Files.copy works as well.

Lifecycle ordering

  1. Start the driver in a JUnit setup method.
  2. Run the test.
  3. Let TestWatcher.failed execute while the session is alive.
  4. Quit the driver in teardown.
  5. Configure CI to archive target/screenshots.

If teardown calls driver.quit() before the watcher runs, the screenshot request has no browser to contact and can fail with WebDriverException. Keep the driver reference available to the rule and avoid setting it to null prematurely.

JUnit 5: use an extension callback

Reusable extension

JUnit Jupiter extensions receive the execution context. AfterTestExecutionCallback runs after the test method, while the test instance and driver can still be available. The example assumes a driver holder used by your setup code.

import org.junit.jupiter.api.extension.AfterTestExecutionCallback;
import org.junit.jupiter.api.extension.ExtensionContext;
import org.openqa.selenium.*;

import java.nio.file.*;

public final class ScreenshotOnFailure
    implements AfterTestExecutionCallback {
  @Override
  public void afterTestExecution(ExtensionContext context) {
    if (context.getExecutionException().isEmpty()) return;

    WebDriver driver = DriverHolder.current();
    if (driver == null) return;

    try {
      Path destination = Paths.get(
          "target/screenshots",
          safe(context.getRequiredTestClass().getSimpleName()) + "_"
              + safe(context.getRequiredTestMethod().getName()) + ".png");
      Files.createDirectories(destination.getParent());

      File source = ((TakesScreenshot) driver)
          .getScreenshotAs(OutputType.FILE);
      Files.copy(source.toPath(), destination,
          StandardCopyOption.REPLACE_EXISTING);
    } catch (Exception captureError) {
      // Log captureError without replacing the original test failure.
    }
  }

  private static String safe(String value) {
    return value.replaceAll("[^a-zA-Z0-9._-]", "_");
  }
}

Register the extension

import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(ScreenshotOnFailure.class)
class CheckoutTest {
  // Start the driver in setup; quit it after the extension has captured failures.
}

JUnit 5 also supports programmatic registration:

import org.junit.jupiter.api.extension.RegisterExtension;

class CheckoutTest {
  @RegisterExtension
  static final ScreenshotOnFailure screenshots = new ScreenshotOnFailure();
}

Use @ExtendWith when the extension is a stable class-level convention. Use @RegisterExtension when configuration or construction must be controlled by the test class.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Making the saved artifact useful in CI

  • Use a deterministic directory. target/screenshots is conventional for Maven projects; choose the equivalent reports directory for your build.
  • Make names unique. Include the test class, method, and—when tests run in parallel—an invocation number, browser, or timestamp. Sanitize slashes, spaces, and characters rejected by the filesystem.
  • Create directories in code. A clean CI workspace usually does not contain the reports folder.
  • Publish the directory. Saving a PNG locally does not make it visible in CI. Configure your CI provider to archive the directory after tests, including it when the test step fails.
  • Keep the original exception. Log screenshot errors and continue propagating the assertion or setup failure.

The screenshot is normally the viewport image. Full-page behavior, browser support, and device-specific details depend on the driver and browser; do not assume that a viewport capture includes content below the fold.

JUnit 5 with Selenide

If the suite uses Selenide’s static WebDriver, its maintained ScreenShooterExtension can take screenshots automatically on failures:

import com.codeborne.selenide.junit5.ScreenShooterExtension;
import org.junit.jupiter.api.extension.ExtendWith;

@ExtendWith(ScreenShooterExtension.class)
class MyTest { }

Selenide documents a configurable reports folder and says the extension handles errors beyond Selenide assertion failures. Its Javadoc limits this extension to Selenide’s static driver. A driver created directly with new SelenideDriver() is outside that scope, and a plain Selenium driver needs the custom JUnit hook shown earlier.

Common failures and precise fixes

ClassCastException when casting to TakesScreenshot

The active driver implementation does not advertise screenshot support. Use a Selenium browser driver that implements TakesScreenshot, or check first with driver instanceof TakesScreenshot and log a diagnostic when it does not.

What’s actually slowing this PC down?

Pick the symptom - the matching free tool is one click away.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

WebDriverException during capture

The session may have crashed, the browser may have been closed, or teardown may have run first. Move capture to the post-test callback, keep the driver alive until it completes, and record the exception without masking the test result.

No file appears

The destination directory may not exist, the copy may have failed, or CI may not archive it. Call Files.createDirectories (or create the directory before FileUtils.copyFile), print the absolute destination, and verify the CI artifact pattern.

Every screenshot has the same filename

Parameterized or parallel tests are overwriting one another. Add a unique invocation identifier, thread name, or timestamp to the filename and retain the class and method for readability.

The screenshot is blank or shows a login/consent overlay

The image reflects the browser state at failure. Wait for the application’s readiness condition, establish authentication before the test, and dismiss expected overlays in setup. A screenshot cannot reconstruct content that never loaded.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Rank #4
Sale

Capture errors hide the assertion

Do not rethrow the capture exception from the watcher or extension. Log it, optionally attach its stack trace, and let JUnit report the original test exception.

Selenide’s extension captures nothing

Confirm that the test uses Selenide’s static WebDriver and that the extension is registered with JUnit 5. For a directly constructed Selenide driver or plain Selenium, use the custom extension and expose the correct driver through your holder.

Performance, reliability, and cost considerations

  • Capture only on failure. This avoids an extra image write for every passing test and keeps artifact storage manageable.
  • Expect a small diagnostic delay. The browser must encode the image and the test process must copy it. Do not put long sleeps in the callback; wait for a real application condition before the test fails.
  • Parallel execution needs isolation. Give each test its own driver and unique artifact path. A shared static driver can produce an image from another test’s state.
  • Remote drivers add transport risk. A Selenium Grid or cloud session can disappear at the same time as a failure. Keep capture best effort and retain browser logs or video when your environment provides them.
  • Plan retention. PNG files can be large. Compress or expire old artifacts according to your CI policy, while preserving enough history to diagnose intermittent failures.
Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Or skip the browser setup

If you need a screenshot outside a test process—or want an API that handles page preparation—ScreenshotNeo provides a single request for a PNG, JPEG, WebP, or PDF. It accepts consent banners before capture and removes more than 60 known consent platforms, newsletter popups, and chat widgets; each step can be disabled. Bot checks/CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server offers take_screenshot, get_page_info, and capture_pdf for Claude, Cursor, and other MCP clients.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp

See the ScreenshotNeo API documentation for authentication and options. The same endpoint is available from Python and Node.js:

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
import requests
r = requests.get("https://api.screenshotneo.com/v1/shot", params={"access_key": "YOUR_API_KEY", "url": "https://stripe.com"}, timeout=90)
open("shot.webp", "wb").write(r.content)
const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);

For test diagnostics, you can call the API from a failure handler when the failing URL—not the private, locally rendered browser state—is what you need. ScreenshotNeo also supports CSS-element capture, full-page lazy-image loading, custom JavaScript and CSS, waits, request blocking, headers, cookies, user agents, geolocation, time zones, dark mode, device presets, retina scale, resizing, caching with a chosen TTL, signed image links, asynchronous jobs with signed webhooks, bulk capture of up to 100 URLs per call, and a usage API. Every feature is included on every plan. The free plan includes 1,000 shots per month without a card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Best Value

Choosing the right approach

Situation Best fit Reason
Diagnose the exact state of a failing Selenium test JUnit 4 watcher or JUnit 5 extension The live session contains cookies, local storage, and the DOM state that caused the failure.
Selenide tests using its static driver ScreenShooterExtension Maintained automatic failure capture with a reports-folder setting.
Capture public pages from CI, scripts, or AI agents ScreenshotNeo Clean shots, only clean shots billed, and a $5 paid entry plan.

Checklist before committing the hook

  • The callback runs before quit().
  • The driver implements TakesScreenshot.
  • The destination directory is created.
  • Filenames are sanitized and unique under parallel or parameterized execution.
  • Capture exceptions are logged without replacing the test exception.
  • CI archives the screenshot directory on failed jobs.
  • Secrets, tokens, and sensitive page data are protected in retained images.

Frequently Asked Questions

Does Selenium take a screenshot automatically when JUnit fails?

No. Selenium provides the capture API, but you must connect it to a JUnit rule, watcher, or extension.

Which JUnit 5 callback is used in the example?

The example uses AfterTestExecutionCallback, which checks ExtensionContext.getExecutionException() before capturing.

Can I save a screenshot as bytes instead of a file?

Yes. Pass OutputType.BYTES to getScreenshotAs and attach the returned bytes through your CI or reporting framework.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.

Where should screenshots be stored?

Use a predictable build-artifact directory such as target/screenshots, then configure your CI system to archive it after the test step.

Quick Recap

SaleBestseller No. 3
SaleBestseller No. 4
Pragmatic Unit Testing in Java with JUnit
Pragmatic Unit Testing in Java with JUnit
Used Book in Good Condition
$13.88
SaleBestseller No. 5

Product prices and availability are accurate as of the date/time indicated and are subject to change. Any price and availability information displayed on Amazon at the time of purchase will apply.