ScreenshotNeo

BlogHow-to

How to save Selenium screenshots directly to an S3-compatible storage bucket

Capture Selenium screenshots as PNG bytes and upload them to an S3-compatible bucket without a temporary file, using Python and Boto3.

By the ScreenshotNeo team4 October 20267 min read

Use Selenium’s get_screenshot_as_png() to get the current browser window as PNG bytes, wrap those bytes in io.BytesIO, and pass the file-like object to Boto3’s upload_fileobj(). This sends the screenshot to your S3-compatible bucket without writing a temporary image file.

The example below uses Python, Selenium, and Boto3. Set the endpoint, bucket, credentials, and region values according to your storage provider’s documentation. Selenium WebDriver API · Boto3 upload_fileobj

1. Install the Python packages

python -m pip install selenium boto3

You also need a browser and the corresponding Selenium driver available in your environment. Selenium Manager can manage drivers for supported browsers in current Selenium releases; consult the Selenium Manager documentation if your environment requires explicit browser or driver setup.

2. Configure the storage settings

Provide these environment variables to the Python process. Their names are for this example; they are not required names from any storage provider.

export S3_ENDPOINT_URL="https://storage.example-provider.invalid"
export S3_BUCKET="my-bucket"
export S3_ACCESS_KEY="replace-me"
export S3_SECRET_KEY="replace-me"
export S3_REGION="us-east-1"

Replace the example endpoint and credentials with values supplied by your provider. The endpoint is a complete URL, including its scheme. Some S3-compatible services require a particular region, signing configuration, addressing style, certificate setup, or authentication method. Check the provider’s current documentation rather than assuming AWS defaults apply. Boto3’s endpoint_url option is documented in the Session reference; its S3 examples warn that endpoint overrides can have unintended effects, including redirects.

Keep access keys out of source control, command histories, and logs. In deployed applications, use the secret-management mechanism available in your environment.

3. Capture PNG bytes and upload them

from io import BytesIO
import os

import boto3
from selenium import webdriver

endpoint_url = os.environ["S3_ENDPOINT_URL"]
bucket_name = os.environ["S3_BUCKET"]
access_key = os.environ["S3_ACCESS_KEY"]
secret_key = os.environ["S3_SECRET_KEY"]
region_name = os.getenv("S3_REGION", "us-east-1")

s3 = boto3.client(
    "s3",
    endpoint_url=endpoint_url,
    region_name=region_name,
    aws_access_key_id=access_key,
    aws_secret_access_key=secret_key,
)

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

    screenshot = BytesIO(driver.get_screenshot_as_png())
    screenshot.seek(0)

    s3.upload_fileobj(
        screenshot,
        bucket_name,
        "selenium/example.png",
        ExtraArgs={"ContentType": "image/png"},
    )
finally:
    driver.quit()

Replace the target URL and object key as needed. The bucket must already exist, and the configured identity must be authorized to write the key. upload_fileobj takes a binary file-like object, bucket name, and object key. Boto3 manages the transfer and may use multipart upload with multiple threads when needed.

Why seek(0) is included

A newly created BytesIO is positioned at the end of the byte sequence. Rewinding it with seek(0) makes the complete PNG available to the uploader from the start. This is especially important if you reuse or otherwise read from the stream before uploading.

Set useful object metadata

The example sets ContentType to image/png, so clients can identify the uploaded object as a PNG. If your application needs a cache policy or other metadata, add only values supported by your storage provider to ExtraArgs. Boto3 documents the accepted upload arguments in the upload_fileobj reference.

4. Choose an object key and handle repeated captures

The key selenium/example.png is a stable path. Uploading another screenshot to the same bucket and key may replace the previous object, depending on the service’s behavior and bucket configuration. If each capture should be retained, generate a unique key in your application, for example from a job identifier or timestamp. Avoid placing secrets or personal data in object names: keys can appear in logs and operational tools.

For parallel browser jobs, make each key unique or coordinate writes to a shared key. Reusing one BytesIO object across concurrent uploads can also cause stream-position and data-integrity problems; create a separate screenshot buffer for each capture.

5. Optional local-file fallback

If another step needs the image on disk, Selenium can save the PNG to a file and Boto3 can upload that filename with upload_file:

from selenium import webdriver
import boto3
import os

s3 = boto3.client(
    "s3",
    endpoint_url=os.environ["S3_ENDPOINT_URL"],
    region_name=os.getenv("S3_REGION", "us-east-1"),
    aws_access_key_id=os.environ["S3_ACCESS_KEY"],
    aws_secret_access_key=os.environ["S3_SECRET_KEY"],
)
bucket_name = os.environ["S3_BUCKET"]

 driver = webdriver.Chrome()
try:
    driver.get("https://example.com")
    saved = driver.save_screenshot("screenshot.png")
    if not saved:
        raise RuntimeError("Selenium could not save screenshot.png")
    s3.upload_file("screenshot.png", bucket_name, "selenium/example.png")
finally:
    driver.quit()

Remove the leading space before driver = webdriver.Chrome() if copying this snippet into a top-level Python file; the executable form is:

driver = webdriver.Chrome()

save_screenshot saves a PNG of the current window and returns false on an I/O error. Boto3’s upload_file accepts a filename, bucket, and key. See the Selenium API and Boto3 upload_file reference.

6. Or skip the browser setup

If you need a screenshot of a public page rather than a Selenium-controlled browser session, ScreenshotNeo can return an image or PDF with one GET request. See the ScreenshotNeo API documentation for the available parameters.

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,
)
r.raise_for_status()
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}`);
if (!res.ok) throw new Error(`Screenshot request failed: ${res.status}`);
const fs = await import('node:fs/promises');
await fs.writeFile('shot.webp', Buffer.from(await res.arrayBuffer()));

ScreenshotNeo accepts cookie or consent banners 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, failed loads, timeouts, and cache hits are not billed, and response headers report the page verdict and billing status. Its MCP server lets AI agents use take_screenshot, get_page_info, and capture_pdf. The free plan includes 1,000 screenshots a month with no card; paid plans start at $5 for 3,000 screenshots.

Create a free ScreenshotNeo account and start with 1,000 screenshots a month at no charge and no card.

7. Troubleshooting

Symptom Likely cause What to check or change
Missing environment variable error A required setting was not supplied to the process. Set S3_ENDPOINT_URL, S3_BUCKET, S3_ACCESS_KEY, and S3_SECRET_KEY in the environment where Python runs.
Access denied The credentials lack permission to write the bucket/key, or the bucket policy rejects the request. Check the principal, bucket policy, key prefix, and required write permissions with your provider.
Bucket not found The bucket name is misspelled, the bucket is in another account or region, or the endpoint routes requests differently. Confirm the bucket exists and that the endpoint and region match the provider’s instructions.
Redirect, signature, or region error The custom endpoint, signing region, addressing style, or TLS setup does not match provider requirements. Use the provider’s documented endpoint and signing settings. Boto3 notes custom endpoint overrides can result in redirects; inspect the actual response and provider guidance.
Uploaded object is empty or incomplete The stream was not rewound, or code modified/reused it before upload. Call screenshot.seek(0) immediately before upload and create a new buffer per screenshot.
Object downloads with the wrong type Metadata was omitted or set incorrectly. Pass ExtraArgs={"ContentType": "image/png"} for PNG bytes.
Browser fails before capture The browser or driver is unavailable, incompatible, or cannot start in the runtime. Check browser installation, Selenium setup, and runtime permissions; inspect the browser startup exception separately from the storage upload.
Screenshot shows an unexpected page state Capture happened before navigation or dynamic content finished rendering. Wait for the specific page condition your application needs before calling get_screenshot_as_png(); choose an explicit selector or application readiness signal where possible.

8. Performance, reliability, and cost

  • Memory: the in-memory approach avoids a temporary file, but the PNG still occupies memory until the buffer is released. Very tall pages or large viewports can produce large images. For unusually large captures or workflows that need a durable local artifact, consider the file-based path.
  • Transfer behavior: Boto3 manages upload_fileobj transfers and may use multipart uploads and multiple threads as needed. Keep the stream open until the call returns.
  • Browser time: navigation, dynamic page rendering, and browser startup often determine total job time. Wait for the required page state without using unnecessarily long fixed delays.
  • Retries: separate capture failures from upload failures. Retrying an upload with the same bytes and key is straightforward, but decide whether overwriting that key is acceptable. For reliable job processing, record the target key and job state so a retry does not silently create duplicates or replace a desired earlier capture.
  • Storage costs: the cited API documentation does not establish provider pricing. Check your chosen provider’s current charges for stored bytes, requests, transfer, and retention; this workflow itself has no universal price.
  • Security: use TLS endpoints, protect credentials, grant only required bucket access, and avoid logging secrets or sensitive screenshot contents.

9. Frequently asked questions

Does this capture the full page?

get_screenshot_as_png() returns a screenshot of the current browser window. If you need a full-page image, use a browser-specific or application-specific full-page capture method and verify its behavior for your browser; the basic example here captures the current window.

Can I upload JPEG bytes as a PNG?

No. The example uses Selenium’s PNG bytes and sets image/png. If you convert the image to another format, use the corresponding bytes, file extension, and content type.

Does every S3-compatible provider accept the same settings?

No universal compatibility settings are established by the cited documentation. Confirm endpoint, region/signing, TLS, authentication, and supported operations with your provider.