Headless Browser Screenshots: Best Practices and Tips
Practical guide to capturing consistent, high-quality screenshots with cloud browsers, covering timing, resolution, and format optimization.
Introduction
Screenshots are one of the most common outputs of browser automation: visual regression testing, content archiving, compliance monitoring, and report generation. Getting consistent, high-quality screenshots requires understanding timing, viewport configuration, and format trade-offs.
Timing: When to Capture
The most common screenshot problem is capturing too early, before the page has fully rendered.
Wait Strategies
// Wait for network to settle (no requests for 500ms)
await page.goto(url, { waitUntil: 'networkidle0' });
// Wait for a specific element
await page.waitForSelector('.content-loaded');
// Wait for fonts to load
await page.evaluate(() => document.fonts.ready);
// Wait for images to load
await page.evaluate(() => {
return Promise.all(
Array.from(document.images)
.filter(img => !img.complete)
.map(img => new Promise(resolve => {
img.onload = img.onerror = resolve;
}))
);
});
// Custom wait for SPA content
await page.waitForFunction(() => {
return document.querySelector('#app')?.__vue__?.isReady === true;
});
Combining Wait Strategies
For complex pages, combine multiple strategies:
await page.goto(url, { waitUntil: 'networkidle0' });
await page.waitForSelector('.main-content');
await page.evaluate(() => document.fonts.ready);
// Small additional delay for animations to complete
await page.evaluate(() => new Promise(r => setTimeout(r, 500)));
Viewport and Resolution
Setting Viewport
// Set viewport before navigation
await page.setViewport({
width: 1920,
height: 1080,
deviceScaleFactor: 2, // For retina-quality screenshots
});
await page.goto(url);
await page.screenshot({ path: 'screenshot.png' });
Full Page Screenshots
// Capture the entire scrollable page
await page.screenshot({
path: 'full-page.png',
fullPage: true,
});
Element Screenshots
// Capture a specific element
const element = await page.$('.hero-section');
await element.screenshot({ path: 'hero.png' });
Format Selection
PNG vs JPEG
| Factor | PNG | JPEG |
|---|---|---|
| Quality | Lossless | Lossy |
| File size | Larger (1-5 MB) | Smaller (100-500 KB) |
| Transparency | Supported | Not supported |
| Best for | UI testing, text-heavy | Photos, visual content |
// PNG (default) - best for visual comparison
await page.screenshot({ path: 'screenshot.png', type: 'png' });
// JPEG with quality control - best for archiving
await page.screenshot({
path: 'screenshot.jpg',
type: 'jpeg',
quality: 80,
});
WebP
// WebP - good balance of quality and size
await page.screenshot({
path: 'screenshot.webp',
type: 'webp',
quality: 85,
});
Device Pixel Ratio
Higher deviceScaleFactor produces sharper screenshots at the cost of larger file sizes:
// 1x - standard resolution (1920x1080 = ~2 MP)
await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 1 });
// 2x - retina resolution (3840x2160 = ~8 MP)
await page.setViewport({ width: 1920, height: 1080, deviceScaleFactor: 2 });
For visual regression testing, 2x provides better detail for detecting subtle rendering differences.
Batch Capture Pattern
For capturing screenshots of many pages efficiently:
async function captureScreenshot(browser, url, outputPath) {
const page = await browser.newPage();
try {
await page.setViewport({ width: 1920, height: 1080 });
await page.goto(url, {
waitUntil: 'networkidle0',
timeout: 30000,
});
await page.screenshot({ path: outputPath, fullPage: true });
} finally {
await page.close();
}
}
const browser = await puppeteer.connect({
browserWSEndpoint: 'wss://bots.win/ws?apiKey=YOUR_API_KEY',
});
const urls = ['https://example.com', 'https://example.com/about', ...];
for (const url of urls) {
const filename = url.replace(/[^a-zA-Z0-9]/g, '_') + '.png';
await captureScreenshot(browser, url, filename);
}
await browser.close();
Best Practices
- Always wait for content before capturing, not just DOMContentLoaded
- Set viewport before navigation for consistent layout
- Use PNG for testing, JPEG for archiving
- Consider deviceScaleFactor: 2 for high-quality output
- Set timeouts on all navigation and wait operations
- Close pages after capture to free memory