How to Capture Selenium Screenshots in an AWS Lambda Function
Run Selenium in AWS Lambda, capture a PNG, and return or store it. Learn how to package Chromium, handle timeouts, and troubleshoot runtime issues.
To capture a Selenium screenshot in AWS Lambda, package Selenium, a Chromium browser, its matching driver, and compatible native libraries for your Lambda runtime and architecture. Launch the browser headlessly, navigate to the page, wait for the content you need, and capture a PNG. Write temporary files under /tmp; return the PNG bytes or upload them to durable storage before the invocation ends.
The example below shows the handler and screenshot flow. It deliberately leaves the Chromium and driver paths configurable: AWS and Selenium document the packaging constraints and screenshot APIs, but there is no single browser bundle or launch-flag set that is correct for every runtime and architecture.
1. Choose a Lambda package format
Lambda accepts ZIP deployment packages, optionally with layers, and container images. For Python, dependencies can go in the function ZIP or a layer. The browser executable, driver, and native libraries must be compatible with the selected Lambda operating system, runtime, and instruction-set architecture. Build and verify those components together; the research sources do not validate a specific third-party Chromium layer or binary release. See AWS Python deployment packages and AWS container images.
| Choice | When it fits | Limit to plan around |
|---|---|---|
| ZIP and layers | The browser bundle and dependencies fit within the package and the build workflow is manageable. | 250 MB unzipped, including layers. |
| Container image | You want to manage the browser and system dependencies in an image or need more packaging headroom. | 10 GB maximum uncompressed image size, including layers. |
These are packaging limits, not performance comparisons. An image does not remove the need to match browser binaries and native libraries to the runtime and architecture. AWS documents a maximum function timeout of 900 seconds (15 minutes) and configurable /tmp storage from 512 MB to 10,240 MB. Check the current Lambda quotas before deploying because limits can change.
2. Configure Selenium and capture the page
This Python handler expects your deployment bundle to provide a compatible Chromium binary and WebDriver executable. Set CHROME_BINARY and CHROMEDRIVER to their actual paths. The example returns the PNG directly through a Lambda proxy response, base64-encoded as required for binary payloads. Configure your API Gateway or other caller to accept binary responses as appropriate.
import base64
import os
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
def lambda_handler(event, context):
url = (event.get("queryStringParameters") or {}).get("url")
if not url:
return {
"statusCode": 400,
"headers": {"content-type": "application/json"},
"body": '{"error":"Provide a url query parameter"}',
}
options = webdriver.ChromeOptions()
options.binary_location = os.environ["CHROME_BINARY"]
options.add_argument("--headless")
# Add only flags required by the specific browser bundle and runtime.
options.add_argument("--no-sandbox")
driver = None
try:
driver = webdriver.Chrome(
service=Service(os.environ["CHROMEDRIVER"]),
options=options,
)
driver.set_page_load_timeout(45)
driver.get(url)
# Replace this with a page-specific readiness condition when content
# is rendered asynchronously. A fixed sleep is not a reliable signal.
png_bytes = driver.get_screenshot_as_png()
return {
"statusCode": 200,
"headers": {"content-type": "image/png"},
"isBase64Encoded": True,
"body": base64.b64encode(png_bytes).decode("ascii"),
}
finally:
if driver is not None:
driver.quit()
Install Selenium into the ZIP or a layer using a build environment compatible with the Lambda runtime. Include the handler at the expected archive path, and include the browser, driver, and all required shared libraries. Make the executables runnable in the deployed environment. The exact build commands depend on the chosen runtime base, browser release, and architecture, so pin and validate those inputs in your own build rather than assuming a community layer is current.
The sample accepts a URL from the request, so a production endpoint should restrict allowed destinations or otherwise validate input to prevent callers from using the function to access unintended network locations. The allowed-host policy depends on your application.
3. Wait for the right content
Selenium’s get(url) waits for the page-load event. That does not guarantee that content rendered later by JavaScript is ready. Use a meaningful condition for the page you are capturing, such as waiting for a known element:
from selenium.webdriver.common.by import By
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.support.ui import WebDriverWait
# After driver.get(url):
WebDriverWait(driver, 20).until(
EC.visibility_of_element_located((By.CSS_SELECTOR, "main article"))
)
png_bytes = driver.get_screenshot_as_png()
Choose a selector that indicates the content you need is present, not merely that the document exists. For sites with multiple rendering states, wait for the specific data or application state that matters. Selenium also provides set_page_load_timeout to bound navigation; keep it below the overall Lambda timeout so cleanup and response handling have time to run.
4. Choose how to handle the screenshot output
Selenium provides three useful screenshot forms:
| Method | Result | Use it when |
|---|---|---|
get_screenshot_as_png() |
PNG bytes | You will return or upload the image from memory. |
get_screenshot_as_base64() |
Base64 text | A downstream interface specifically needs base64. |
save_screenshot(path) or get_screenshot_as_file(path) |
PNG written to a file | A library or upload flow needs a filesystem path. |
The file methods return True on success and False for an I/O error. If you save a file, use a unique filename under /tmp, check the return value, and read or upload the result before returning. For example:
path = "/tmp/page.png"
if not driver.save_screenshot(path):
raise RuntimeError("Selenium could not write the screenshot")
with open(path, "rb") as image_file:
png_bytes = image_file.read()
/tmp is temporary storage unique to an execution environment. AWS may reuse an execution environment, so files can remain between invocations in that environment, but they are not durable and should not be treated as shared storage. Upload results that must persist to a durable destination, or return the image in the invocation response. Allocate enough ephemeral storage for the extracted browser, downloads, and screenshots your workload needs. See AWS Lambda ephemeral storage.
5. Return an image or persist it
Returning base64 in a proxy response is convenient for small captures and direct callers. For a workflow that needs durable access, upload the PNG to an object store or another durable destination before the handler exits, then return a reference your application can use. The specific storage service and access policy are application decisions; the screenshot itself does not become durable just because it was written to /tmp.
Consider response size and transport constraints when returning images inline. If images can be large, uploading them and returning a reference avoids carrying the full binary through every caller in the request path. Set permissions so only intended users can retrieve stored screenshots.
6. Clean up browser processes
Call driver.quit() in a finally block. It closes the browser and driver executable, including when navigation or screenshot capture raises an error. Avoid relying on the next invocation or environment shutdown to clean up the browser.
7. Tune timeout, storage, and reliability
- Timeout: Set the Lambda timeout to cover cold browser startup, navigation, readiness waits, capture, and upload or response work. Lambda allows up to 900 seconds, but a short, realistic per-page limit helps contain slow or stuck targets.
- Page-load timeout: Set Selenium’s navigation timeout explicitly. Handle a timeout as a failed capture and ensure the browser is still quit.
- Memory and CPU: Browser startup and page rendering consume resources. Choose function memory based on observed behavior for your workload; the supplied research does not establish a performance benchmark or recommended memory setting.
- Ephemeral storage: Count browser extraction, temporary downloads, and output files together. Set the
/tmpallocation to match the actual bundle and concurrency pattern. - Retries: Retrying can help with transient target failures, but avoid unbounded retries. Use an overall request deadline and distinguish navigation failure from a successful capture.
- Cleanup: Always quit the WebDriver and avoid reusing leftover files by using unique names or deleting temporary outputs after upload.
- Compatibility: Keep Lambda runtime, architecture, Chromium, driver, and native libraries pinned as a compatible set. Rebuild and verify the bundle when any of them changes.
8. Troubleshoot common failures
| Symptom | Likely cause | What to check or change |
|---|---|---|
| Driver or browser cannot start | Wrong executable path, architecture mismatch, missing shared library, or incompatible browser and driver. | Log the configured paths; verify executability and architecture; inspect native dependencies in the deployed runtime; rebuild the bundle for the selected Lambda runtime and architecture. |
| “Unable to locate or obtain driver” | The driver was not packaged where configured or Selenium cannot execute it. | Include the matching driver, set the correct service path, and verify file permissions and runtime compatibility. |
| Browser exits immediately | A required runtime dependency or browser launch setting is missing, or the binary is incompatible. | Read browser and driver logs, confirm the bundle was built for the Lambda environment, and add only launch flags required by that browser build. |
| Navigation times out | The target is slow, unreachable from the function, or blocked, or the page never reaches the load condition. | Check network access and target response; set a bounded page-load timeout; wait on a specific readiness condition where appropriate; keep the Lambda timeout longer than the work plus cleanup. |
| Screenshot is blank or missing dynamic content | The capture occurred before client-rendered content appeared, or the target returned an interstitial or error page. | Wait for a page-specific element or state and inspect the resulting page before capture. A generic delay is not a guarantee. |
| Screenshot file is absent | Wrong output path, I/O failure, or file was assumed to persist outside the invocation. | Write under /tmp, check the file method’s Boolean result, and return or upload the bytes before the invocation ends. |
| “No space left on device” | The ephemeral allocation is insufficient for extracted browser files, downloads, or output. | Increase configured /tmp storage within Lambda’s supported range and remove unnecessary temporary files. |
| Function hits its overall timeout | Startup, page load, wait, or upload took longer than the configured invocation timeout. | Bound navigation and readiness waits, set a realistic Lambda timeout, and avoid unbounded retries. |
| Image response is corrupted | Binary response was returned as ordinary text or proxy integration is not configured for binary content. | Base64-encode the PNG, set isBase64Encoded to true, use image/png, and configure the integration to handle binary payloads. |
9. Cost and deployment tradeoffs
Lambda cost depends on your invocation configuration and actual execution, including the time spent starting Chromium, loading the page, waiting, and persisting the image. The research does not provide a cost benchmark for Selenium screenshots, so estimate with your own memory, timeout, invocation volume, and storage settings. ZIP packages have less uncompressed headroom than container images; choosing an image may simplify dependency packaging, but it does not imply faster capture. Keep browser updates and compatibility checks in the maintenance cost.
Or skip the browser setup
If you need screenshot output without bundling and maintaining Selenium, Chromium, a driver, and native libraries in Lambda, ScreenshotNeo is a website screenshot API and MCP server for developers. One GET request returns a 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}`);
ScreenshotNeo accepts cookie and consent banners like a visitor and removes 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 the result identified in response headers. Its MCP server provides take_screenshot, get_page_info, and capture_pdf tools 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. Sign up for ScreenshotNeo free and get 1,000 screenshots a month with no card.
FAQ
Does Selenium’s screenshot method save a full-page screenshot?
The methods shown capture the browser’s current screenshot. Full-page behavior depends on the browser and capture approach; the code here does not implement a full-page stitching workflow.
Can I keep a screenshot in /tmp for a later invocation?
You can use temporary files while an execution environment is available, but /tmp is not durable storage and is not shared across all environments. Persist the image separately if it must remain available.
Which Chromium layer should I use?
The research does not verify a specific layer or binary release. Select and pin a bundle that matches your runtime and architecture, then validate its browser, driver, and native dependencies together.
Should I use ZIP or a container image?
Choose based on package size, dependency build workflow, and how you manage native libraries. ZIPs have a 250 MB unzipped limit including layers; container images allow up to 10 GB uncompressed.


