Puppeteer vs. Selenium for Rendering Website Screenshots
Compare Puppeteer and Selenium for website screenshots, with runnable examples, browser setup guidance, troubleshooting, and help choosing the right tool.

Short answer: Choose Puppeteer when your screenshot workflow is centered on Chrome and you want a direct JavaScript API for page and element captures. Choose Selenium when your team needs its broader language bindings, browser coverage, or Selenium Grid orchestration. Both can capture screenshots in headless CI. The available documentation does not establish a universal speed winner, so decide based on browser and protocol support, deployment, and the capture controls your job requires.
If you want website screenshots without installing and maintaining a browser, ScreenshotNeo is an API alternative to try first: it removes common consent banners, popups, and chat widgets before capture, and failed or unclean captures are not billed.
1. What Puppeteer and Selenium do
Puppeteer and Selenium automate real browsers. A screenshot job typically opens a URL, waits for the page to reach the state you need, applies a viewport or capture mode, and writes an image file. The screenshot is the output of a browser render, so browser version, viewport, device scale factor, fonts, network responses, and page readiness can all affect the result.

Puppeteer provides screenshot methods directly: Page.screenshot() captures a page, and ElementHandle.screenshot() captures a selected element. Selenium provides browser automation through WebDriver; screenshot capture fits naturally into an existing Selenium test or browser automation suite. See the official Puppeteer screenshot guide and Chrome automation overview.
Both are suitable for unattended server and CI work. Chrome Headless is designed for those environments. For stable output, pin browser versions; when using WebDriver with Chrome, use a matching Chrome for Testing and ChromeDriver pair.
2. Choose by the constraints that matter
| Need | Usually the better fit | Why |
|---|---|---|
| JavaScript or Node.js stack | Puppeteer | Its page and element screenshot APIs are direct and documented. |
| Multiple programming languages | Selenium | Selenium offers more language bindings. |
| Existing distributed browser infrastructure | Selenium | Selenium Grid can orchestrate browsers at scale. |
| Chrome-centered capture with compact setup | Puppeteer | It offers a straightforward browser control and screenshot workflow. |
| Specific browser or protocol requirements | Evaluate both against the exact requirements | Check browser support and the screenshot features available through the chosen protocol. |
This comparison is about fit, not an assertion that one tool is faster. The official sources describe capabilities and architecture rather than a controlled current screenshot benchmark. Measure your own pages if throughput or latency is a deciding factor.
3. Capture screenshots with Puppeteer
Install and capture a full page
With Node.js and npm installed, create a project and install Puppeteer. The package downloads a compatible browser as part of its normal installation workflow; in managed CI environments, follow the project’s browser installation guidance and pin versions.
npm init -y
npm install puppeteer
Save this as screenshot.js and run node screenshot.js https://example.com. It writes a full-page PNG. The navigation wait is configurable because a generic network-idle condition does not guarantee that every application has finished its visual updates.
const puppeteer = require('puppeteer');
async function main() {
const url = process.argv[2];
if (!url) throw new Error('Usage: node screenshot.js <url>');
const browser = await puppeteer.launch({ headless: true });
try {
const page = await browser.newPage();
await page.setViewport({ width: 1440, height: 900, deviceScaleFactor: 1 });
await page.goto(url, { waitUntil: 'networkidle2', timeout: 60000 });
await page.screenshot({ path: 'page.png', fullPage: true });
} finally {
await browser.close();
}
}
main().catch((error) => {
console.error(error);
process.exitCode = 1;
});
Use a deliberate readiness condition. Puppeteer supports navigation lifecycle conditions such as domcontentloaded, load, and network idle. A page with long polling, analytics, or continuously refreshed content may never become network-idle. For application-specific rendering, wait for a stable selector or a known state after navigation instead of waiting indefinitely for all network activity to stop.
Capture one element
Select the element and invoke its screenshot method. Puppeteer can scroll a hidden element into view before taking the capture. A missing selector should be treated as a page-state or selector error, not silently saved as a valid image.
const target = await page.waitForSelector('.invoice-summary', { timeout: 15000 });
if (!target) throw new Error('Could not find .invoice-summary');
await target.screenshot({ path: 'summary.png' });
Useful capture controls
- Viewport: Set width and height before navigation or capture to control responsive layout.
- Device scale factor: Use a stable value such as 1 for predictable pixel dimensions, or a higher scale when the output needs more pixels.
- Full page: Set
fullPage: truewhen the whole document should be captured. Very long pages create large images and may expose lazy-loading behavior. - Element-only: Use an element handle when a page section is more useful than the complete document.
- Readiness: Wait for an application selector, a known state, or a carefully chosen navigation condition. Add a short delay only when the page has a known deferred visual update.
- Output: Choose a path and format supported by the Puppeteer screenshot API, then preserve that format in the file extension and downstream processing.
For repeatability, keep the browser version, Puppeteer version, viewport, scale factor, target URL, readiness logic, and capture mode with the output metadata. Use a consistent environment with installed fonts and predictable network access when comparing screenshots over time.
4. Capture screenshots with Selenium
Selenium is a good choice when screenshots are one action in a test suite that already uses WebDriver, or when its language bindings and Grid orchestration match your team. The following Python example uses Selenium 4 and Chrome in headless mode. Install the binding with python -m pip install selenium; use the Chrome for Testing and ChromeDriver pairing recommended for your environment.
from pathlib import Path
import sys
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
if len(sys.argv) != 2:
raise SystemExit('Usage: python screenshot.py <url>')
url = sys.argv[1]
options = Options()
options.add_argument('--headless=new')
options.add_argument('--window-size=1440,900')
driver = webdriver.Chrome(options=options)
try:
driver.set_page_load_timeout(60)
driver.get(url)
WebDriverWait(driver, 15).until(
lambda d: d.execute_script('return document.readyState') == 'complete'
)
Path('page.png').write_bytes(driver.get_screenshot_as_png())
finally:
driver.quit()
Run it as python screenshot.py https://example.com. This captures the browser’s current viewport. A full-document screenshot is not a uniform WebDriver operation across browsers and bindings; check the specific driver’s supported behavior if full-page capture is required. For an element screenshot, locate the element and use the binding’s element screenshot method, for example:
from selenium.webdriver.common.by import By
summary = WebDriverWait(driver, 15).until(
lambda d: d.find_element(By.CSS_SELECTOR, '.invoice-summary')
)
summary.screenshot('summary.png')
The readiness condition above checks document loading, not that a single-page application has finished rendering its data. Replace or supplement it with a wait for the specific content your screenshot depends on. The browser viewport, headless configuration, and browser/driver versions should be held stable across runs.
5. Browser protocol and compatibility
Puppeteer’s protocol support is not simply CDP-only. It uses Chrome DevTools Protocol (CDP) by default for Chrome and WebDriver BiDi by default for Firefox; Chrome can also use BiDi. However, Puppeteer documents incomplete feature support over BiDi, and Chrome continues to default to CDP because not all CDP features are available through BiDi. Unsupported operations may throw UnsupportedOperation.
The documented Puppeteer BiDi screenshot parameters include clip, encoding, and fullPage. If your workflow depends on a particular option, verify that the option works over the exact browser and protocol combination before adopting it. Selenium’s WebDriver ecosystem is a different route for cross-browser automation and Grid orchestration; confirm the behavior of screenshot operations in your chosen binding and browser.
For current details, consult the Puppeteer WebDriver BiDi documentation, the Puppeteer FAQ, and your Selenium binding and driver documentation. Selenium’s August 2024 announcement welcomes Puppeteer’s BiDi support and gives historical context; use current docs for exact feature behavior.
6. Reliability, performance, and cost
Make output repeatable
- Pin Puppeteer or Selenium, browser, and driver versions. For Chrome WebDriver, keep Chrome for Testing and ChromeDriver matched.
- Fix the viewport, scale factor, timezone, and other page inputs that influence responsive or time-sensitive output.
- Wait for the content you need, not merely a convenient global signal. Network idle is unsuitable for some sites; document readiness alone may be too early for client-rendered content.
- Close the page and browser in cleanup code, including on errors. In Selenium, call
quit(); in Puppeteer, close the browser in afinallyblock. - Record versions and capture settings alongside image artifacts to explain changes between runs.
Plan for resource use
Browser automation consumes CPU and memory, and each browser process adds setup and lifecycle work. Reuse browser processes carefully when running many jobs, while isolating pages and cleaning up state between captures. Parallelism can improve throughput but also increases resource use; set concurrency according to the memory and CPU available in your runner. Full-page images are larger and can take longer to produce than viewport captures, especially on very long pages.
There is no source-backed universal speed comparison between Puppeteer and Selenium. The practical cost includes engineering time for browser installation, upgrades, debugging, and infrastructure, plus the compute consumed by each capture. Run a representative workload in your own environment before setting concurrency or service-level expectations.
7. Troubleshooting common screenshot failures
| Symptom | Likely cause | Fix |
|---|---|---|
| Navigation times out | The site is slow, blocked, or keeps network requests open. | Increase the timeout only when justified; choose a suitable readiness condition or wait for the page-specific selector. |
| Screenshot is blank or incomplete | Capture occurred before client rendering, fonts, images, or data were ready. | Wait for the relevant selector or application state; confirm required resources can load in the runner. |
| Element selector times out | The selector changed, the element is conditional, or the page is in the wrong state. | Inspect the rendered DOM and wait for the correct selector after the required navigation or interaction. |
| ChromeDriver cannot start Chrome | Browser and driver versions do not match, or the headless environment lacks needed dependencies. | Use a matched Chrome for Testing/ChromeDriver pair and install the runtime dependencies required by the container. |
| Screenshot dimensions differ between runs | Viewport, device scale factor, browser version, fonts, or responsive content changed. | Pin browser and capture settings; use a consistent environment and inspect the recorded metadata. |
| Full-page capture omits lazy images | Images load only when their regions enter the viewport. | Scroll through the page to trigger lazy loading, wait for image completion, then capture; confirm the chosen tool’s full-page behavior. |
UnsupportedOperation with Puppeteer BiDi |
The selected operation is not implemented over BiDi. | Check the current support list and use a supported operation or the protocol path that provides the needed feature. |
| CI process hangs after the image is saved | A browser process or WebDriver session was not closed. | Use cleanup blocks and close the browser/session on both success and error paths. |
8. Or skip the browser setup
ScreenshotNeo is a website screenshot API and MCP server. One GET request returns PNG, JPEG, WebP, or PDF. The example below uses the documented API call; see the ScreenshotNeo API documentation for parameters and configuration.

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; each step can be turned off.
- Bot checks, blank pages, timeouts, failed loads, and cache hits are not billed. Responses include
X-Page-VerdictandX-Billedheaders. - An MCP server lets Claude, Cursor, or another MCP client use
take_screenshot,get_page_info, andcapture_pdf. - 1,000 screenshots per month are free with no card. Paid plans start at $5 for 3,000 screenshots.
Sign up for 1,000 free screenshots a month, with no card required.
9. Decision checklist
- Is the job primarily JavaScript and Chrome? Start with Puppeteer.
- Do you need more language bindings, cross-browser coverage, or Grid orchestration? Start with Selenium and validate the precise screenshot behavior you need.
- Does the capture depend on element screenshots, full-page output, or a particular protocol option? Confirm browser and protocol support with a small representative page.
- Will this run repeatedly in CI? Pin browser and driver versions, define a page-specific readiness condition, and save capture metadata.
- Would browser installation and upkeep be unnecessary overhead? Consider the ScreenshotNeo API or MCP server for service-based capture.
10. FAQ
Which is better for website screenshots, Puppeteer or Selenium?
Puppeteer is a direct fit for Chrome-centered JavaScript capture. Selenium is a stronger fit when language choice, browser coverage, or Grid orchestration leads the decision.
Can Puppeteer capture a single element?
Yes. Its documented ElementHandle.screenshot() API captures an element and can scroll it into view.
Does Selenium work in headless CI?
Yes. Selenium can drive headless browsers in unattended environments. Pin the browser and driver pair for reproducibility.
Is Puppeteer faster than Selenium?
The cited official documentation does not establish a universal speed winner. Compare them using your target sites, browser versions, runner resources, and readiness rules.
Can Puppeteer use WebDriver BiDi with Chrome?
Yes, but documented feature support is incomplete. Check the current support list for the screenshot parameters and operations your workflow needs.