Can We Capture Screenshot in Headless Mode Selenium WebDriver?
Yes. Run Selenium with a headless browser, set a deliberate viewport, then capture the page, an element, or the full document.
Yes. Selenium WebDriver can capture screenshots while Chrome, Firefox, or another supported browser runs without a visible display. Enable the browser’s headless mode, navigate to the URL, set the viewport, and call the normal screenshot method. Headless mode does not remove Selenium’s screenshot API.
A regular screenshot captures the current browsing context, usually the visible viewport. Selenium also supports element screenshots, while full-document capture is a separate capability that varies by browser.
Quick start: headless Chrome with Python
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--headless")
options.add_argument("--window-size=1440,900")
with webdriver.Chrome(options=options) as driver:
driver.get("https://example.com")
driver.save_screenshot("example.png")
The screenshot is written to example.png. Selenium documents save_screenshot(), get_screenshot_as_file(), get_screenshot_as_png(), and get_screenshot_as_base64() for Python. See the Selenium screenshot documentation.
What headless mode changes
Headless mode means the browser runs without opening a visible window or requiring a desktop display. Page navigation, JavaScript execution, cookies, waits, and screenshot commands still use the WebDriver session. Your code calls the same screenshot methods as it would in headed mode.
Headless execution is useful in CI, containers, scheduled jobs, and servers without an X display. It can still differ from headed execution when a page responds to viewport size, device scale factor, browser flags, fonts, GPU behavior, or user-agent details. Make those settings explicit when screenshots are compared pixel by pixel.
Choose the capture type
| Need | Method | Result |
|---|---|---|
| Visible page | Driver screenshot | Current browsing context, normally the viewport |
| One component | Element screenshot | The rendered WebElement region |
| Entire document | Browser-specific full-page method or a stitching approach | Page beyond the viewport |
| Upload or process in memory | PNG bytes | Binary image data |
| Embed in HTML or JSON | Base64 | Encoded screenshot string |
The WebDriver screenshot endpoint is defined for the current browsing context. A conforming implementation follows the WebDriver specification; unsupported implementations may make a best effort. Element screenshots are exposed by WebElement. Firefox’s Python driver additionally provides full-document methods such as get_full_page_screenshot_as_file() and save_full_page_screenshot().
Python: complete examples
Save a viewport screenshot
from pathlib import Path
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
output = Path("artifacts/example.png")
output.parent.mkdir(parents=True, exist_ok=True)
options = Options()
options.add_argument("--headless")
options.add_argument("--window-size=1365,768")
with webdriver.Chrome(options=options) as driver:
driver.get("https://example.com")
if not driver.save_screenshot(str(output)):
raise RuntimeError("Selenium did not save the screenshot")
Get PNG bytes or Base64
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add_argument("--headless")
options.add_argument("--window-size=1280,800")
with webdriver.Chrome(options=options) as driver:
driver.get("https://example.com")
png_bytes = driver.get_screenshot_as_png()
encoded = driver.get_screenshot_as_base64()
with open("page.png", "wb") as image:
image.write(png_bytes)
Capture an element
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
options = Options()
options.add_argument("--headless")
options.add_argument("--window-size=1440,900")
with webdriver.Chrome(options=options) as driver:
driver.get("https://example.com")
card = driver.find_element(By.CSS_SELECTOR, "main")
card.screenshot("main.png")
Firefox full-document capture
from selenium import webdriver
from selenium.webdriver.firefox.options import Options
options = Options()
options.add_argument("-headless")
with webdriver.Firefox(options=options) as driver:
driver.get("https://example.com")
driver.get_full_page_screenshot_as_file("full-page.png")
Use the Firefox full-page API when you specifically need the document beyond the viewport. For Chrome, a normal driver screenshot is generally viewport-scoped; full-page capture may require browser-specific support or stitching.
Java: headless Chrome and TakesScreenshot
import java.io.File;
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;
public class HeadlessShot {
public static void main(String[] args) {
ChromeOptions options = new ChromeOptions();
options.addArguments("--headless", "--window-size=1440,900");
WebDriver driver = new ChromeDriver(options);
try {
driver.get("https://example.com");
File image = ((TakesScreenshot) driver)
.getScreenshotAs(OutputType.FILE);
System.out.println(image.getAbsolutePath());
} finally {
driver.quit();
}
}
}
The Java API also supports OutputType.BASE64. The TakesScreenshot API describes a driver or element that can capture a screenshot in different output forms.
JavaScript: Selenium WebDriver
const { Builder } = require('selenium-webdriver');
const chrome = require('selenium-webdriver/chrome');
const fs = require('node:fs/promises');
(async () => {
const options = new chrome.Options();
options.addArguments('--headless', '--window-size=1440,900');
const driver = await new Builder()
.forBrowser('chrome')
.setChromeOptions(options)
.build();
try {
await driver.get('https://example.com');
const encoded = await driver.takeScreenshot();
await fs.writeFile('example.png', Buffer.from(encoded, 'base64'));
} finally {
await driver.quit();
}
})();
takeScreenshot() returns an encoded screenshot string suitable for Base64 decoding. Keep the browser session open until the file has been written.
C# and Ruby
C#
using OpenQA.Selenium;
using OpenQA.Selenium.Chrome;
var options = new ChromeOptions();
options.AddArgument("--headless");
options.AddArgument("--window-size=1440,900");
using IWebDriver driver = new ChromeDriver(options);
driver.Navigate().GoToUrl("https://example.com");
var screenshot = ((ITakesScreenshot)driver).GetScreenshot();
screenshot.SaveAsFile("example.png");
Ruby
require "selenium-webdriver"
options = Selenium::WebDriver::Chrome::Options.new
options.add_argument("--headless")
options.add_argument("--window-size=1440,900")
driver = Selenium::WebDriver.for :chrome, options: options
begin
driver.navigate.to "https://example.com"
driver.save_screenshot("example.png")
ensure
driver.quit
end
Make dimensions reproducible
- Set the browser window or viewport size before navigation or capture.
- Use the same browser version, driver version, fonts, device scale factor, and headless flags in CI.
- Wait for the page state you need instead of capturing immediately after
get(). - Use a stable URL, test data, timezone, locale, and network conditions for visual comparisons.
Chrome’s headless command-line documentation pairs --screenshot with --window-size=412,892 for deterministic dimensions. The same principle applies when configuring Selenium.
Wait for a specific element
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
wait = WebDriverWait(driver, 20)
hero = wait.until(EC.visibility_of_element_located((By.CSS_SELECTOR, "main")))
hero.screenshot("hero.png")
Wait for fonts and lazy content
driver.execute_async_script("""
const done = arguments[0];
Promise.all([
document.fonts ? document.fonts.ready : Promise.resolve(),
new Promise(resolve => {
if (document.readyState === 'complete') resolve();
else window.addEventListener('load', resolve, { once: true });
})
]).then(() => done());
""")
A page can report load while application JavaScript, images, fonts, or API data are still changing. Add a targeted wait or a short delay only when the page has no reliable readiness signal.
Viewport versus full-page screenshots
A viewport screenshot is fast and predictable, but it excludes content below the fold. Full-page screenshots include the document’s scrollable area when the browser binding supports it. Long pages can contain fixed headers, lazy-loaded images, sticky elements, animations, and cross-origin frames that make stitching imperfect.
For visual regression, decide whether you want a viewport contract or a document contract. Keep the choice consistent across runs. If you stitch screenshots yourself, scroll in controlled increments, wait for images after each scroll, and remove or freeze sticky elements to avoid repeated headers.
Common errors and fixes
| Error or symptom | Likely cause | Fix |
|---|---|---|
DevToolsActivePort file doesn't exist |
Browser startup failure, incompatible versions, or restricted container settings | Match browser and driver versions, use a current Selenium release, and inspect browser startup logs. In a constrained Linux container, configure the container’s shared memory and sandbox policy according to its security requirements. |
SessionNotCreatedException |
Driver cannot launch the installed browser | Update Selenium and ensure the browser binary and driver are compatible. |
| Screenshot is blank | Capture happened before content rendered, navigation failed, or the page returned a bot check | Check current_url and page source, wait for a visible selector, and log browser console or network failures. |
| Only the viewport is captured | Standard driver screenshot is viewport-scoped | Use an element or full-document API supported by your browser, or implement careful stitching. |
| Text or layout differs in CI | Different viewport, fonts, scale factor, browser version, locale, or timezone | Pin those inputs and set the window size explicitly. |
| Cookie banner covers content | The page requires consent interaction | Locate and click the consent control before capture, or hide the banner only when doing so matches your test’s purpose. |
| Images are missing | Lazy loading, blocked requests, slow network, or capture too early | Wait for the relevant images, scroll lazy regions into view, and verify image completion with JavaScript. |
| Driver hangs on quit | Browser process or page script is stuck | Use explicit command timeouts, collect logs, and make sure every code path calls quit(). |
Reliability and performance checklist
- Reuse one driver for a controlled batch of URLs when isolation is not required; creating a browser for every URL adds startup cost.
- Use explicit waits for page-specific readiness rather than a large fixed sleep.
- Set page-load and script timeouts so a broken URL cannot hold a worker forever.
- Limit concurrency according to available CPU and memory. Each browser session consumes resources.
- Save screenshots atomically when another process reads the output directory.
- Record URL, viewport, browser version, timestamp, and failure details with each artifact.
- Disable animations or wait for them to finish when comparing pixels.
- Expect third-party ads, consent tools, chat widgets, and bot checks to change independently of your code.
Or skip the browser setup
ScreenshotNeo provides a website screenshot API: one GET request returns PNG, JPEG, WebP, or PDF. Its capture options cover full-page screenshots with lazy images loaded, CSS element selection, dark mode, device presets or custom viewports, retina scale, waits, custom CSS and JavaScript, clicks, hidden selectors, request blocking, headers, cookies, user agents, authorization, timezone, geolocation, resizing, caching, signed links, asynchronous jobs, bulk capture, and PDF settings. Read the ScreenshotNeo documentation for the complete parameter list.
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}`);
Cookie banners, newsletter popups, and chat widgets are removed before the shot. Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed, and response headers report the page verdict and billing result. ScreenshotNeo also has an MCP server so Claude, Cursor, and other MCP clients can take screenshots. 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.
FAQ
Can Selenium take screenshots without a display?
Yes. Chrome, Firefox, and other supported browsers can run headlessly, and their WebDriver screenshot methods remain available.
Does headless mode capture the whole page?
Usually the standard call captures the current viewport. Full-document capture is a separate browser or binding capability.
Can I capture an element instead of the page?
Yes. Find the element and call its screenshot method, such as Python’s element.screenshot().
Why should I set a window size?
Responsive layouts change with viewport dimensions. An explicit size makes output dimensions and visual comparisons repeatable.
What formats does Selenium return?
Bindings commonly write a PNG file, return PNG bytes, or return a Base64-encoded string. Selenium’s exact method names depend on the language binding.
When is an API preferable to running Selenium?
Use an API when you need hosted capture, consent and popup cleanup, billing for successful captures only, bulk jobs, signed links, or an MCP workflow for AI agents.


