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.
#1 Best Overall
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
- Start the driver in a JUnit setup method.
- Run the test.
- Let
TestWatcher.failedexecute while the session is alive. - Quit the driver in teardown.
- 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.
Rank #2
Making the saved artifact useful in CI
- Use a deterministic directory.
target/screenshotsis 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.
Rank #3
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.
The Tool Desk
Outbyte PC Repair FREEClear out junk files and repair common Windows errorsFree Scan →Outbyte Driver Updater FREEScan for outdated or missing drivers - takes under a minuteDriver Scan →Rank #4
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.
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:
Crashes, No Sound, or Screen Glitches?
Random freezes, missing sound and display glitches usually trace back to one bad driver. Find and replace yours safely.Free scan · under a minuteWindows Errors? Fix Them Before They Spread
Repair common Windows errors and clear accumulated junk for a smoother, more stable PC - no reinstall needed.Free scan · no reinstallimport 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.
Do these 3 things before closing this tab:
1Clear out junk files and repair common Windows errors2Fix the driver behind crashes, sound loss and screen glitches3Repair Windows errors before they cause bigger problemsWhere 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
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.

