ScreenshotNeo

BlogHow-to

How to Take Multiple Screenshots in Selenium WebDriver with Java

Capture every Selenium checkpoint in Java with unique files, reliable waits, output choices, troubleshooting, and a browser-free ScreenshotNeo option.

By the ScreenshotNeo team1 October 20268 min read

Call getScreenshotAs after each state you want to preserve, and save each result to a different path. Selenium does not require a special multi-screenshot API. A normal screenshot call can be repeated throughout a test.

The pattern is:

  1. Wait until the page reaches the state you want to document.
  2. Capture the driver or a specific element.
  3. Copy the returned file, bytes, or Base64 value to durable storage.
  4. Use deterministic, unique names such as 01-home.png and 02-results.png.

1. Save multiple full-page viewport screenshots

TakesScreenshot is the Selenium Java interface for drivers and HTML elements. Its getScreenshotAs(OutputType) method returns the requested representation. See the TakesScreenshot Java API and the OutputType Java API.

This complete pattern creates the directory, captures the current driver state, and copies Selenium’s temporary file to a predictable destination:

import org.openqa.selenium.By;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.ExpectedConditions;
import org.openqa.selenium.support.ui.WebDriverWait;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
import java.time.Duration;

public class MultipleScreenshots {
    private static void saveScreenshot(WebDriver driver, Path destination)
            throws IOException {
        Files.createDirectories(destination.getParent());

        File temporaryScreenshot = ((TakesScreenshot) driver)
                .getScreenshotAs(OutputType.FILE);

        Files.copy(
                temporaryScreenshot.toPath(),
                destination,
                StandardCopyOption.REPLACE_EXISTING
        );
    }

    public static void main(String[] args) throws IOException {
        WebDriver driver = new ChromeDriver();
        WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(20));
        Path folder = Path.of("screenshots", "checkout-flow");

        try {
            driver.get("https://example.com");
            wait.until(ExpectedConditions.titleContains("Example"));
            saveScreenshot(driver, folder.resolve("01-home.png"));

            // Perform an action, then wait for the resulting state.
            // driver.findElement(By.cssSelector("a.next")).click();
            // wait.until(ExpectedConditions.visibilityOfElementLocated(
            //         By.cssSelector("main.results")));
            saveScreenshot(driver, folder.resolve("02-results.png"));

            // Capture another checkpoint after a different transition.
            // driver.findElement(By.id("confirm")).click();
            // wait.until(ExpectedConditions.urlContains("confirmation"));
            saveScreenshot(driver, folder.resolve("03-confirmation.png"));
        } finally {
            driver.quit();
        }
    }
}

The example uses OutputType.FILE. Selenium places that file in temporary storage, so copy it if it must remain after the JVM exits. The official Selenium example follows the same temporary-file-to-named-file approach in its TakeScreenshot documentation.

Use a step counter when names are generated dynamically

A counter avoids accidental overwrites while keeping files easy to sort:

import java.nio.file.Path;
import java.util.concurrent.atomic.AtomicInteger;

AtomicInteger step = new AtomicInteger(1);

Path nextScreenshot(Path directory, String state) {
    int number = step.getAndIncrement();
    String safeState = state.replaceAll("[^a-zA-Z0-9._-]", "_");
    return directory.resolve(String.format("%02d-%s.png", number, safeState));
}

In a real class, make nextScreenshot a method or replace it with a local naming function. Include the test case or browser session in the parent directory when multiple tests run concurrently.

2. Capture at the right moments

A screenshot captures the browser state at the instant the call runs. It does not wait for your application to finish rendering. Put the call after the action and after a condition that proves the new state is ready.

driver.findElement(By.id("search")).sendKeys("selenium");
driver.findElement(By.cssSelector("button[type='submit']")).click();

wait.until(ExpectedConditions.visibilityOfElementLocated(
        By.cssSelector("[data-testid='results']")));
saveScreenshot(driver, Path.of("screenshots", "04-search-results.png"));

Useful wait conditions include:

  • visibilityOfElementLocated when the evidence is a visible component.
  • elementToBeClickable before an interaction that changes the next checkpoint.
  • urlContains or urlToBe after navigation.
  • titleContains after a page transition.
  • invisibilityOfElementLocated when a loading overlay must disappear.

A fixed sleep can be useful for a deliberately timed animation, but a state-based wait is usually more reliable because it follows the application condition you actually need.

3. Choose FILE, BYTES, or BASE64

OutputType provides three useful representations:

Representation Use it when Persistence detail
FILE You want to copy an image into a test-artifact directory. The returned file is temporary. Copy it before the JVM exits.
BYTES Your report, object store, or test framework accepts raw bytes. Write or upload the byte array immediately.
BASE64 A report or remote API expects encoded image content. Keep the encoded value with the report record.

Write bytes directly

byte[] image = ((TakesScreenshot) driver)
        .getScreenshotAs(OutputType.BYTES);
Files.write(Path.of("screenshots", "05-bytes.png"), image);

Keep Base64 for a report

String base64 = ((TakesScreenshot) driver)
        .getScreenshotAs(OutputType.BASE64);
// Pass base64 to the reporting system that consumes your test result.

4. Capture one element instead of the whole viewport

Use an element screenshot when the evidence is a card, dialog, chart, or other component. The element and the driver both expose the screenshot contract where the driver and browser support it.

import org.openqa.selenium.WebElement;

WebElement summary = wait.until(ExpectedConditions.visibilityOfElementLocated(
        By.cssSelector("[data-testid='order-summary']")));

File temporaryElementImage = summary.getScreenshotAs(OutputType.FILE);
Files.copy(
        temporaryElementImage.toPath(),
        Path.of("screenshots", "06-order-summary.png"),
        StandardCopyOption.REPLACE_EXISTING
);

Element capture produces a cropped image. Use driver capture when surrounding page context matters. Confirm that your browser and driver support the requested capture; Selenium documents behavior caveats for non-W3C-conformant implementations.

5. Organize screenshots for parallel and repeated tests

Predictable paths make CI artifacts searchable and prevent one test from replacing another test’s evidence.

String testName = "checkout";
String browser = "chrome";
String runId = System.getenv().getOrDefault("BUILD_ID", "local");
Path output = Path.of("screenshots", runId, browser, testName);
Files.createDirectories(output);
saveScreenshot(driver, output.resolve("01-cart.png"));
  • Use a separate directory per test case, browser, and run.
  • Keep the numeric prefix stable so a file listing follows the user journey.
  • Include a descriptive state after the prefix.
  • Do not share a mutable counter between parallel tests unless it is synchronized and scoped correctly.
  • Decide whether a repeated checkpoint should overwrite by design or receive a unique suffix.

6. Complete checkpoint helper with failure evidence

When a test fails, capture the browser before cleanup. The helper below records a failure image and then rethrows the original exception:

static void captureFailure(WebDriver driver, Path directory, String testName) {
    try {
        Files.createDirectories(directory);
        Path destination = directory.resolve(testName + "-failure.png");
        File file = ((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE);
        Files.copy(file.toPath(), destination, StandardCopyOption.REPLACE_EXISTING);
    } catch (Exception screenshotError) {
        // Preserve the test failure. Log screenshotError in your test framework.
    }
}

Call this from your framework’s failure hook while the driver is still alive. A screenshot failure should not hide the assertion or navigation error that caused the test to fail.

7. Troubleshooting

Symptom Likely cause Fix
ClassCastException when casting to TakesScreenshot The driver implementation does not expose the screenshot interface. Use a driver/browser combination that supports Selenium screenshots and check the driver type before casting.
Only the last screenshot remains Every capture used the same destination. Generate a unique filename for every checkpoint or create a separate run directory.
NoSuchFileException for the destination The parent directory does not exist. Call Files.createDirectories(destination.getParent()) before copying.
Screenshot is blank or shows a spinner The call happened before the target state was ready. Wait for a visible result, URL change, title, or overlay removal before capture.
Temporary screenshot disappears OutputType.FILE points to temporary storage. Copy it immediately, or use BYTES and write the bytes yourself.
Element screenshot has the wrong crop The element moved, was clipped, or was not the intended target. Wait for visibility, scroll it into view when needed, and verify the selector.
Intermittent capture errors in CI Browser startup, rendering, or parallel file access is racing the test. Use explicit waits, isolate output directories per run, and capture failure evidence before quitting the driver.
Screenshot support differs between browsers Driver behavior and conformance vary. Use a W3C-conformant driver and verify the specific driver/browser combination used by the suite.

8. Performance, reliability, and storage

  • Capture only useful checkpoints. Each image adds browser and file-system work, so select state transitions that help diagnose or document the test.
  • Prefer bytes for direct uploads. BYTES avoids managing a temporary source file when your reporting or storage layer accepts byte arrays.
  • Keep images out of the source tree. Put artifacts under a run-specific directory and configure CI retention separately from code.
  • Use stable waits. Screenshots taken after application conditions are met are more useful than a large number of timing-dependent captures.
  • Protect parallel runs. Include a run, browser, and test identifier in the path so independent drivers never write the same file.
  • Preserve the original failure. Treat screenshot capture as diagnostic work; log capture errors without replacing the assertion or navigation exception.

9. Or skip the browser setup

If you need screenshots of URLs rather than a browser session inside a Java test, ScreenshotNeo provides a single HTTP request. It accepts the cookie or consent banner like a visitor and removes more than 60 known consent platforms, newsletter popups, and chat widgets before capture; each step can be turned off. Bot checks or CAPTCHAs, blank pages, timeouts, failed loads, and cache hits cost nothing, and the response identifies the result with X-Page-Verdict and X-Billed headers. It also provides an MCP server for Claude, Cursor, and other MCP clients with take_screenshot, get_page_info, and capture_pdf.

See the ScreenshotNeo API documentation for the full option set. The basic call returns a WebP image:

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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot failed: ${res.status}`);
const buffer = Buffer.from(await res.arrayBuffer());
await import('node:fs/promises').then(fs => fs.writeFile('shot.webp', buffer));

ScreenshotNeo includes full-page capture with lazy images loaded, CSS-selector element capture, dark mode, device presets and custom viewports, retina scale, PDF output, custom CSS and JavaScript, clicks before capture, selector or network-idle waits, request blocking, custom headers and cookies, user agents and Authorization, timezone and geolocation, transparent backgrounds, resizing, configurable-TTL caching, signed links, asynchronous jobs with signed webhooks, bulk capture for up to 100 URLs per call, a usage API, and an OpenAPI specification. Its parameter names are compatible with those used by other screenshot APIs, which helps when switching.

The Free plan includes 1,000 screenshots each month with no card. Paid plans start at $5 for 3,000 screenshots, and every feature is available on every plan. Create a free ScreenshotNeo account.

10. FAQ

Do I need a special Selenium method for multiple screenshots?

No. Call getScreenshotAs repeatedly after each state transition and save each result under a different destination.

Should I use a driver screenshot or an element screenshot?

Use a driver screenshot for page context and an element screenshot for a focused component such as a dialog or card.

Why should I copy an OutputType.FILE result?

The file is temporary and may be deleted when the JVM exits. Copy it to your artifact directory while the test is running.

Can screenshots prove that asynchronous content finished loading?

They show only the rendered state at capture time. Wait for a condition that represents completion, then capture.

When is ScreenshotNeo a better fit?

Use it when you need URL screenshots without maintaining browser drivers, or when you want cleaned pages, API billing signals, bulk and asynchronous capture, or MCP tools for AI agents.