Playwright Screenshot API¶
Playwright is a browser automation service used by Aegis for visual monitoring, screenshots, and web scraping.
Overview¶
- Image:
ghcr.io/vlazic/playwright-screenshot-api:latest - Container:
aegis-playwright - Internal Port: 3000
- External Port: 3002 (mapped to host)
- URL:
http://playwright:3000(from scheduler container) - Network:
traefik_proxy
Architecture¶
┌───────────────────────────────────────┐
│ Aegis Scheduler │
│ │
│ aegis.monitor.scheduler │
│ ↓ │
│ Website monitoring jobs │
│ ↓ │
│ HTTP POST http://playwright:3000 │
└───────────────────────────────────────┘
↓
┌───────────────────────────────────────┐
│ Playwright Screenshot API │
│ │
│ - Launches headless Chromium │
│ - Renders page │
│ - Captures screenshot │
│ - Returns PNG/JPEG │
└───────────────────────────────────────┘
Configuration¶
Docker Compose¶
playwright:
image: ghcr.io/vlazic/playwright-screenshot-api:latest
container_name: aegis-playwright
ports:
- "3002:3000"
restart: unless-stopped
networks:
traefik_proxy:
healthcheck:
test: ["CMD", "wget", "-q", "-O", "-", "http://localhost:3000/health"]
interval: 30s
timeout: 10s
retries: 3
Environment Variables (Scheduler)¶
API Endpoints¶
POST /screenshot¶
Capture a screenshot of a URL.
Request:
curl -X POST http://localhost:3002/screenshot \
-H "Content-Type: application/json" \
-d '{
"url": "https://aegisagent.ai",
"width": 1920,
"height": 1080,
"fullPage": false,
"type": "png"
}'
Request Body:
{
"url": "https://example.com", // Required
"width": 1920, // Optional, default: 1920
"height": 1080, // Optional, default: 1080
"fullPage": false, // Optional, default: false
"type": "png", // Optional: "png" or "jpeg", default: "png"
"quality": 90, // Optional: 0-100 (JPEG only)
"waitFor": 0, // Optional: milliseconds to wait
"selector": null, // Optional: CSS selector to screenshot
"clip": { // Optional: specific region
"x": 0,
"y": 0,
"width": 800,
"height": 600
}
}
Response: Binary image data (PNG or JPEG)
Response Headers:
- Content-Type: image/png or image/jpeg
- Content-Length: <size>
GET /health¶
Health check endpoint.
Request:
Response:
Usage in Aegis¶
Website Monitoring¶
Location: /home/agent/projects/aegis-core/aegis/monitor/
Scheduled job captures screenshots of monitored websites:
import aiohttp
import asyncio
from pathlib import Path
async def capture_screenshot(url: str, output_path: Path):
"""Capture screenshot using Playwright API."""
playwright_url = os.getenv("PLAYWRIGHT_URL", "http://playwright:3000")
payload = {
"url": url,
"width": 1920,
"height": 1080,
"fullPage": True,
"type": "png"
}
async with aiohttp.ClientSession() as session:
async with session.post(
f"{playwright_url}/screenshot",
json=payload,
timeout=aiohttp.ClientTimeout(total=60)
) as resp:
if resp.status == 200:
image_data = await resp.read()
output_path.write_bytes(image_data)
return True
else:
error = await resp.text()
logger.error("screenshot_failed", url=url, status=resp.status, error=error)
return False
Screenshot Storage¶
Screenshots are stored in volume-mounted directory:
/home/agent/projects/aegis-core/data/monitor/screenshots/
├── aegisagent.ai_2026-01-25_12-00-00.png
├── intel.aegisagent.ai_2026-01-25_12-05-00.png
└── ...
Mounted in scheduler container:
Competitor Monitoring¶
Captures screenshots of competitor pages for change detection:
from aegis.monitor.competitor import CompetitorMonitor
monitor = CompetitorMonitor()
# Add competitor
await monitor.add_competitor(
name="Competitor Corp",
url="https://competitor.com/pricing"
)
# Capture and compare
result = await monitor.check_competitor("Competitor Corp")
if result["changed"]:
print(f"Change detected! Diff: {result['diff_percentage']}%")
# Screenshot saved to data/monitor/screenshots/
Browser Configuration¶
Default Settings¶
The Playwright Screenshot API launches Chromium with:
- Headless mode: Enabled (no GUI)
- Viewport: Configurable per request (default: 1920x1080)
- User agent: Chromium default
- JavaScript: Enabled
- Images: Enabled
- Cookies: Isolated per request
- Network conditions: Normal (not throttled)
Supported Features¶
- Full page screenshots: Scrolls and captures entire page
- Element screenshots: Target specific CSS selectors
- Custom viewports: Any resolution
- Wait conditions: Wait for network idle, specific selectors
- Authentication: Basic auth, cookies (if supported by API)
- Responsive testing: Change viewport size
Browser Engines¶
The API uses Chromium (not Firefox or WebKit). This provides: - Best compatibility with modern web standards - Consistent rendering across platforms - DevTools Protocol support
Performance¶
Typical Timings¶
- Simple page: 2-5 seconds
- Complex SPA: 5-10 seconds
- Full page screenshot: 10-15 seconds (depends on page length)
Resource Usage¶
- Memory: ~200-500 MB per browser instance
- CPU: Burst during page render, idle otherwise
- Disk: Minimal (no persistent cache)
Concurrency¶
The API handles multiple concurrent requests. Each request launches a separate browser context (isolated).
Recommended limits: - Max 5 concurrent screenshots - Timeout requests after 60 seconds
Error Handling¶
Common Errors¶
Connection refused:
- Check container is running:docker ps | grep playwright
- Check network connectivity: docker exec aegis-scheduler ping playwright
Timeout:
- Page took too long to load - IncreasewaitFor in request
- Check URL is accessible
Page load failed:
- DNS resolution failed - Check URL is valid - Check network can reach external internetBrowser crash:
- Check container memory limits - Restart container:docker restart aegis-playwright
Retry Logic¶
Implement retries with exponential backoff:
import asyncio
async def capture_with_retry(url: str, max_retries: int = 3):
for attempt in range(max_retries):
try:
return await capture_screenshot(url)
except Exception as e:
if attempt == max_retries - 1:
raise
wait_time = 2 ** attempt # 1s, 2s, 4s
logger.warning("screenshot_retry", url=url, attempt=attempt, wait=wait_time)
await asyncio.sleep(wait_time)
Monitoring¶
Health Check¶
Container includes built-in healthcheck:
Expected:
Logs¶
# View container logs
docker logs aegis-playwright -f
# Filter for errors
docker logs aegis-playwright 2>&1 | grep -i error
# Check recent requests
docker logs aegis-playwright --tail 100
Resource Usage¶
Monitor for: - Memory spikes (>1GB indicates issue) - CPU usage (sustained 100% indicates stuck render) - Network I/O (unexpected high traffic)
Security¶
Sandboxing¶
Chromium runs with sandbox enabled by default. This isolates browser processes from the host system.
Benefits: - Prevents malicious pages from escaping container - Limits damage from browser exploits - Resource isolation
Note: If sandbox fails to start, check kernel capabilities.
URL Validation¶
Always validate URLs before sending to Playwright:
from urllib.parse import urlparse
def is_safe_url(url: str) -> bool:
"""Validate URL is safe to screenshot."""
try:
parsed = urlparse(url)
# Allow only http/https
if parsed.scheme not in ("http", "https"):
return False
# Block localhost/private IPs (optional)
hostname = parsed.hostname
if hostname in ("localhost", "127.0.0.1", "0.0.0.0"):
return False
return True
except:
return False
Rate Limiting¶
Implement rate limits to prevent abuse:
from collections import defaultdict
import time
# Simple in-memory rate limiter
screenshot_counts = defaultdict(list)
def check_rate_limit(client_ip: str, limit: int = 10, window: int = 60):
"""Allow max 'limit' screenshots per 'window' seconds."""
now = time.time()
timestamps = screenshot_counts[client_ip]
# Remove old timestamps
timestamps[:] = [t for t in timestamps if now - t < window]
if len(timestamps) >= limit:
return False
timestamps.append(now)
return True
Development¶
Local Testing¶
# Start Playwright container
docker compose up -d playwright
# Test screenshot endpoint
curl -X POST http://localhost:3002/screenshot \
-H "Content-Type: application/json" \
-d '{"url": "https://example.com", "fullPage": true}' \
-o test.png
# View screenshot
open test.png # macOS
xdg-open test.png # Linux
Custom Playwright Script¶
For advanced use cases, run custom Playwright scripts:
from playwright.async_api import async_playwright
async def custom_screenshot():
async with async_playwright() as p:
browser = await p.chromium.launch(headless=True)
page = await browser.new_page(viewport={"width": 1920, "height": 1080})
await page.goto("https://example.com")
# Wait for specific element
await page.wait_for_selector(".main-content")
# Capture screenshot
await page.screenshot(path="custom.png", full_page=True)
await browser.close()
Note: This requires Playwright Python library installed in your environment.
Debugging¶
Enable verbose logging:
# Run container with debug logs
docker run --rm \
-e DEBUG=pw:api \
-p 3000:3000 \
ghcr.io/vlazic/playwright-screenshot-api:latest
Troubleshooting¶
Container won't start¶
Check logs:
Common issues: - Port 3000 already in use - Insufficient memory - Kernel missing capabilities for Chromium sandbox
Screenshots are blank¶
Possible causes:
- Page requires JavaScript (check waitFor delay)
- Page blocks headless browsers
- Page uses unusual viewport detection
Solutions:
- Increase waitFor milliseconds
- Use waitForSelector for specific element
- Try different viewport size
Memory leaks¶
If container memory grows over time:
-
Check for stuck browser processes:
-
Restart container:
-
Add memory limit:
Related Documentation¶
- scheduler.md - Runs scheduled screenshot jobs
- dashboard.md - May serve screenshot viewer UI