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

The reliable pattern is three separate operations: detect the failed test, capture the browser image while its session is still alive, and pass the image bytes to a report integration that understands attachments. In JUnit 5, a TestWatcher can handle ordinary test-method failures; Allure can display the resulting PNG when you declare its media type as image/png. Selenium requires explicit capture and attachment code, while Selenide can capture failure screenshots automatically when its Allure listener is enabled.

The three-part failure workflow

A JUnit result does not automatically contain a browser screenshot. Treat the workflow as three contracts:

  1. Failure detection: a Jupiter extension receives the failed-test event.
  2. Capture: the extension asks the live WebDriver (or Selenide) session for PNG bytes.
  3. Reporting: Allure or another attachment-aware integration stores those bytes beside the test result.

Keeping these concerns separate makes failures easier to diagnose. A callback without a live browser cannot capture anything, and captured bytes are useless to readers if the report format or viewer does not support image attachments.

Option 1: JUnit 5 TestWatcher with Selenium and Allure

TestWatcher receives testFailed for failed test methods and test templates. It is a good default when your test owns a WebDriver instance and you want one reusable extension.

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

Minimal watcher extension

import io.qameta.allure.Allure;
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 java.util.Optional;

public final class FailureScreenshotWatcher implements TestWatcher {
    private final WebDriver driver;

    public FailureScreenshotWatcher(WebDriver driver) {
        this.driver = driver;
    }

    @Override
    public void testFailed(ExtensionContext context, Throwable cause) {
        if (driver == null) {
            return;
        }
        try {
            byte[] png = ((TakesScreenshot) driver).getScreenshotAs(OutputType.BYTES);
            String name = "Failure screenshot - " + context.getDisplayName();
            Allure.addAttachment(name, "image/png", new java.io.ByteArrayInputStream(png), ".png");
        } catch (RuntimeException captureError) {
            // Do not replace the original test failure with a screenshot error.
            System.err.println("Could not capture failure screenshot: " + captureError.getMessage());
        }
    }
}

The extension deliberately catches capture errors. JUnit describes a TestWatcher as an observer that is not permitted to adversely influence test execution; a broken screenshot must not hide the assertion that failed. The attachment call supplies a descriptive name, PNG bytes, and the image/png media type. Allure can then offer both a download link and a preview for supported image types.

Register it with the test

Because the driver is normally created in setup, register the watcher after the driver exists. A simple instance field works for ordinary test methods:

import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

class CheckoutTest {
    private WebDriver driver;

    @RegisterExtension
    FailureScreenshotWatcher screenshots;

    @BeforeEach
    void openBrowser() {
        driver = new ChromeDriver();
        screenshots = new FailureScreenshotWatcher(driver);
    }

    @AfterEach
    void closeBrowser() {
        if (driver != null) {
            driver.quit();
        }
    }

    @Test
    void cardDeclineMessageIsShown() {
        driver.get("https://example.test/checkout");
        // assertions here; a failure invokes testFailed
    }
}

In practice, prefer a class-level extension that can obtain the driver from a test-owned holder or dependency-injection mechanism. The important timing rule is that quit() must run after the failure callback has captured the page.

Lifecycle limitations you must plan for

  • Setup failures: an exception in @BeforeAll is a class-level failure and does not produce a normal TestWatcher result callback. There may be no test-scoped browser to capture.
  • Disabled tests or classes: disabled items do not execute and therefore do not produce a failure screenshot.
  • Template coverage: with the default PER_METHOD lifecycle, a non-static instance registration does not receive template events. Use a static registration or an appropriate lifecycle when templates matter.
  • Browser availability: a failed login, crash, or early navigation may leave no live window. Treat that as a missing artifact, not a second test failure.

If you need to intercept the thrown exception itself—including failures where an exception handler is a better fit—implement TestExecutionExceptionHandler. The handler still needs access to a live WebDriver and must rethrow the original exception after attempting the capture.

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

Option 2: attach a PNG with Allure APIs

Allure’s JUnit 5 integration supports annotation and runtime attachment APIs. A small helper keeps media-type handling in one place:

import io.qameta.allure.Allure;
import java.io.ByteArrayInputStream;

public final class AllureArtifacts {
    private AllureArtifacts() {}

    public static void png(String name, byte[] bytes) {
        Allure.addAttachment(
            name,
            "image/png",
            new ByteArrayInputStream(bytes),
            ".png"
        );
    }
}

You can also use an @Attachment-annotated method that returns byte[], or call Allure.attachment with a stream. PNG data should always be labeled image/png; otherwise a report may offer a download without rendering an image preview. Confirm that the Allure JUnit 5 adapter version matches the rest of your build because integration APIs and dependency coordinates change over time.

Selenium: capture before the session is closed

Selenium’s TakesScreenshot interface returns the current browser view. Attach it from the watcher or exception handler before teardown:

byte[] screenshot = ((TakesScreenshot) driver)
        .getScreenshotAs(org.openqa.selenium.OutputType.BYTES);
Allure.addAttachment("Failure screenshot", "image/png", screenshot,
        ".png");

If your teardown is guaranteed to run immediately after each test, keep the capture in the failure callback rather than @AfterEach. By the time teardown executes, the driver may already have navigated away or been quit. For full-page images, Selenium’s standard screenshot behavior is driver-dependent; if the viewport image is insufficient, use a browser-specific full-page facility and still attach the resulting PNG with the same media type.

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

Selenide: automatic screenshots through Allure

Selenide’s Allure integration is the shortest route for Selenide-based tests. Selenide captures screenshots after failed tests by default, and the Allure Selenide listener can publish those artifacts in the report.

Listener registration

import com.codeborne.selenide.logevents.SelenideLogger;
import io.qameta.allure.selenide.AllureSelenide;
import org.junit.jupiter.api.BeforeAll;

class UiTest {
    @BeforeAll
    static void configureReporting() {
        SelenideLogger.addListener(
            "allure",
            new AllureSelenide().screenshots(true)
        );
    }
}

The listener attaches Selenide’s failure screenshot automatically. Selenide’s default screenshot output folder is build/reports/tests; the integration guide documents changing it with the JVM property -Dselenide.reportsFolder=test-result/reports. Use a folder that your CI preserves, and verify the setting against the Selenide version in your build.

For a deliberate capture at a particular point, obtain the image yourself and call Allure’s attachment API. Automatic failure capture and manual checkpoints can coexist, but use distinct names so readers can tell which image represents the failed assertion.

What a plain JUnit report can and cannot show

JUnit’s TestReporter can publish additional test data, and the JUnit Platform can emit Open Test Reporting XML. Output capture can include standard output and error. Those capabilities do not guarantee that a generic JUnit XML viewer will render arbitrary image bytes inline.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
Output path What it provides Screenshot preview expectation
JUnit TestReporter Additional key/value data or published files, depending on the launcher and consumer Viewer-dependent; not established as an inline image preview
Open Test Reporting XML Structured test results and configurable output capture Consumer-dependent; configure an image-aware viewer separately
Allure attachment Named binary attachment associated with the test result Allure previews supported image media types and provides downloads

If a team requirement says “the screenshot must appear beside the failed step,” choose a reporting integration that explicitly documents image attachments rather than assuming every JUnit XML consumer behaves the same way.

Registration and teardown checklist

  • Create the browser session before the test and keep it alive until the failure callback finishes.
  • Register the extension at class level, or use a static registration when parameterized or template tests must be covered.
  • Capture bytes, not a temporary filesystem path, when attaching directly to Allure.
  • Set the attachment type to image/png and use a .png file extension.
  • Catch screenshot exceptions so the original assertion remains the reported failure.
  • Configure Selenide’s reports folder and CI artifact retention if you need the raw files after a build.
  • Check the generated report locally with a deliberately failing test before enabling the pattern across the suite.

Troubleshooting common failures

No screenshot appears in Allure

Confirm that the Allure JUnit 5 adapter is active, the attachment call executes, and the result directory is included when generating the report. A screenshot saved outside the result directory will not automatically become an Allure attachment.

The attachment downloads but does not preview

Check the declared media type. PNG bytes must be attached as image/png, with an optional .png extension. A generic binary type can prevent preview even when the bytes are valid.

Rank #4
Sale

The watcher never runs

Check registration scope. A non-static instance field under the default per-method lifecycle can miss template events. Also remember that disabled tests and class-level setup failures do not produce normal watcher callbacks.

Free tools Windows power users keep installed

One-click scans. No signup required.

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

“Session ID is null” or “driver has already been quit”

Teardown closed the browser before capture. Move capture into testFailed or the exception handler, and ensure the quit operation runs afterward.

The screenshot itself throws an exception

The browser may have crashed, lost its window, or failed before a document was available. Catch the capture exception, log it, and preserve the original test error. For diagnostics, attach browser logs or the exception text as a separate report item.

Selenide files exist but Allure is empty

Register AllureSelenide with screenshots enabled and ensure the listener is installed before tests run. Verify the Selenide reports folder and that your report-generation step reads the same result directory.

Independent reader supportYour contribution helps us test, update, and keep practical guides available for everyone.Support on Ko-Fi

Performance, reliability and retention

A screenshot is usually cheaper than rerunning a browser test, but it still consumes memory and storage. Keep the image at the point of failure, avoid taking repeated full-page captures in every step, and use a predictable attachment name containing the test display name. In parallel execution, never share a mutable driver between tests; a watcher attached to the wrong session can produce a valid image for the wrong failure.

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

Retention is a CI policy, not a JUnit guarantee. Configure your CI system to preserve Allure results and generated reports for the period your team needs. If the report is regenerated from cleaned result files, attachments disappear even though the test history remains.

Or skip the browser setup

For teams that only need a clean image of a URL (rather than a screenshot of a live, authenticated test session), ScreenshotNeo provides a one-request screenshot API and an MCP server for AI clients. 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, blank pages, timeouts, failed loads and cache hits are not billed, and the response identifies the result with X-Page-Verdict and X-Billed headers.

See the ScreenshotNeo API documentation for all parameters. A direct call looks like this:

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

The same request in Python:

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)

And Node.js:

const q = new URLSearchParams({ access_key: 'YOUR_API_KEY', url: 'https://stripe.com' });
const res = await fetch(`https://api.screenshotneo.com/v1/shot?${q}`);
const image = Buffer.from(await res.arrayBuffer());

ScreenshotNeo is not a replacement when the image must show the exact in-test browser state, cookies or WebDriver session. It is useful when your pipeline can capture a public URL independently, or when an AI agent should call take_screenshot, get_page_info or capture_pdf through MCP. Every feature is available on every plan: 1,000 screenshots per month are free with no card, and paid plans start at $5 for 3,000. Sign up for the free plan.

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

Frequently Asked Questions

Can I attach screenshots to JUnit XML without Allure?

JUnit can publish additional data and Open Test Reporting output, but inline image rendering depends on the report consumer. Use a viewer that explicitly supports image attachments if previews are required.

Will TestWatcher capture an @BeforeAll failure?

No. Class-level setup failures do not produce a normal TestWatcher result callback; use an exception-handler or broader lifecycle strategy if that setup failure must produce an artifact.

Why is my Selenide screenshot folder empty in CI?

Check the configured selenide reports folder, listener registration, and whether the CI job preserves that directory after the test process exits.

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
$14.26
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.

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