Generic screenshot capture utilities (models, services, CLI helpers).
Project description
infra-screenshot
infra-screenshot provides reusable models, services, and CLI helpers for capturing website screenshots. It exposes the core abstractions.
For a task-oriented index, see CAPABILITIES.md or inspect screenshot.capabilities from an installed environment. The installed wheel exposes screenshot.capabilities; the source tree and source distribution also include AGENTS.md, examples/, agent-snippets/, and skills/.
Table of Contents
- infra-screenshot
Features
- 🎭 Multiple backends: Support for both Playwright and Selenium
- 📸 Flexible capture: Single screenshots or batch processing
- 🔧 Configurable viewports: Desktop, mobile, and custom viewport sizes
- 💾 Storage abstractions: Local filesystem or cloud storage backends
- 🚀 Async/await support: Modern async architecture for better performance
- 🛠️ CLI tools: Ready-to-use command-line interface
- 🔄 Retry logic: Built-in retry with exponential backoff for reliability
- 🎨 Visual cleanup: Auto-hide overlays, disable animations for cleaner screenshots
Installation
Using uv (Recommended)
# Install with Playwright backend (includes bundled Chromium)
uv pip install "infra-screenshot[playwright]"
uv run playwright install chromium
# Add GIF motion snapshot support when needed
uv pip install "infra-screenshot[playwright,motion]"
# OR install with Selenium backend (requires system Chrome)
uv pip install "infra-screenshot[selenium]"
Using pip
# Install with Playwright backend
pip install "infra-screenshot[playwright]"
playwright install chromium
# Add GIF motion snapshot support when needed
pip install "infra-screenshot[playwright,motion]"
# OR install with Selenium backend
pip install "infra-screenshot[selenium]"
Quick Verification
# Check installation
screenshot local -h
# Test capture
screenshot local --urls https://example.com --output-dir ./test-screenshots
Usage
CLI: Local Screenshot Capture
The CLI provides a local subcommand for capturing screenshots locally.
Basic Examples
# Capture a single URL
screenshot local --urls https://example.com --output-dir ./screenshots
# Capture multiple URLs (repeat the --urls flag for each URL)
screenshot local \
--urls https://example.com \
--urls https://github.com \
--output-dir ./screenshots
# For many URLs, use a JSONL input file (recommended)
screenshot local --input urls.jsonl --output-dir ./screenshots
# Capture with custom settings
screenshot local \
--urls http://localhost:3000 \
--output-dir ./screenshots \
--viewports desktop mobile \
--depth 0 \
--scroll false \
--allow-autoplay true
Input File Format (JSONL)
For batch processing, create a file with one JSON object per line:
{"url": "https://example.com", "job_id": "example"}
{"url": "https://github.com", "job_id": "github"}
{"url": "https://docs.python.org", "job_id": "python-docs"}
Then run:
screenshot local --input urls.jsonl --output-dir ./screenshots
Each non-empty JSONL line must be an object with a non-empty url. Invalid records fail fast with a file-and-line validation error.
Each record can also include optional job_id, backend, metadata, nested options, and nested artifacts fields. For a richer template using screenshot_options/v2 plus optional HTML/GIF artifacts, see examples/cli_jsonl_batch_capture.py.
Motion artifacts are configured through JSONL artifacts payloads or the programmatic API rather than direct --motion-* CLI flags.
Common Options
| Option | Description | Default |
|---|---|---|
--viewports |
Viewport presets (desktop, mobile, tablet) | desktop |
--depth |
Link depth to follow (0 = single page only) | 0 |
--scroll |
Enable scrolling before capture | false |
--scroll-step-delay-ms |
Delay between scroll steps (ms) | 250 |
--max-scroll-steps |
Max scroll iterations | 15 |
--full-page |
Capture entire page height (not just viewport) | false |
--timeout-s |
Page load timeout in seconds | 60 |
--post-nav-wait-s |
Wait after navigation (settling time) | 2.5 |
--pre-capture-wait-s |
Wait before screenshot | 1.5 |
--hide-overlays |
Auto-hide popups/cookie banners | true |
--disable-animations |
Disable CSS animations for cleaner shots | true |
--allow-autoplay |
Allow media autoplay | true |
--mute-media |
Mute audio/video | true |
--block-media |
Block video/audio requests | false |
--site-concurrency |
Number of sites to capture in parallel | 1 |
--max-viewport-concurrency |
Number of viewports captured in parallel per page | 1 |
--max-pages |
Max pages per site (when following links) | 1 |
See all options:
screenshot local -h
The CLI exits non-zero when any job is partial or failed. Partial jobs keep any artifacts already written and are reported in the JSON summary with outcome: "partial".
Viewport preset typos now fail fast instead of falling back silently, and Playwright honors --post-nav-wait-s exactly without adding hidden settle waits.
If you are upgrading existing consumers, see docs/contract-hardening-migration.md.
Real-World Examples
Capture homepage only (no scrolling, viewport-only):
screenshot local \
--urls http://localhost:3000 \
--output-dir ./tmp \
--depth 0 \
--scroll false \
--full-page false
Full-page screenshot with scrolling:
screenshot local \
--urls http://localhost:3000 \
--output-dir ./tmp \
--depth 0 \
--scroll true \
--full-page true
Capture multiple viewports:
screenshot local \
--urls https://example.com \
--output-dir ./screenshots \
--viewports desktop mobile tablet
Python API: Programmatic Usage
For integration into your own tooling, call the async runner directly with a configured
ScreenshotOptions payload:
from pathlib import Path
import asyncio
from screenshot import ScreenshotCaptureOutcome, ScreenshotOptions, capture_screenshots_async
from screenshot.models import CaptureOptions
async def capture_example() -> None:
options = ScreenshotOptions(
capture=CaptureOptions(
enabled=True,
viewports=("desktop",),
depth=0,
scroll=False,
)
)
result = await capture_screenshots_async(
"demo-job",
"https://example.com",
store_dir=Path("screenshots"),
partition_date=None,
options=options,
)
if result.outcome is ScreenshotCaptureOutcome.SUCCESS:
print(f"Captured {result.captured} screenshot(s)")
elif result.outcome is ScreenshotCaptureOutcome.PARTIAL:
print(f"Partially captured {result.captured} screenshot(s)")
for error in result.errors:
print(f"Partial failure: {error.message}")
else:
for error in result.errors:
print(f"Capture failed: {error.message}")
asyncio.run(capture_example())
For auxiliary HTML/GIF artifacts, pass artifacts=ScreenshotArtifactsRequest(...) to
capture_screenshots_async(). See
examples/custom_viewport_and_html_snapshot.py
and examples/playwright_motion_snapshot.py.
For complete runnable scripts covering CLI batches, Selenium, ScreenshotService,
and custom viewport flows, see the repository examples below.
Repository Examples
The repository ships copyable examples for both CLI and programmatic usage:
- examples/cli_jsonl_batch_capture.py generates a nested JSONL batch file and prints a ready-to-run
screenshot localcommand. - examples/playwright_programmatic_capture.py shows the basic Playwright async API.
- examples/playwright_motion_snapshot.py shows Playwright-only GIF motion capture via
ScreenshotArtifactsRequest. - examples/playwright_scrolling_depth_capture.py shows scrolling, full-page capture, and limited link following with
depthplusmax_pages. - examples/custom_viewport_and_html_snapshot.py shows validated custom viewport input plus HTML snapshot output.
- examples/selenium_programmatic_capture.py shows the equivalent Selenium programmatic flow.
- examples/service_batch_capture.py shows multi-job execution through
ScreenshotService. - examples/storage_summary_integration.py shows batch-summary processing and storage-oriented integration patterns.
Browser Setup
Playwright: Bundled Chromium vs System Chrome
By default, Playwright uses its own bundled Chromium (installed via playwright install chromium). This provides:
- ✅ Reproducibility: Known browser version across environments
- ✅ No system dependencies: Works in containers/CI without system Chrome
- ✅ Headless-first design: Optimized for automation
When to use system Chrome (--playwright-executable-path):
- 🎯 Testing against real Chrome (not Chromium)
- 🎯 Using Chrome extensions or enterprise policies
- 🎯 Matching end-user browser versions exactly
- 🎯 Debugging with Chrome DevTools locally
Trade-offs:
| Aspect | Bundled Chromium | System Chrome |
|---|---|---|
| Setup | playwright install chromium |
Install Chrome + ensure compatibility |
| Version control | Pinned to Playwright release | Depends on system updates |
| Size | ~300MB download | Already on system |
| Reproducibility | ✅ High (version-locked) | ⚠️ Lower (varies by system) |
| Extensions | ❌ Not supported | ✅ Supported |
| DevTools | Limited | Full local debugging |
Usage example with system Chrome:
screenshot local \
--urls https://example.com \
--output-dir ./screenshots \
--playwright-executable-path /usr/bin/google-chrome-stable
Need a deeper comparison? Check the repository's
.dev_docs/playwright_vs_selenium_linux.mdfor codec/DRM support, driver management, and when to switch to system Chrome.
Finding Chrome path:
# Linux/WSL
which google-chrome-stable # Usually /usr/bin/google-chrome-stable
which chromium-browser # Usually /usr/bin/chromium-browser
# macOS
/Applications/Google\ Chrome.app/Contents/MacOS/Google\ Chrome
# Windows (WSL path)
/mnt/c/Program\ Files/Google/Chrome/Application/chrome.exe
If the path is invalid, the tool logs a warning and falls back to bundled Chromium automatically.
Installing System Chrome/Chromium
For Playwright (Optional - only if using system Chrome)
# Google Chrome (stable) - Linux/WSL
wget -O /tmp/chrome.deb https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb
sudo apt install -y /tmp/chrome.deb
# OR Chromium (from distro packages)
sudo apt-get update && sudo apt-get install -y chromium-browser fonts-liberation
For Selenium (Required)
Selenium requires a system Chrome/Chromium install. On modern Selenium 4 setups,
Selenium Manager will usually locate or download a matching driver
automatically, so webdriver-manager is not required for the normal case.
# Install Chrome/Chromium (as above)
If your environment cannot use Selenium Manager, install a compatible
chromedriver manually and make it available on PATH. Advanced programmatic
callers can also provide an explicit driver path via runner options.
Configuration
Environment Variables
Runtime behavior can be customized via environment variables:
| Variable | Description | Default |
|---|---|---|
SCREENSHOT_SCROLL_STEP_DELAY_MS |
Delay between scroll steps (ms) | 250 |
SCREENSHOT_MAX_SCROLL_STEPS |
Maximum scroll iterations | 15 |
PLAYWRIGHT_CAPTURE_MAX_ATTEMPTS |
Retry attempts for failed captures | 3 |
SCREENSHOT_RETRY_BACKOFF_S |
Initial retry delay (seconds) | 0.5 |
SCREENSHOT_RETRY_MAX_BACKOFF_S |
Maximum retry delay (seconds) | 5.0 |
SCREENSHOT_ENABLE_TIMING |
Enable additional timing/performance metrics during capture (see docs/perf-testing.md) |
See docs/perf-testing.md |
Example:
export SCREENSHOT_SCROLL_STEP_DELAY_MS=200
export PLAYWRIGHT_CAPTURE_MAX_ATTEMPTS=5
screenshot local --urls https://example.com --output-dir ./screenshots
Logging
infra-screenshot uses Python's standard logging module. Enable diagnostics in your application or CLI runs with:
import logging
logging.basicConfig(level=logging.INFO)
logging.getLogger("screenshot.playwright_runner").setLevel(logging.DEBUG)
Logger namespaces:
| Logger | Purpose |
|---|---|
screenshot.playwright_runner |
Playwright capture + upload lifecycle |
screenshot.selenium_runner |
Selenium fallback pipeline |
screenshot.cli |
CLI orchestration and batch processing |
Log records include structured extra={...} fields such as job_id, url, and viewport. URLs are sanitized before logging to prevent leaking SAS tokens or credentials; configure your formatter (JSON/text) to emit those keys for easier filtering.
OpenTelemetry correlation
When using OpenTelemetry, attach trace/span IDs to screenshot logs so traces and logs stay aligned:
import logging
from pathlib import Path
from opentelemetry import trace
from screenshot import capture_screenshots_async
tracer = trace.get_tracer(__name__)
url = "https://example.com/products"
job_id = "otel-demo"
with tracer.start_as_current_span("screenshot-job") as span:
logger = logging.getLogger("screenshot.playwright_runner")
logger.info(
"Starting screenshot job",
extra={
"job_id": job_id,
"trace_id": span.get_span_context().trace_id,
"span_id": span.get_span_context().span_id,
},
)
options = ... # Build ScreenshotOptions as shown above
await capture_screenshots_async(
job_id,
url,
store_dir=Path("/tmp/screens"),
partition_date=None,
options=options,
)
Contributing
We welcome contributions! To get started with development:
- Read the contributing guide: CONTRIBUTING.md
- Set up your development environment (covered in CONTRIBUTING.md)
- Run tests and linters before submitting PRs
For bug reports and feature requests, please open an issue.
License
This project is dual-licensed:
AGPL-3.0 (Open Source)
Free for open-source and non-commercial use under the GNU Affero General Public License v3.0.
Key requirement: If you run this software as a service (SaaS, API, web app), you must make your complete source code available under AGPL-3.0.
Commercial License
For commercial use without AGPL obligations (proprietary products, SaaS without open-sourcing, etc.).
See LICENSE for full details.
Need help? Check out:
- Documentation - Configuration reference and migration guides
- Chromium Compatibility Levels - Understanding browser options
- Architecture - Internal design and models layer
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file infra_screenshot-0.3.0.tar.gz.
File metadata
- Download URL: infra_screenshot-0.3.0.tar.gz
- Upload date:
- Size: 118.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
e7a63368331252b6e9aab9895ac65f29836c952c8cef6f10f05e4c528b76cc08
|
|
| MD5 |
2bd9d79879784aabbf9fcc92b3a96f7c
|
|
| BLAKE2b-256 |
64271be57923820b2b943f1feb61b2d3efebc1f4e357048af6e2d35bdcf6a7ed
|
Provenance
The following attestation bundles were made for infra_screenshot-0.3.0.tar.gz:
Publisher:
publish.yml on pj-ms/infra-screenshot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
infra_screenshot-0.3.0.tar.gz -
Subject digest:
e7a63368331252b6e9aab9895ac65f29836c952c8cef6f10f05e4c528b76cc08 - Sigstore transparency entry: 1109238301
- Sigstore integration time:
-
Permalink:
pj-ms/infra-screenshot@72fd12f38296bc57e4dc65109696099355c22d82 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/pj-ms
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@72fd12f38296bc57e4dc65109696099355c22d82 -
Trigger Event:
push
-
Statement type:
File details
Details for the file infra_screenshot-0.3.0-py3-none-any.whl.
File metadata
- Download URL: infra_screenshot-0.3.0-py3-none-any.whl
- Upload date:
- Size: 75.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
40b9fb0577c3dd6f90dbf6a54aea76d909b8f4e67fc093ef66b90fb43d0fdb45
|
|
| MD5 |
bf2b4113bd21bc45b97d2ce9b2ba661a
|
|
| BLAKE2b-256 |
7eb63965fee8f2bb5201f0fd05f949853d3459a2273b551589e4dc25cd956280
|
Provenance
The following attestation bundles were made for infra_screenshot-0.3.0-py3-none-any.whl:
Publisher:
publish.yml on pj-ms/infra-screenshot
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
infra_screenshot-0.3.0-py3-none-any.whl -
Subject digest:
40b9fb0577c3dd6f90dbf6a54aea76d909b8f4e67fc093ef66b90fb43d0fdb45 - Sigstore transparency entry: 1109238306
- Sigstore integration time:
-
Permalink:
pj-ms/infra-screenshot@72fd12f38296bc57e4dc65109696099355c22d82 -
Branch / Tag:
refs/tags/v0.3.0 - Owner: https://github.com/pj-ms
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@72fd12f38296bc57e4dc65109696099355c22d82 -
Trigger Event:
push
-
Statement type: