The Complete Guide to Playwright Screenshots: From Basic Snaps to Complex Automation
1/27/2025
Hey There, Screenshot Enthusiasts! 👋
Laura and Heidi here! After spending way too much time wrestling with browser automation (we built SCRNIFY, remember?), we thought we'd share a complete guide on using Playwright for screenshots. Whether you're just starting or looking to level up your automation game, this guide's got you covered.
Getting Started: The Basics
First things first - let's get Playwright installed. Open your terminal and type:
npm init -y
npm install playwright
That's it! No really, that's all you need to start. Let's write our first screenshot code:
// screenshot.js
const { chromium } = require('playwright');
async function takeBasicScreenshot() {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://news.ycombinator.com');
await page.screenshot({ path: 'screenshot.png' });
await browser.close();
}
takeBasicScreenshot();
Now run it!
node screenshot.js
Let's break this down:
chromium.launch()
starts a new browser instancebrowser.newPage()
opens a new tabpage.goto()
navigates to our URLpage.screenshot()
captures what we seebrowser.close()
cleans up after we're done
Making It Better: Screenshot Types
That basic example works, but let's add some options. Playwright gives us three main ways to capture screenshots:
// Different types of screenshots
async function takeVariousScreenshots() {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://news.ycombinator.com');
// Full page screenshot
await page.screenshot({
path: 'full-page.png',
fullPage: true
});
// Viewport only
await page.screenshot({
path: 'viewport.png'
});
// Specific element
const element = await page.locator('.hnname');
await element.screenshot({
path: 'element.png'
});
await browser.close();
}
takeVariousScreenshots();
node screenshot.js
Format Matters: JPEG, PNG, or WebP?
Different scenarios need different formats. Here's how to handle each:
async function differentFormats() {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://news.ycombinator.com');
// JPEG - Great for photos, smaller file size
await page.screenshot({
path: 'screenshot.jpg',
type: 'jpeg',
quality: 80 // 0-100, lower = smaller file
});
// PNG - Perfect for screenshots with text
await page.screenshot({
path: 'screenshot.png',
type: 'png'
});
// WebP - Modern format with good compression
// currently missing https://github.com/microsoft/playwright/issues/22984
// await page.screenshot({
// path: 'screenshot.webp',
// type: 'webp',
// });
await browser.close();
}
differentFormats();
Pro tip: Use JPEG for images with lots of colors, PNG for text-heavy screenshots, and WebP when you need both quality and small size.
Advanced Configuration: Making It Production-Ready
Let's step up our game with some advanced configurations. First, let's create a reusable config:
// config.js
const VIEWPORT_SIZES = {
mobile: { width: 375, height: 812 },
tablet: { width: 768, height: 1024 },
desktop: { width: 1920, height: 1080 }
};
const SCREENSHOT_TYPES = {
fullPage: {
fullPage: true,
type: 'png'
},
viewport: {
fullPage: false,
type: 'jpeg',
quality: 80
},
element: {
type: 'png',
omitBackground: true
}
};
const TIMEOUTS = {
navigation: 30000,
waitForElement: 5000,
screenshot: 10000
};
module.exports = {
VIEWPORT_SIZES,
SCREENSHOT_TYPES,
TIMEOUTS
};
Now let's use these configurations in a more robust setup:
// screenshot-service.js
const { chromium } = require('playwright');
const { VIEWPORT_SIZES, SCREENSHOT_TYPES, TIMEOUTS } = require('./config');
class ScreenshotService {
constructor(options = {}) {
this.browser = null;
this.context = null;
this.headless = options.headless !== false;
}
async initialize() {
this.browser = await chromium.launch({
headless: this.headless
});
this.context = await this.browser.newContext({
viewport: VIEWPORT_SIZES.desktop
});
}
async takeScreenshot(url, options = {}) {
if (!this.browser) {
await this.initialize();
}
const page = await this.context.newPage();
try {
await page.goto(url, {
timeout: TIMEOUTS.navigation,
waitUntil: 'networkidle'
});
const screenshotConfig = {
...SCREENSHOT_TYPES[options.type || 'fullPage'],
path: options.path || `screenshot-${Date.now()}.png`
};
await page.screenshot(screenshotConfig);
} finally {
await page.close();
}
}
async close() {
if (this.browser) {
await this.browser.close();
this.browser = null;
}
}
}
Handling Authentication
Most real-world scenarios require authentication. Here's how to handle it:
async function screenshotWithAuth() {
const browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
// Navigate to login page
await page.goto('https://example.com/login');
// Fill in login form
await page.fill('input[name="username"]', 'myuser');
await page.fill('input[name="password"]', 'mypassword');
await page.click('button[type="submit"]');
// Wait for navigation
await page.waitForNavigation();
// Store authentication state
await context.storageState({ path: 'auth.json' });
// Now take screenshots while authenticated
await page.goto('https://example.com/dashboard');
await page.screenshot({ path: 'authenticated-page.png' });
await browser.close();
}
Pro tip: You can reuse the authentication state:
async function reuseAuth() {
const browser = await chromium.launch();
const context = await browser.newContext({
storageState: 'auth.json'
});
// Now you're already authenticated!
const page = await context.newPage();
await page.goto('https://example.com/dashboard');
await page.screenshot({ path: 'still-authenticated.png' });
}
Handling Dynamic Content
Modern websites are tricky - content loads dynamically, and we need to wait for the right moment:
async function handleDynamicContent() {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://twitter.com/playwright', {
waitUntil: 'networkidle'
});
// Wait for specific content
await page.waitForSelector('.tweet-content', {
state: 'visible',
timeout: 10000
});
// Wait for images to load
await page.waitForLoadState('domcontentloaded');
// Optional: wait a bit more for animations
await page.waitForTimeout(1000);
await page.screenshot({
path: 'dynamic-content.png',
fullPage: true
});
await browser.close();
}
Common Challenges and Solutions
Handling Cookie Banners
async function handleCookieBanner() {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
// Check if cookie banner exists and click accept
const cookieBanner = await page.locator('.cookie-banner');
if (await cookieBanner.isVisible()) {
await page.click('.accept-cookies');
// Wait for banner to disappear
await cookieBanner.waitFor({ state: 'hidden' });
}
await page.screenshot({ path: 'no-cookie-banner.png' });
await browser.close();
}
Dealing with Lazy Loading
async function handleLazyLoading() {
const browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://infinite-scroll-example.com');
// Scroll to load more content
await page.evaluate(() => {
window.scrollTo(0, document.body.scrollHeight);
});
// Wait for new content
await page.waitForSelector('.lazy-loaded-content');
await page.screenshot({
path: 'fully-loaded.png',
fullPage: true
});
await browser.close();
}
Best Practices
1. Resource Management
Always clean up your browser instances:
async function properResourceManagement() {
let browser = null;
try {
browser = await chromium.launch();
const page = await browser.newPage();
await page.goto('https://example.com');
await page.screenshot({ path: 'screenshot.png' });
} catch (error) {
console.error('Screenshot failed:', error);
} finally {
if (browser) {
await browser.close();
}
}
}
2. Error Handling
async function robustErrorHandling() {
const browser = await chromium.launch();
const page = await browser.newPage();
try {
await page.goto('https://example.com', {
timeout: 30000,
waitUntil: 'networkidle'
});
} catch (error) {
if (error.name === 'TimeoutError') {
console.error('Page load timed out');
// Maybe try again or use a different strategy
}
throw error;
}
try {
await page.screenshot({ path: 'screenshot.png' });
} catch (error) {
console.error('Screenshot failed:', error);
// Handle screenshot failure
}
await browser.close();
}
Debugging Tips
Visual Debugging
async function debugWithVisuals() {
// Launch in headed mode
const browser = await chromium.launch({ headless: false, slowMo: 1000 });
const page = await browser.newPage();
// Enable debugging
page.on('console', msg => console.log('Browser console:', msg.text()));
await page.goto('https://example.com');
// Pause for inspection
await page.pause();
await page.screenshot({ path: 'debug.png' });
await browser.close();
}
Quick Reference
Common Commands
// Basic screenshot
await page.screenshot({ path: 'basic.png' });
// Full page
await page.screenshot({ path: 'full.png', fullPage: true });
// Element screenshot
await page.locator('.element').screenshot({ path: 'element.png' });
// Custom viewport
await page.setViewportSize({ width: 1920, height: 1080 });
// Wait for element
await page.waitForSelector('.content');
// Wait for network
await page.waitForLoadState('networkidle');
Common Issues and Solutions
- White/Blank Screenshots
// Wait for content to be visible
await page.waitForSelector('.content', { state: 'visible' });
- Missing Dynamic Content
// Wait for network idle
await page.goto(url, { waitUntil: 'networkidle' });
- Authentication Issues
// Save auth state
await context.storageState({ path: 'auth.json' });
// Reuse auth state
const context = await browser.newContext({
storageState: 'auth.json'
});
Security Considerations
- Never store sensitive credentials in code
- Use environment variables for auth data
- Clear stored auth states after use
- Be careful with screenshot storage locations
- Consider implementing rate limiting
Further Reading and Resources
Official Resources:
- Playwright Official Documentation
- Playwright Screenshot API Reference
- Playwright Best Practices Guide
Authentication & Testing:
Advanced Topics:
- Handling Dynamic Content in Playwright
- Network Interception & Mocking
- Playwright Debug Tools
- Playwright Trace Viewer
Community Resources:
Cheers! 🍻
That's it! You're now equipped to handle pretty much any screenshot scenario with Playwright. Remember, while building your own solution can be fun (and educational), there's no shame in using existing services (like SCRNIFY 😉) for production needs.
Happy capturing!
Laura & Heidi from Numero33 🇦🇹