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

Some links on this page are affiliate links: if you buy through them we may earn a commission, at no extra cost to you.

Capture the screenshot in your test framework’s failure path, before WebDriver teardown calls quit(). In Python, call driver.save_screenshot(path) while the driver session is still alive. In Java, use TakesScreenshot.getScreenshotAs(...). Treat screenshot capture as secondary reporting: if it fails, record that fact without replacing the exception that caused the test to fail.

Why timing matters after a failed command

A Selenium command failure does not guarantee that the browser session is still usable. The command may have failed because an element was missing, a wait expired, a navigation timed out, or the remote browser endpoint disappeared. Selenium’s screenshot APIs depend on an active WebDriver session, so the reliable place to capture evidence is the framework’s failure-reporting hook, before teardown closes the session.

Do not put the screenshot call only after driver.quit(), and do not assume every failed command leaves a capturable page. If the browser process or remote session has already ended, the screenshot can fail as well.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
  • Preserve the original assertion or WebDriver exception as the test failure.
  • Wrap the screenshot operation in its own error handling.
  • Use an absolute or deliberately constructed artifact path.
  • Make filenames unique when tests run concurrently or retry.

Python: save a PNG directly from WebDriver

Selenium’s Python WebDriver exposes save_screenshot(filename) and get_screenshot_as_file(filename). Both save the current window as a PNG and return False when the file operation fails. The API also provides PNG bytes and a base64 representation when your test reporter accepts attachments instead of files.

A minimal failure-safe helper

from pathlib import Path


def save_failure_screenshot(driver, path="artifacts/failure.png"):
    output = Path(path)
    output.parent.mkdir(parents=True, exist_ok=True)

    try:
        saved = driver.save_screenshot(str(output))
    except Exception as screenshot_error:
        # Keep the original test exception; report this separately.
        print(f"Screenshot capture raised an error: {screenshot_error}")
        return None

    if not saved:
        print(f"Could not save screenshot to {output}")
        return None

    return output

Call the helper from the failure branch while driver still exists:

def test_checkout(driver):
    try:
        driver.get("https://example.test/checkout")
        driver.find_element("id", "pay-now").click()
        assert "Receipt" in driver.title
    except Exception:
        save_failure_screenshot(driver, "artifacts/checkout-failure.png")
        raise

The raise is important. It re-raises the original exception after the diagnostic attempt, so a failed screenshot does not make a passing test or hide the real defect.

Use bytes or base64 for report attachments

When a CI system, JUnit-style reporter, or custom dashboard accepts binary attachments, use get_screenshot_as_png() or get_screenshot_as_base64() instead of writing a file yourself. The same lifecycle rule applies: obtain the data before teardown, and handle capture errors independently.

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.
try:
    png_bytes = driver.get_screenshot_as_png()
    report.attach("failure.png", png_bytes, "image/png")
except Exception as screenshot_error:
    report.log(f"Screenshot unavailable: {screenshot_error}")
    raise

pytest-selenium: persist the plugin screenshot extra

pytest-selenium can place a screenshot in the report’s debug extras. If you are not using the plugin’s HTML report, its documented pytest_selenium_capture_debug(item, report, extra) hook lets you decode the base64 screenshot and write a PNG.

import base64
from pathlib import Path


def pytest_selenium_capture_debug(item, report, extra):
    for entry in extra:
        if entry["name"] != "Screenshot":
            continue

        content = base64.b64decode(entry["content"].encode("utf-8"))
        path = Path("artifacts") / f"{item.name}.png"
        path.parent.mkdir(parents=True, exist_ok=True)
        path.write_bytes(content)

The example uses the test name as the filename. In parallel execution, add a worker, run, retry, or timestamp component so two tests cannot overwrite each other. Also confirm the hook signature against the pytest-selenium version installed in your project; the current documentation is labeled “latest,” while the available crawl information is old.

Collision-resistant pytest filenames

import os
import re


def artifact_name(item):
    worker = os.getenv("PYTEST_XDIST_WORKER", "local")
    safe = re.sub(r"[^A-Za-z0-9_.-]+", "_", item.nodeid)
    return f"{worker}-{safe}.png"

Use that name in the hook when your suite runs with xdist or another parallel runner. Keep artifacts grouped by build and test run so a retry does not silently replace the first failure.

Java Selenium: use TakesScreenshot

The Selenium Java API exposes getScreenshotAs(OutputType<X>). The 4.28.0 reference documents file and base64 output types and states that the method can throw WebDriverException when capture fails.

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.

Save a file without masking the original failure

import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;

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

public final class FailureScreenshot {
    public static void save(WebDriver driver, Path destination) {
        try {
            Files.createDirectories(destination.getParent());
            File temporary = ((TakesScreenshot) driver)
                    .getScreenshotAs(OutputType.FILE);
            Files.copy(temporary.toPath(), destination,
                    StandardCopyOption.REPLACE_EXISTING);
        } catch (WebDriverException e) {
            System.err.println("WebDriver screenshot failed: " + e.getMessage());
        } catch (Exception e) {
            System.err.println("Screenshot file operation failed: " + e.getMessage());
        }
    }
}

Invoke this method from your JUnit, TestNG, or custom listener before the driver is quit. Match the API reference to the Selenium version used by your build rather than copying imports blindly between major versions.

Base64 output for an attachment

try {
    String image = ((TakesScreenshot) driver)
            .getScreenshotAs(OutputType.BASE64);
    report.attachBase64("failure", image, "image/png");
} catch (WebDriverException captureError) {
    report.log("Screenshot unavailable: " + captureError.getMessage());
}

Framework-specific alternatives

Selenide

Selenide documents automatic screenshots for certain failed checks and integrations with JUnit 4, TestNG, and JUnit 5. If you use Selenide, configure its report folder and framework integration rather than adding a second capture hook that may run after the browser has been closed. Verify which failures your Selenide version captures automatically.

Custom runners and remote grids

For a custom runner, place capture in the same callback that records the failure, immediately before teardown. With a remote driver, a network interruption can make both the failed command and the screenshot request unavailable. Logging the secondary capture error is still useful because it distinguishes “the page was not captured” from “the page was captured and saved.”

What the screenshot actually contains

save_screenshot and TakesScreenshot capture the current browser window or viewport exposed by the driver. They do not automatically prove that the entire document, every lazy-loaded image, or a previous page state was visible. If the failure occurred during navigation, the image may show a partially loaded page, an error document, or the last stable browser state. That is diagnostic evidence, not a replay of every command that ran.

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

Capture any additional state your diagnosis needs—URL, title, current command, browser logs, and the exception traceback—in the same failure record. Keep those records together so the PNG is interpretable.

Common failure modes and fixes

Symptom Likely cause Fix
save_screenshot returns False Destination directory is missing or the process cannot write there. Create the parent directory, use a full path, and check CI workspace permissions.
Java throws WebDriverException The browser session, driver, or remote endpoint is no longer available. Record the capture error, preserve the original failure, and inspect session or grid logs.
No file appears after a failed test Capture runs after teardown, or the failure bypasses the hook. Move capture into the framework’s failure callback and verify that callback executes for the failing test type.
Parallel tests overwrite images Filenames contain only the test name. Add worker, run, retry, and/or a unique identifier to the path.
The PNG shows a blank or half-loaded page The failure happened during navigation or before rendering completed. Save URL, title, logs, and the exception alongside the image; do not interpret the screenshot as a complete page trace.
The screenshot error replaces the assertion error The reporting code raised its own exception. Catch screenshot and file errors separately, log them, then re-raise the original test exception.

Reliability, performance, and security considerations

Keep capture out of the success path

A screenshot is most valuable on failure. Capturing every successful step adds file I/O and storage without improving failure diagnosis. If you need checkpoints, make them explicit and retain only the states your debugging policy requires.

Use deterministic artifact retention

CI jobs should publish the artifact directory even when the test process exits nonzero. Apply retention limits and ensure that retries use separate paths. A screenshot can contain account details, tokens rendered in a page, customer data, or internal URLs; protect it with the same access controls and retention rules as logs and video.

Do not wait until teardown

Many fixtures quit the driver in a finalizer regardless of whether the test failed. Register the failure hook before that finalizer runs. If your framework’s lifecycle is unclear, create a deliberately failing test and confirm that the PNG is produced while the browser remains open.

Special offer. See more information about Outbyte and uninstall instructions. Please review EULA and Privacy policy.
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 your requirement is simply to obtain a clean image of a URL rather than preserve the exact state of a Selenium session, ScreenshotNeo provides a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or a PDF. It accepts cookie and consent banners before capture, removes more than 60 known consent platforms plus newsletter popups and chat widgets, and reports whether a response was billed. Bot checks, CAPTCHAs, blank pages, timeouts, failed loads, and cache hits are not billed.

See the ScreenshotNeo documentation for request options. A minimal cURL request is:

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

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)

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}`);

ScreenshotNeo also offers full-page capture, element selectors, dark mode, device presets, custom viewports, retina scale, PDF settings, custom CSS and JavaScript, pre-capture clicks, hidden selectors, selector or network-idle waits, request blocking, headers, cookies, user agents, authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed image links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API, and an OpenAPI specification. Its MCP server exposes take_screenshot, get_page_info, and capture_pdf to Claude, Cursor, and other MCP clients.

The Free plan includes 1,000 screenshots per month without a card. Paid plans start at $5 for 3,000 shots; every feature is included on every plan. If you want URL screenshots without maintaining a browser setup, sign up for the free ScreenshotNeo plan.

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

Frequently Asked Questions

Can a failed Selenium command be retried before taking the screenshot?

Only if your test design explicitly retries the operation. Capture the first failure before retrying so the original browser state is not lost, and store each attempt under a different artifact name.

Should screenshot artifacts be treated like production data?

Yes. The image may expose authenticated pages or personal information, so restrict access and apply the same retention and deletion policy used for test logs.

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.