Selenium Screenshot Guide: Python, Java, and C# (Plus a Faster API Alternative)

Profile

Written By Hanzala Saleem

Updated At July 14, 2026 | 9 min read

You're mid-way through a test run, a page fails an assertion, and you need visual proof of what the browser actually rendered. Or you're building a monitoring script and someone asks, "can we just grab a screenshot of this page every hour?" Either way, you land on the same tool: Selenium.

Selenium can capture screenshots in every language it supports, but the built-in methods only grab what's inside the current viewport unless you do extra work. This guide walks through working Selenium screenshot code in Python, Java, and C#, covers the full-page workarounds and the problems that trip people up (lazy loading, sticky headers, timing), and compares Selenium against a screenshot API for teams that need this at scale, including for AI agents and LLM pipelines that need a reliable screenshot API for AI agents rather than a browser cluster to babysit.

What Is Selenium Screenshot Capture?

image

Selenium screenshot capture is the process of using Selenium WebDriver's built-in commands to save an image of whatever a browser has rendered at a given moment. Under the hood, WebDriver asks the browser (Chrome, Firefox, Edge) to encode the current viewport as a PNG and return it as base64 data, which your test script then writes to disk. It's part of the W3C WebDriver specification, so the core method works the same way across browsers, even if the full-page behavior doesn't.

Why Developers Capture Screenshots

Screenshots aren't just for pretty test reports. Teams rely on them for:

  • Failure evidence: attaching a screenshot to a failed assertion saves someone from re-running the test to see what happened
  • Visual regression checks: comparing today's render against yesterday's baseline
  • Documentation: keeping product docs and onboarding guides current without manual screen grabs
  • Compliance and audit trails: proving what a page looked like on a specific date
  • Competitor and SERP tracking: a recurring capture of a pricing page or search result

Screenshot Types

TypeWhat it capturesCommon use case
Viewport screenshotOnly what's visible in the current browser windowQuick debugging, fast test runs
Full-page screenshotThe entire scrollable page, top to bottomDocumentation, visual audits, compliance
Element screenshotA single DOM element (a button, a modal, a card)Component-level visual testing

Selenium Screenshot in Python

Python is the most common language for Selenium test suites, and WebDriver exposes screenshot methods directly on the driver object and on any WebElement.

from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By

options = Options()
options.add_argument("--headless=new")
options.add_argument("--window-size=1920,1080")

driver = webdriver.Chrome(options=options)

try:
    driver.get("https://example.com")

    # Viewport screenshot
    driver.save_screenshot("viewport.png")

    # Element screenshot
    element = driver.find_element(By.TAG_NAME, "h1")
    element.screenshot("heading.png")

finally:
    driver.quit()
What this does: save_screenshot() is a convenience wrapper around get_screenshot_as_file() and returns True on success, False on an I/O error. It only captures the current viewport, so anything below the fold is cut off. The element screenshot call works the same way but crops to the target element's bounding box, which is handy for testing a single card, header, or button in isolation. Headless mode is set through --headless=new, the flag Chromium now recommends over the older --headless argument.

Selenium Screenshot in Java

Java's Selenium bindings use the TakesScreenshot interface, which every driver implements.

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.chrome.ChromeOptions;

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

public class ScreenshotExample {
    public static void main(String[] args) throws Exception {
        ChromeOptions options = new ChromeOptions();
        options.addArguments("--headless=new");
        options.addArguments("--window-size=1920,1080");

        WebDriver driver = new ChromeDriver(options);

        try {
            driver.get("https://example.com");

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

            Files.copy(screenshot.toPath(),
                    new File("viewport.png").toPath());
        } finally {
            driver.quit();
        }
    }
}
What this does: WebDriver itself doesn't declare a screenshot method, so you cast the driver to TakesScreenshot first. getScreenshotAs(OutputType.FILE) returns a temporary file that Selenium deletes once the JVM exits, which is why the code copies it to a permanent location right away with Files.copy. Swap OutputType.FILE for OutputType.BYTES if you want the raw PNG bytes instead, for example to upload straight to S3 without touching disk.

Selenium Screenshot in C#

The .NET bindings mirror the Java API almost exactly, through the ITakesScreenshot interface.

using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;

var options = new ChromeOptions();
options.AddArgument("--headless=new");
options.AddArgument("--window-size=1920,1080");

IWebDriver driver = new ChromeDriver(options);

try
{
    driver.Navigate().GoToUrl("https://example.com");

    ITakesScreenshot screenshotDriver = (ITakesScreenshot)driver;
    Screenshot screenshot = screenshotDriver.GetScreenshot();
    screenshot.SaveAsFile("viewport.png");
}
finally
{
    driver.Quit();
}
What this does: GetScreenshot() returns a Screenshot object holding the image as a base64 string and a byte array, and SaveAsFile() writes it straight to disk in the format you specify. Because IWebDriver doesn't expose this method directly, you cast to ITakesScreenshot, exactly like the Java example. Microsoft's .NET documentation covers the base language, while the Selenium C# API reference covers the driver-specific pieces.
LanguageScreenshot callReturns
Pythondriver.save_screenshot(path)True / False
Java((TakesScreenshot) driver).getScreenshotAs(OutputType.FILE)File (or byte[])
C#((ITakesScreenshot) driver).GetScreenshot()Screenshot object

Full-Page Screenshots With Selenium

None of the three examples above capture more than the visible viewport, and that's on purpose. Selenium's native screenshot command was never designed for full-page capture.

The limitation: the WebDriver spec only defines a viewport screenshot. Firefox's GeckoDriver added get_full_page_screenshot_as_file() as a non-standard extension, so Firefox can do it in one call. Chrome and Edge, which run on Chromium, don't expose an equivalent method through plain Selenium.

Browser differences: because Firefox and Chromium diverge here, teams that need consistent full-page output across both browsers usually end up writing two different code paths, or falling back to the workaround below for both.

Common workarounds for Chromium browsers:

  1. Use execute_cdp_cmd("Page.getLayoutMetrics", {}) to read the full content height, then resize the window to that height before capturing.
  2. Set --headless=new and a very tall --window-size so the whole page renders inside a single viewport (unreliable for infinite-scroll pages).
  3. Stitch multiple scrolled screenshots together with an image library like Pillow, a common but fragile approach when sticky headers repeat in every slice.

Each workaround adds code, adds a browser-version dependency, and still breaks on lazy-loaded images that only appear after the page has been scrolled past.

Common Selenium Screenshot Issues

IssueWhy it happensTypical fix
Lazy loadingImages/content load only when scrolled into viewScroll incrementally before capture, add wait time
Responsive layoutsFixed window size doesn't match the target deviceSet explicit window-size per breakpoint
AuthenticationPage redirects to a login wallInject session cookies or auth headers before navigation
TimingScreenshot fires before the DOM or JS finishes renderingUse explicit waits (WebDriverWait) instead of sleep()
AnimationsCSS transitions mid-motion at capture timeAdd a short delay or disable animations via injected CSS
Sticky headersRepeats in every scrolled slice during stitchingHide the element with injected CSS before capture
High DPI displaysScreenshot looks blurry on retina screensSet device pixel ratio explicitly in browser options
Dynamic contentAds, timestamps, or carousels shift between runsBlock or hide the element, or accept minor diff tolerance
CAPTCHABot-detection blocks the page entirelyAvoid triggering detection; some sites cannot be automated this way

Selenium vs Screenshot API

image
FactorSelenium (self-hosted)Screenshot API
SetupInstall drivers, manage browser binariesOne HTTP request, no install
InfrastructureYou provision and patch serversFully managed
Rendering accuracyDepends on installed browser versionConsistent Chromium version every time
JavaScript supportFull, but you configure waits yourselfFull, with built-in wait/lazy-load handling
SpeedDepends on your hardware and browser startup timeOptimized, request-response
ScalabilityManual scaling, queues, orchestrationHandles concurrency for you
MaintenanceOngoing (Chrome updates, driver mismatches)None on your side
AuthenticationCookie/header injection via codeCookie/header parameters in the request
Output formatsPNG (native), others need extra librariesPNG, JPG, WebP, PDF
Full-page supportRequires workarounds (see above)Built-in parameter
ConcurrencyLimited by your own machine or gridHandled by the provider
CostServer and DevOps timeUsage-based pricing

Neither approach is wrong. If you're already running a Selenium test suite for functional testing, adding a screenshot call costs nothing extra. If screenshots are the actual product, or you need them at volume, the calculation changes.

When a Screenshot API Is the Better Choice

A few situations tend to push teams away from raw Selenium and toward an API:

  • Website monitoring: checking dozens or hundreds of pages on a schedule without maintaining a fleet of headless browsers
  • Visual regression at scale: running captures across multiple viewports on every deploy, in CI, without version-pinning Chrome
  • Competitor tracking: capturing pricing and landing pages from other companies on a recurring basis
  • AI agents and LLM pipelines: an agent that needs to "see" a webpage benefits from a screenshot API for AI agents that returns a clean, ad-free, rendered image (or extracted text) on demand, without spinning up its own browser process for every call. ScreenshotAPI's guide on feeding web pages to AI agents covers this pattern in more detail
  • Web scraping and automation workflows: combining a rendered screenshot with structured website scraping output in one call
  • Compliance archiving: timestamped, tamper-evident captures stored directly to your own cloud bucket
  • PDF generation: converting a live URL into a PDF without wiring up print stylesheets yourself
  • High-volume screenshot generation: bulk jobs where spinning up a browser per URL doesn't scale economically

ScreenshotAPI.net runs every capture inside a real, isolated Chromium instance, so JavaScript-heavy React, Vue, and Next.js pages render the same way they would in an actual browser. It handles full-page scrolling, ad and cookie-banner blocking, and lazy-loaded content automatically, which removes several of the workarounds listed earlier in this guide. It also supports scheduled captures, bulk screenshot jobs, and direct storage to your own S3, Google Cloud, or Wasabi bucket.

Code Example: ScreenshotAPI.net Request

// One HTTP request instead of managing a headless browser
const token = "YOUR_API_KEY";
const targetUrl = encodeURIComponent("https://example.com");

const query =
  `https://shot.screenshotapi.net/v3/screenshot` +
  `?token=${token}&url=${targetUrl}` +
  `&full_page=true` +
  `&block_ads=true` +
  `&no_cookie_banners=true` +
  `&lazy_load=true` +
  `&file_type=png` +
  `&output=image`;

fetch(query)
  .then((response) => response.arrayBuffer())
  .then((buffer) => {
    // save or upload the returned image buffer
  });
What this does: token and url are required parameters; everything else is optional configuration. full_page=true scrolls the entire page before capturing, so nothing below the fold is missed, which is the exact gap in native Selenium screenshots described earlier. block_ads and no_cookie_banners strip out ads and consent popups automatically. lazy_load=true tells the renderer to scroll and wait for lazy-loaded images before capturing. Setting output=json instead of output=image returns a URL to the hosted screenshot rather than the raw image bytes, which is useful when you want to store a reference instead of downloading the file immediately. The full parameter list is in the render screenshot documentation.

Best Practices

PracticeWhy it matters
Use explicit waits, not sleep()Fixed delays waste time or fire too early on slow connections
Set a consistent window sizeKeeps screenshots comparable across test runs
Disable animations before captureAvoids mid-transition, inconsistent frames
Hide dynamic elements (ads, timestamps)Reduces false positives in visual diffs
Run headless in CIFaster, and avoids requiring a display server
Version-pin your browser driverPrevents silent breakage after a Chrome auto-update
Store screenshots with a timestamp in the filenameMakes audit trails and rollbacks easier to trace

Troubleshooting

SymptomLikely causeFix
Blank or white screenshotPage hadn't finished renderingAdd an explicit wait for a key element before capturing
Screenshot cuts off contentOnly the viewport was capturedUse a full-page workaround or a full_page=true API call
Element not found for element screenshotSelector changed or element loads lateRe-check the selector, wait for element visibility
Blurry image on retina displaysDevice pixel ratio not setSet pixel ratio in browser options, or use a retina parameter
Screenshot shows a cookie bannerNo blocking configuredAdd cookie-banner blocking logic, or use no_cookie_banners=true
Inconsistent results across machinesDifferent browser/driver versionsPin driver versions, or move to a managed rendering service

Final Thoughts

Selenium's screenshot methods work well for what they were built for: quick, in-test visual evidence tied to a specific viewport. The moment you need full-page captures, cross-browser consistency, scheduled monitoring, or screenshots at volume, you're writing and maintaining infrastructure that a managed API already handles. If that sounds like where your project is heading, try ScreenshotAPI.net for free and see how a single request compares to your current setup.

Frequently Asked Questions

Does Selenium support full-page screenshots natively?

Firefox's driver supports it through get_full_page_screenshot_as_file(). Chrome and Edge don't have a built-in equivalent, so full-page capture on Chromium browsers needs a CDP-based workaround or a stitching approach.

Can Selenium take a screenshot of a specific element?

Yes. Both Python and Java expose element-level screenshot methods (element.screenshot() in Python, or capturing a WebElement's bounding box in Java), which crop the image to just that element.

Why does my Selenium screenshot come out blank?

Usually because the capture fires before the page has finished loading or before JavaScript has hydrated the content. Replace fixed sleep() calls with explicit waits tied to a specific element or condition.

Is a screenshot API a replacement for Selenium?

Not for functional testing. Selenium still owns interaction testing (clicks, form fills, assertions). A screenshot API is a better fit specifically for the capture step, particularly at scale, in monitoring pipelines, or for AI agents that just need a rendered view of a page.