ScreenshotNeo

BlogHow-to

How to Save Selenium Screenshots to a Destination File

Save Selenium screenshots to reliable PNG paths in Python, Java, JavaScript, Ruby and C#, with error handling, troubleshooting and a no-browser API option.

By the ScreenshotNeo team1 October 20267 min read

How to Save Selenium Screenshots to a Destination File

To save a Selenium screenshot to a destination file, call the binding’s screenshot method with a writable path. In Python, the direct solution is:

driver.save_screenshot('/absolute/path/to/screenshot.png')

Use an existing, writable directory, prefer an absolute path, and check the Boolean return value. Selenium’s Python API documents this method as saving the current window to a PNG file and returning False when an I/O error prevents the save. See the official Python WebDriver API.

Python: save a screenshot to a destination file

This complete example creates the destination directory, opens a page, saves the current browser window, checks the result, and always closes the driver:

A screenshot call captures the current browser window and writes the PNG to a chosen destination.
A screenshot call captures the current browser window and writes the PNG to a chosen destination.
from pathlib import Path
from selenium import webdriver

output = Path('/absolute/path/to/screenshots/page.png')
output.parent.mkdir(parents=True, exist_ok=True)

driver = webdriver.Chrome()
try:
    driver.get('https://example.com')
    saved = driver.save_screenshot(str(output))
    if not saved:
        raise OSError(f'Selenium could not write {output}')
    print(f'Screenshot saved to {output}')
finally:
    driver.quit()

Relative paths versus absolute paths

A relative path is resolved from the process’s current working directory, which may differ between a terminal, IDE, test runner and CI job. Use an absolute path when the destination matters:

from pathlib import Path

output = Path.cwd() / 'artifacts' / 'login.png'
output.parent.mkdir(parents=True, exist_ok=True)
driver.save_screenshot(str(output))

The filename should end in .png for Python’s file-saving methods. Selenium does not create missing parent directories, so create them yourself.

Save bytes or Base64 instead of writing directly

When an application uploads the image, stores it in object storage, or sends it elsewhere, use the byte or Base64 APIs:

png_bytes = driver.get_screenshot_as_png()
with open('/absolute/path/to/screenshots/page.png', 'wb') as file:
    file.write(png_bytes)

base64_png = driver.get_screenshot_as_base64()

get_screenshot_as_png() returns PNG bytes. get_screenshot_as_base64() returns a Base64 string. These alternatives avoid having Selenium perform the final file write, but your code must handle filesystem or storage errors.

Java: copy the temporary screenshot to your destination

In Java, OutputType.FILE returns a temporary file. Copy it to a durable destination before the JVM exits. Selenium’s examples use Apache Commons IO:

import java.io.File;
import org.apache.commons.io.FileUtils;
import org.openqa.selenium.OutputType;
import org.openqa.selenium.TakesScreenshot;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.chrome.ChromeDriver;

public class SaveScreenshot {
    public static void main(String[] args) throws Exception {
        WebDriver driver = new ChromeDriver();
        try {
            driver.get("https://example.com");
            File temporary = ((TakesScreenshot) driver)
                    .getScreenshotAs(OutputType.FILE);
            File destination = new File(
                    "/absolute/path/to/screenshots/page.png");
            FileUtils.copyFile(temporary, destination);
        } finally {
            driver.quit();
        }
    }
}

Create the parent directory before copying if it does not exist. The Java API also supports OutputType.BYTES and OutputType.BASE64. See the TakesScreenshot API and OutputType uses.

JavaScript and Node.js

Selenium’s JavaScript binding returns a Base64-encoded screenshot. Decode it and write the destination file with Node’s filesystem API:

const fs = require('node:fs/promises');
const { Builder } = require('selenium-webdriver');

(async () => {
  const output = '/absolute/path/to/screenshots/page.png';
  await fs.mkdir(require('node:path').dirname(output), { recursive: true });

  const driver = await new Builder().forBrowser('chrome').build();
  try {
    await driver.get('https://example.com');
    const base64 = await driver.takeScreenshot();
    await fs.writeFile(output, Buffer.from(base64, 'base64'));
    console.log(`Screenshot saved to ${output}`);
  } finally {
    await driver.quit();
  }
})();

Ruby and C#

The official Selenium browser-interactions examples use binding-specific destination methods:

driver.save_screenshot('./screenshots/page.png')
((ITakesScreenshot)driver).GetScreenshot()
    .SaveAsFile("screenshots/page.png", ScreenshotImageFormat.Png);

For both bindings, make sure the directory exists and the process has write permission. The exact interface and image-format enum depend on the Selenium package version.

What Selenium captures

The ordinary screenshot call captures the current browsing context or window. It does not automatically promise a full-page image. Full-page behavior varies by browser, driver, Selenium version and binding. Element screenshots are a separate capability where supported:

element = driver.find_element('css selector', 'main')
element.screenshot('/absolute/path/to/screenshots/main.png')

If you need the entire document, verify the full-page support and semantics of your chosen browser and binding instead of assuming that a viewport screenshot includes content below the fold. Lazy-loaded content may also require scrolling or an explicit wait before capture.

Reliable destination-file checklist

  • Choose a path with the intended extension, normally .png.
  • Create the parent directory before calling the screenshot method.
  • Use an absolute path in CI, containers and test runners.
  • Confirm the process user can write to the directory.
  • Wait for navigation and important page content before capture.
  • Check Python’s Boolean return value, or catch the binding’s file and driver exceptions.
  • Close the driver in a finally block so browser processes do not accumulate.
  • Use unique names when parallel tests could write the same file.

Common errors and fixes

Error or symptom Cause Fix
Python returns False Filesystem I/O failed. Check the parent directory, permissions, disk space and path. Raise an error instead of treating the save as successful.
FileNotFoundError or missing output The destination directory does not exist, or a relative path resolved somewhere unexpected. Create the directory and print or log the absolute path.
PermissionError The account running the browser cannot write there. Choose a writable workspace or adjust directory ownership and permissions.
Java file disappears OutputType.FILE is temporary. Copy it to the final path immediately with FileUtils.copyFile or equivalent.
Image is blank or incomplete Capture happened before navigation, rendering or lazy loading finished. Wait for a specific element, document state, or an application-ready condition before saving.
Only the visible viewport appears Current-window capture is not the same as full-page capture. Use a supported full-page technique for your browser and binding, or capture and stitch deliberately.
Screenshot has the wrong tab The driver is attached to another window handle. Switch to the intended window before calling the screenshot method.
Parallel tests overwrite files Workers use the same destination name. Include a test ID, timestamp or worker ID in each filename.

Timing, performance and reliability

Wait for the state you need

A screenshot records the state at the instant Selenium captures it. A page-load return does not guarantee that fonts, animations, API data or lazy images are ready. Prefer an explicit wait for the element or state that matters. Disable or finish animations when visual consistency is important.

ScreenshotNeo removes common consent banners, popups and chat widgets before capture.
ScreenshotNeo removes common consent banners, popups and chat widgets before capture.

Keep files manageable

PNG is lossless and is the expected format for Python’s direct save method. Large viewport dimensions and high device scale factors produce larger files and use more memory. Capture only the viewport or element you need, and compress or convert after the capture when your workflow permits.

Make writes atomic when consumers watch the directory

If another process immediately picks up new files, write to a temporary name and rename after the write completes. This prevents consumers from opening a partially written file.

from pathlib import Path
import os

final = Path('/absolute/path/to/screenshots/page.png')
temporary = final.with_suffix('.tmp.png')
driver.save_screenshot(str(temporary))
os.replace(temporary, final)

CI and containers

Use a known workspace path, create it at job startup, and publish that directory as an artifact. Headless browser configuration, sandbox settings and driver versions must match the environment; a successful local path does not prove that the CI user can write the same location.

Or skip the browser setup

ScreenshotNeo provides a GET-based screenshot API when you need a destination image without managing WebDriver, browser binaries and drivers. The API accepts a URL and returns PNG, JPEG, WebP or PDF. See the ScreenshotNeo API documentation.

curl -G "https://api.screenshotneo.com/v1/shot" -d access_key=YOUR_API_KEY --data-urlencode url=https://stripe.com -o shot.webp
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}`);

Before capture, ScreenshotNeo accepts cookie and consent banners and removes more than 60 known consent platforms, newsletter popups and chat widgets; each step can be turned off. Bot checks, blank pages, timeouts, failed loads and cache hits are not billed, and response headers identify the page verdict and billing status. Its MCP server provides take_screenshot, get_page_info and capture_pdf for Claude, Cursor and other MCP clients. The free plan includes 1,000 screenshots per month with no card; paid plans start at $5 for 3,000 shots. Create a free ScreenshotNeo account.

Cost and operational considerations

  • Selenium itself does not charge per screenshot, but you operate browser processes, drivers, CPU, memory, storage and CI minutes.
  • Persist only the files your workflow needs and clean up temporary artifacts.
  • For an API workflow, account for request volume, response storage, retries and cache behavior. ScreenshotNeo lets you choose a cache TTL; cache hits are not billed.
  • Use retries for transient navigation failures, but avoid blindly repeating a bad URL or authentication failure.

FAQ

Does save_screenshot save JPEG or WebP?

The Python file-saving method is documented for PNG. Convert the resulting bytes afterward if another format is required.

Why should I check the return value?

Python returns False when an I/O error prevents writing the file, so checking it distinguishes a completed capture from a failed save.

Can I save an element instead of the window?

Use the element screenshot method supported by your binding, such as Python’s element.screenshot(path). Its dimensions and browser support differ from a window screenshot.

Is a Java screenshot file permanent?

Not when it comes from OutputType.FILE. Copy the temporary file to your destination before the JVM exits.

When is an API preferable to Selenium?

An API is useful when you want a URL-to-image request without provisioning a browser and when built-in handling for consent banners, popups, failed loads, caching or agent workflows saves application code.

Primary references