Puppeteer vs Selenium for Automated Website Screenshots
Compare Puppeteer and Selenium for website screenshots, with runnable full-page and element capture examples, trade-offs, troubleshooting, and a simpler API option.
Both Puppeteer and Selenium can capture website screenshots. Choose Puppeteer when your automation is JavaScript-centric and its direct page and element screenshot APIs fit your target browser. Choose Selenium when your team’s language bindings, local or remote WebDriver sessions, or established WebDriver ecosystem are the deciding factors. There is no source-grounded universal winner for speed, visual fidelity, reliability, or cost.
For repeatable results, prove out the exact browser, version, binding, viewport, full-page behavior, element capture, and page-readiness strategy you plan to deploy. If you want screenshots without provisioning a browser, ScreenshotNeo offers a screenshot API and MCP server; its call is shown below.
What differs for screenshot work
| Decision | Puppeteer | Selenium |
|---|---|---|
| Code and API | A JavaScript library with page-level and element-level screenshot APIs. | Language bindings paired with browser-control implementations. |
| Browser control | Officially describes Chrome and Firefox control over the DevTools Protocol or WebDriver BiDi; headless by default. | WebDriver drives browsers locally or remotely and is a W3C Recommendation. |
| Page capture | Page.screenshot() can save a file or return image data. |
WebDriver exposes screenshot commands; return shape and options depend on binding. |
| Element capture | ElementHandle.screenshot() scrolls the element into view when needed. |
Element screenshot methods/examples are documented; check your binding’s API. |
| Full document | Use fullPage: true where supported by the selected protocol. |
Do not assume the generic screenshot command means full document. Behavior varies by driver and binding; Selenium’s Firefox Python API documents full-document methods specifically. |
Puppeteer’s BiDi guide lists clip, encoding, and fullPage among the supported Page.screenshot() parameters and warns that other parameters may not be supported there. Selenium’s JavaScript API describes screenshot capture as best effort, preferring the entire page, then current window, visible frame, and display. Treat these as implementation-specific details and verify your chosen stack.
Runnable Puppeteer examples
Install Puppeteer in a Node.js project with npm install puppeteer. The package’s browser setup and requirements can change, so consult its current official documentation for your installed version.
Full-page PNG
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({ width: 1365, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com', { waitUntil: 'networkidle2' });
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await browser.close();
}
})();
networkidle2 is a navigation wait option shown in Puppeteer’s guide, not a guarantee that every site is visually ready. For pages with ongoing requests, wait for a meaningful selector or application-specific ready state instead.
Capture one element
const puppeteer = require('puppeteer');
(async () => {
const browser = await puppeteer.launch();
try {
const page = await browser.newPage();
await page.setViewport({ width: 1365, height: 900, deviceScaleFactor: 1 });
await page.goto('https://example.com', { waitUntil: 'domcontentloaded' });
const element = await page.waitForSelector('main article');
if (!element) throw new Error('Target element was not found');
await element.screenshot({ path: 'article.png' });
} finally {
await browser.close();
}
})();
Puppeteer scrolls an element into view for its screenshot if needed. The call can fail if the element has become detached from the DOM, so locate it after navigation and avoid retaining handles across rerenders.
Runnable Selenium examples
Install Selenium for Python with python -m pip install selenium. Selenium’s browser and driver setup depends on the environment and current Selenium version; follow the official documentation for your browser and binding.
Python: screenshot the current browser view
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
options = Options()
options.add_argument('--headless')
options.add_argument('--window-size=1365,900')
driver = webdriver.Chrome(options=options)
try:
driver.get('https://example.com')
WebDriverWait(driver, 15).until(
lambda d: d.execute_script('return document.readyState') == 'complete'
)
driver.save_screenshot('viewport.png')
finally:
driver.quit()
This uses Selenium’s general WebDriver screenshot route. Its scope can depend on the browser implementation. Confirm whether the output is the viewport or full document in your target setup.
Python: screenshot an element
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
options = Options()
options.add_argument('--headless')
options.add_argument('--window-size=1365,900')
driver = webdriver.Chrome(options=options)
try:
driver.get('https://example.com')
article = WebDriverWait(driver, 15).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, 'main article'))
)
article.screenshot('article.png')
finally:
driver.quit()
Full-document capture with Selenium
There is no single cross-browser, cross-binding guarantee that the generic Selenium screenshot method captures the full document. Selenium’s Firefox Python API documents full-document screenshot methods. If full-page capture is required, use the method documented for your actual driver and binding, or assemble a tiled capture only after testing sticky headers, lazy content, and page layout. Do not silently treat a viewport screenshot as full-page output.
Compare the two fairly
- Pin the environment. Record browser and framework versions, operating system, installed fonts, and headless or headful mode.
- Set the same viewport. Choose width, height, and device scale explicitly. Puppeteer’s guide demonstrates
page.setViewport(); Selenium documents window resizing and notes screen resolution affects rendering. Puppeteer documents an 800 × 600 default headless screen unless a window size is specified, so avoid relying on defaults. - Define capture scope. Decide viewport, full document, or a particular element. Validate dimensions and content in the produced file.
- Wait for the page you need. Use a selector or application-ready condition for dynamic pages. Network-idle conditions can be unsuitable for pages with long-lived connections or polling.
- Use equivalent inputs. Keep URL, authentication, cookies, locale, time zone, test data, and network conditions consistent.
- Check artifact handling. Confirm output format, location, encoding, naming, and cleanup in your deployment environment.
- Exercise parallel runs. Check browser provisioning, memory use, and isolation under your expected concurrency. The documentation reviewed does not establish a comparative capacity or performance winner.
Configuration choices that affect the result
Viewport, scale, and headless mode
Set the viewport instead of trusting ambient defaults. A different viewport changes responsive breakpoints, line wrapping, and page layout. Device scale affects pixel dimensions and should be held constant in visual comparisons. Puppeteer’s headless screen configuration has documented options and limits; its --screen-info configuration is headless-only, so do not assume it configures a physical headful display.
Readiness and dynamic content
A completed navigation does not necessarily mean images, fonts, client-side rendering, or delayed widgets are ready. Wait for a site-specific element or state when possible. For lazy-loaded pages, scrolling may be necessary before capture; test that the content expected in the image has loaded. Keep waits bounded so a permanently busy page does not stall a job indefinitely.
Page versus element and full-page scope
Element screenshots are useful for cards, charts, and individual components. Confirm the selector identifies one intended visible element. For full-page work, inspect the result on tall pages: fixed elements, sticky headers, lazy images, and browser-specific capture behavior can affect output. Puppeteer’s element API can throw if its target is detached; reacquire the handle after DOM changes.
Data returned by APIs
Puppeteer’s page screenshot returns a Uint8Array by default, or a string when base64 encoding is requested. Selenium’s JavaScript takeScreenshot() returns base64-encoded PNG. Decode and save according to the binding’s documented return type; do not write a base64 string as if it were raw PNG bytes.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
| Only the visible area is in the image | The selected Selenium command or browser driver captures the current window, or full-page support is not enabled for that route. | Use a documented full-document method for the exact driver/binding and verify output dimensions. For Puppeteer, set fullPage: true where supported. |
| Screenshot is blank or missing a chart | Capture happened before client rendering or chart drawing completed. | Wait for a meaningful selector or app-ready condition; use a bounded wait and inspect browser logs if needed. |
| Lazy images are absent | The images have not entered the page’s loading region before capture. | Scroll through the document and wait for image completion, then capture; validate on the actual page. |
| Puppeteer reports a detached element | The page rerendered and replaced the element after its handle was obtained. | Wait for the final state and query the element again immediately before capture. |
| Different runs produce different layout | Viewport, device scale, fonts, browser version, locale, data, or readiness differs. | Pin these inputs and capture only after a stable page state. |
| Navigation wait never completes | The site keeps requests open or continuously polls, making network-idle an unsuitable condition. | Wait for a specific selector or application signal instead, and use a timeout. |
| Image file cannot be opened | Base64 text was saved as binary, or the output path/format is wrong. | Use the binding’s documented byte or base64 return behavior and decode base64 before writing. |
| Browser does not start in deployment | Browser provisioning, executable availability, or headless configuration differs from development. | Check the framework’s current setup guide and run the same minimal capture in the deployment image. |
Performance, reliability, and cost
The reviewed official documentation does not provide a controlled comparison of speed, fidelity, flakiness, or operating cost. Browser startup, page complexity, network conditions, and concurrency all affect a screenshot job, so benchmark your own target pages and deployment setup before choosing on performance grounds.
For reliability, close browser sessions in a finally block, bound navigation and readiness waits, and isolate per-job state such as cookies and browser contexts. Save failures with enough context to diagnose the page and environment. These are implementation practices; they are not measured comparative claims about either product.
For cost, include browser hosting, driver/browser maintenance, engineering time, storage, and retries in your own estimate. The dossier supports no comparative cost figure for Puppeteer or Selenium.
Or skip the browser setup
Use ScreenshotNeo when you want a screenshot API call instead of managing browser automation. 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}`);
ScreenshotNeo accepts cookie and consent banners 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, blank pages, timeouts, failed loads, and cache hits cost nothing, with page verdict and billing headers in each response. Its MCP server gives AI agents tools for screenshots, page information, and PDF capture. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 shots. Every feature is on every plan.
Sign up for 1,000 free screenshots a month, with no card required.
Frequently asked questions
Can Selenium take screenshots?
Yes. Selenium documents WebDriver and element screenshot paths. The exact scope and options depend on the chosen browser and language binding.
How do I take a full-page screenshot with Selenium?
Use the full-document method documented for your exact driver and binding. Selenium’s Firefox Python API provides full-document methods; generic screenshot behavior should be verified rather than assumed.
How do I screenshot an element with Puppeteer?
Find the element and call its screenshot() method. Puppeteer scrolls it into view if needed; the handle must still refer to an attached element.
Which one should a JavaScript team choose?
Puppeteer is a natural choice when its documented browser/protocol support and direct APIs fit the requirement. Existing WebDriver infrastructure can still make Selenium the simpler team choice.
Does either produce more accurate screenshots?
The reviewed documentation establishes no general image-quality winner. Compare both in the browser, version, viewport, and page conditions you will actually use.
