How to Save Selenium Screenshots to Amazon S3 from an Indian AWS Region
Capture Selenium screenshots as PNG files and upload them to an Indian S3 bucket with Python and Boto3, with region, IAM, and troubleshooting guidance.
Use Selenium’s save_screenshot() to write the current browser window to a PNG file, then upload that file with Boto3’s S3 client. Set the client region to the region where the destination bucket was created: Mumbai is ap-south-1 and Hyderabad is ap-south-2. The location of your Selenium runner does not determine the bucket’s region.
The example below assumes you already have a working Selenium driver, an existing S3 bucket, and AWS credentials configured for the process. It creates the local output directory, checks whether Selenium saved the file, and uses a run-specific S3 key to reduce accidental overwrites.
Complete Python example
from pathlib import Path
import boto3
from botocore.exceptions import BotoCoreError, ClientError
# Assumes `driver` is an already configured Selenium WebDriver.
run_id = "replace-with-test-run-id"
bucket = "your-existing-bucket"
region = "ap-south-1" # Mumbai; use ap-south-2 for Hyderabad if that is the bucket region.
test_name = "checkout"
local_path = Path("artifacts") / run_id / f"{test_name}.png"
object_key = f"selenium-screenshots/{run_id}/{test_name}.png"
local_path.parent.mkdir(parents=True, exist_ok=True)
try:
saved = driver.save_screenshot(str(local_path))
if not saved:
raise OSError(f"Selenium could not save screenshot to {local_path}")
s3 = boto3.client("s3", region_name=region)
s3.upload_file(
str(local_path),
bucket,
object_key,
ExtraArgs={"ContentType": "image/png"},
)
except (OSError, BotoCoreError, ClientError) as exc:
raise RuntimeError(
f"Screenshot capture or upload failed: local_path={local_path}, "
f"bucket={bucket}, key={object_key}, region={region}"
) from exc
print(f"Uploaded s3://{bucket}/{object_key}")
Install the Python dependencies in the environment running the test:
python -m pip install selenium boto3
The browser and its driver must also be installed and compatible with the Selenium setup. This article starts from an existing driver session because browser startup depends on the environment; the capture and upload portion above is directly reusable.
Choose and configure the Indian S3 region
AWS lists these Indian S3 regions:
| Region | Region identifier | Use when |
|---|---|---|
| Asia Pacific (Mumbai) | ap-south-1 |
The bucket was created in Mumbai or your data-location requirements specify it. |
| Asia Pacific (Hyderabad) | ap-south-2 |
The bucket was created in Hyderabad or your data-location requirements specify it. |
Use the bucket’s actual region in boto3.client("s3", region_name=...). A runner in India can upload to a bucket in another region, and an India-based runner can also upload to either Indian region. For this authenticated upload, use the regional S3 API; you do not need an S3 static website endpoint or public bucket. See AWS’s [S3 endpoints list](https://docs.aws.amazon.com/general/latest/gr/s3.html) and [S3 overview](https://docs.aws.amazon.com/AmazonS3/latest/userguide/Welcome.html).
Configure AWS credentials and permissions
Boto3 uses its credential provider chain, which can obtain credentials from the runtime environment or configured providers. On EC2, AWS recommends using an attached IAM role rather than putting long-lived access keys in source code. Use the appropriate role or credential provider for your runner, and do not print secrets in logs. See the [Boto3 credentials guide](https://docs.aws.amazon.com/boto3/latest/guide/credentials.html).
The identity used by the process needs permission to write the selected object key, subject to the bucket’s policy, encryption configuration, and any organizational controls. The exact least-privilege policy depends on those details; verify it against your bucket and prefix rather than copying an unverified broad policy.
S3 buckets and objects are private unless access is granted. Keep screenshots private unless sharing is an explicit requirement. AWS recommends Block Public Access controls, and new S3 uploads are encrypted at rest by default with SSE-S3. Encryption at rest does not replace access control or a retention policy. See AWS’s guidance on [blocking public access](https://docs.aws.amazon.com/AmazonS3/latest/userguide/access-control-block-public-access.html) and [SSE-S3](https://docs.aws.amazon.com/AmazonS3/latest/userguide/UsingServerSideEncryption.html).
Use a reliable object key and content type
An S3 object key is the object’s name within the bucket. A key such as selenium-screenshots/<run-id>/<test-name>.png makes test artifacts easier to find and avoids repeated runs writing to the same location. If you intentionally use the same key again, it points to the same object location and the upload can replace that object, depending on bucket versioning and policy.
Set ContentType to image/png for Selenium’s PNG output. This metadata helps clients interpret the object; it does not make the object public. Keep the extension and content type aligned with the actual image format.
Upload without a local screenshot file
If you already have screenshot bytes in memory, Boto3 also supports upload_fileobj. Pass a readable binary file-like object, such as BytesIO, opened in binary mode. For the ordinary Selenium workflow, saving to a local path and using upload_file is straightforward and leaves an artifact available for local inspection.
from io import BytesIO
png_bytes = driver.get_screenshot_as_png()
image_stream = BytesIO(png_bytes)
s3.upload_fileobj(
image_stream,
bucket,
object_key,
ExtraArgs={"ContentType": "image/png"},
)
Boto3 documents both [file uploads](https://docs.aws.amazon.com/boto3/latest/guide/s3-uploading-files.html) and the requirement that file objects be opened in binary mode.
cURL alternative for an existing local PNG
cURL does not capture the browser. It can upload a PNG that Selenium has already saved, provided you supply an S3-compatible signed request or use another authenticated signing workflow. Do not put AWS secret keys directly in a cURL command. For AWS authentication from application code, Boto3 is usually the simpler fit because it integrates with configured credential providers.
Operational notes: speed, reliability, and cost
- Region and network: Choose the bucket’s region based on data-location requirements and the existing bucket. The runner’s network path can affect upload time. The source material does not establish whether Mumbai or Hyderabad is faster for a particular runner; measure your deployed path if latency matters.
- Capture and upload failures are separate: Check Selenium’s boolean result and local filesystem errors first, then diagnose AWS exceptions. Keeping these stages distinct makes failed test artifacts easier to investigate.
- Parallel test runs: Include a unique run or job identifier in the key. Otherwise, concurrent executions using the same key may replace one another.
- Retries: If your test framework retries transient upload failures, preserve the intended key and ensure repeated uploads are acceptable for your bucket’s versioning and retention setup. Avoid retry loops that hide persistent permission or region errors.
- Storage and transfer costs: S3 charges depend on account, region, storage, requests, and data transfer. The provided sources do not establish a workload-specific estimate. Set a retention policy appropriate to test artifacts and consult current AWS pricing for your account and region.
- Screenshot sensitivity: Browser images may contain personal or production data. Restrict access, avoid logging image bytes or credentials, and delete artifacts when they are no longer needed.
Troubleshooting
| Symptom | Likely cause | What to check or fix |
|---|---|---|
save_screenshot() returns False |
The screenshot could not be written, often because the path or filesystem is unavailable. | Create the parent directory, use a full or valid path, ensure it ends in .png, and confirm the process can write there. |
| Local file is missing or zero bytes | Capture did not complete as expected, or the browser session is in an unexpected state. | Check the return value, browser/driver health, and the file before starting the S3 upload. |
| Bucket does not exist or redirect/region error | The configured client region does not match the bucket’s actual region, or the bucket name is wrong. | Inspect the bucket’s region and set region_name accordingly: ap-south-1 for Mumbai or ap-south-2 for Hyderabad. |
| Access denied | The active AWS identity or bucket policy does not allow the requested write. | Confirm which credentials Boto3 selected and review permissions for the bucket and key prefix, including encryption or organizational policy requirements. |
| Credentials not found | No usable credential provider is configured in the runner. | Configure the execution role or another supported provider for the environment. Do not hard-code long-lived keys in the test source. |
| Upload times out or has a network error | The runner cannot reach the S3 endpoint reliably, or the request encountered a transient network/service issue. | Check outbound network access, DNS, proxy/firewall configuration, endpoint region, and AWS response details. Add bounded retries only for transient errors. |
| Image downloads with unexpected type or handling | The uploaded object metadata does not match its contents. | For Selenium PNG files, use the .png extension and ContentType="image/png". |
| Objects overwrite each other | Repeated or parallel runs use the same object key. | Add a unique run ID, job ID, or timestamp component to the key. |
| Cannot view the object anonymously | The object is private by default, as intended. | Use authenticated access or a deliberate sharing mechanism. Do not make the entire bucket public just to inspect a test artifact. |
Or skip the browser setup
If you need a website screenshot rather than an artifact from an existing Selenium test, ScreenshotNeo provides a one-call screenshot API. It can return PNG, JPEG, WebP, or PDF; see the [ScreenshotNeo documentation](https://screenshotneo.com/docs/) for API options.
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}`);
await Bun.write("shot.webp", new Uint8Array(await res.arrayBuffer()));
ScreenshotNeo removes cookie banners, newsletter popups, and chat widgets before the shot. Bot checks, blank pages, and failed loads are never billed. Its MCP server lets AI agents use screenshot tools, and 1,000 screenshots a month are free with no card; paid plans start at $5 for 3,000. You can save the returned image locally and upload it to S3 with the same Boto3 flow above. [Learn about ScreenshotNeo](https://screenshotneo.com).
Sign up for ScreenshotNeo and get 1,000 free screenshots a month with no card.
FAQ
Does this capture the full webpage?
save_screenshot() captures the current browser window. The example does not implement full-page capture.
Should the Selenium runner and S3 bucket be in the same region?
No. Configure the Boto3 client for the bucket’s actual region. The runner’s location is a separate network and data-location consideration.
Does uploading with Boto3 make the screenshot public?
No. S3 objects are private unless access is granted by policy or configuration.
Can I use this with a bucket outside India?
Yes. Use that bucket’s actual AWS region in the client configuration and follow your organization’s data-location requirements.


