Using Playwright for Advanced Screenshot Workflows
4/8/2025
Hey there! Laura and Heidi here from SCRNIFY! 👋
When it comes to capturing screenshots of authenticated content or after complex interactions, Playwright provides powerful capabilities that make it the perfect tool for the job. In this tutorial, we'll show you how to set up and use Playwright for advanced screenshot workflows, focusing on real-world scenarios like capturing authenticated pages, handling dynamic content, and testing responsive designs.
Get free access to the SCRNIFY API during our open beta!
1. Introduction: What Means Advanced Use Cases with Playwright
When we talk about "advanced use cases" for screenshots with Playwright, we're referring to scenarios that go beyond simply capturing what's visible on a public page. These include:
- Authentication workflows: Capturing screenshots of content that requires login
- Complex interactions: Taking screenshots after filling forms, clicking buttons, or navigating multi-step processes
- Dynamic content handling: Waiting for specific elements or state changes before capturing
- Local development testing: Capturing screenshots during local development
- CI/CD integration: Automating screenshot capture in your continuous integration pipeline
- Responsive testing: Capturing the same page across multiple device sizes
Playwright excels at all of these scenarios thanks to its powerful automation capabilities and built-in screenshot functionality.
2. Prerequisites: Getting Your Environment Ready
Before we dive in, make sure you have:
- Node.js installed (v14 or newer recommended)
- Basic understanding of JavaScript/Node.js and async programming
Let's make sure we have everything we need by checking our Node.js version:
node --version
If you don't have Node.js installed or need to update it, visit nodejs.org to download the latest version.
3. Setting Up a Project: Technical Details
Let's start by creating a new project directory and initializing it:
mkdir playwright-screenshot-demo
cd playwright-screenshot-demo
npm init -y
Now, let's install Playwright. We'll use the official Playwright test framework which provides a robust foundation for our automation:
npm init playwright@latest
During the installation, you'll be prompted with several options:
- Choose JavaScript or TypeScript (we'll use JavaScript for this tutorial)
- Choose the location for your tests (accept the default)
- Add GitHub Actions workflow (optional)
- Install Playwright browsers (select yes)
Once the installation completes, you'll have a basic Playwright project structure with a playwright.config.js
file and a tests
directory.
Let's also create a few additional directories and files for our examples:
mkdir -p src/utils
mkdir -p src/screenshots
touch src/utils/playwright-helpers.js
touch src/capture-screenshots.js
4. Local Setup: Configuring Your Playwright Environment
Let's create some helper functions to make our screenshot workflows more organized. Open src/utils/playwright-helpers.js
and add:
// src/utils/playwright-helpers.js
const fs = require('fs').promises;
const path = require('path');
/**
* Performs authentication on a website using Playwright
* @param {import('playwright').Page} page - Playwright page object
* @param {Object} authConfig - Authentication configuration
*/
async function authenticateWebsite(page, authConfig) {
const { url, username, password, usernameSelector, passwordSelector, submitSelector } = authConfig;
console.log(`🔑 Authenticating to ${url}...`);
// Navigate to the login page
await page.goto(url);
// Fill in the login form
await page.fill(usernameSelector, username);
await page.fill(passwordSelector, password);
// Submit the form
await page.click(submitSelector);
// Wait for navigation to complete
await page.waitForLoadState('networkidle');
console.log(`✅ Authentication successful`);
}
/**
* Saves a screenshot buffer to a file
* @param {Buffer} buffer - The screenshot buffer
* @param {string} filename - The filename to save as
* @param {string} directory - The directory to save in
*/
async function saveScreenshot(buffer, filename, directory = 'src/screenshots') {
// Ensure the directory exists
await fs.mkdir(directory, { recursive: true });
const filePath = path.join(directory, filename);
await fs.writeFile(filePath, buffer);
console.log(`✅ Screenshot saved to ${filePath}`);
return filePath;
}
/**
* Takes a screenshot of an element
* @param {import('playwright').Page} page - Playwright page object
* @param {string} selector - CSS selector for the element
* @param {string} filename - The filename to save as
* @param {string} directory - The directory to save in
*/
async function screenshotElement(page, selector, filename, directory = 'src/screenshots') {
console.log(`📸 Taking screenshot of element "${selector}"...`);
// Find the element
const element = await page.$(selector);
if (!element) {
throw new Error(`Element not found: ${selector}`);
}
// Take the screenshot
const buffer = await element.screenshot();
// Save the screenshot
return saveScreenshot(buffer, filename, directory);
}
module.exports = {
authenticateWebsite,
saveScreenshot,
screenshotElement
};
5. Creating Advanced Screenshot Workflows with Playwright
Now let's create several examples of advanced screenshot workflows using Playwright.
5.1 Capturing Screenshots of Authenticated Content
Create a new file src/authenticated-screenshots.js
:
// src/authenticated-screenshots.js
const { chromium } = require('playwright');
const { authenticateWebsite, saveScreenshot } = require('./utils/playwright-helpers');
const fs = require('fs').promises;
const path = require('path');
async function captureAuthenticatedScreenshots() {
// Launch a browser
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
try {
// Step 1: Authenticate with the website
const authConfig = {
url: 'https://example.com/login', // Replace with actual login URL
username: process.env.WEBSITE_USERNAME || 'demo',
password: process.env.WEBSITE_PASSWORD || 'demo',
usernameSelector: '#username', // Replace with actual selector
passwordSelector: '#password', // Replace with actual selector
submitSelector: 'button[type="submit"]' // Replace with actual selector
};
await authenticateWebsite(page, authConfig);
// Step 2: Navigate to the authenticated pages we want to screenshot
const pagesToCapture = [
{ url: 'https://example.com/dashboard', name: 'dashboard' },
{ url: 'https://example.com/profile', name: 'profile' },
{ url: 'https://example.com/settings', name: 'settings' }
];
// Step 3: For each page, navigate and capture with Playwright
for (const pageInfo of pagesToCapture) {
// Navigate to the page
console.log(`🌐 Navigating to ${pageInfo.url}...`);
await page.goto(pageInfo.url);
await page.waitForLoadState('networkidle');
// Take the screenshot with Playwright
console.log(`📸 Taking screenshot of ${pageInfo.name}...`);
const buffer = await page.screenshot({ fullPage: true });
// Save the screenshot
await saveScreenshot(buffer, `${pageInfo.name}_${Date.now()}.png`);
}
console.log('🎉 All authenticated screenshots captured successfully!');
} catch (error) {
console.error('❌ Error during screenshot capture:', error);
} finally {
// Close the browser
await browser.close();
}
}
captureAuthenticatedScreenshots().catch(console.error);
5.2 Capturing Screenshots After Form Submissions
Create a new file src/form-submission-screenshots.js
:
// src/form-submission-screenshots.js
const { chromium } = require('playwright');
const { saveScreenshot } = require('./utils/playwright-helpers');
async function captureFormSubmissionScreenshots() {
// Launch a browser
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
try {
// Step 1: Navigate to a form page
console.log('🌐 Navigating to form page...');
await page.goto('https://example.com/contact');
// Step 2: Fill out the form
console.log('✏️ Filling out the form...');
await page.fill('#name', 'Test User');
await page.fill('#email', 'test@example.com');
await page.fill('#message', 'This is a test message from Playwright');
// Take a screenshot of the filled form
const formBuffer = await page.screenshot({ fullPage: true });
await saveScreenshot(formBuffer, `form_filled_${Date.now()}.png`);
// Step 3: Submit the form
console.log('📤 Submitting the form...');
await page.click('#submit-button');
// Step 4: Wait for the success message
await page.waitForSelector('.success-message', { state: 'visible' });
// Step 5: Take a screenshot of the result
console.log('📸 Taking screenshot of submission result...');
const resultBuffer = await page.screenshot({ fullPage: true });
await saveScreenshot(resultBuffer, `form_result_${Date.now()}.png`);
console.log('🎉 Form submission workflow completed successfully!');
} catch (error) {
console.error('❌ Error during form submission workflow:', error);
} finally {
await browser.close();
}
}
captureFormSubmissionScreenshots().catch(console.error);
5.3 Capturing Screenshots of Local Development
Create a new file src/local-dev-screenshots.js
:
// src/local-dev-screenshots.js
const { chromium } = require('playwright');
const { saveScreenshot } = require('./utils/playwright-helpers');
const http = require('http');
const fs = require('fs').promises;
const path = require('path');
// Simple local server for demonstration
function startLocalServer(port = 3000) {
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<!DOCTYPE html>
<html>
<head>
<title>Local Development Page</title>
<style>
body { font-family: Arial, sans-serif; padding: 2rem; }
.container { max-width: 800px; margin: 0 auto; }
.header { background: #f0f0f0; padding: 1rem; border-radius: 4px; }
.content { margin-top: 1rem; }
</style>
</head>
<body>
<div class="container">
<div class="header">
<h1>Local Development Page</h1>
<p>This is a test page running on a local server.</p>
</div>
<div class="content">
<h2>Features</h2>
<ul>
<li>Capturing screenshots of local development</li>
<li>Testing layout changes before deployment</li>
<li>Documenting local UI for team sharing</li>
</ul>
<p>Current time: ${new Date().toLocaleString()}</p>
</div>
</div>
</body>
</html>
`);
});
server.listen(port, () => {
console.log(`Local server running at http://localhost:${port}`);
});
return server;
}
async function captureLocalDevelopmentScreenshots() {
// Start a local server
const server = startLocalServer();
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
try {
// Navigate to our local server
await page.goto('http://localhost:3000');
await page.waitForLoadState('networkidle');
// Take a screenshot
console.log('📸 Taking screenshot of local development page...');
const buffer = await page.screenshot({ fullPage: true });
await saveScreenshot(buffer, `local_dev_${Date.now()}.png`);
console.log('🎉 Local development screenshot captured successfully!');
} catch (error) {
console.error('❌ Error during local development capture:', error);
} finally {
await browser.close();
server.close();
console.log('🛑 Local server stopped');
}
}
captureLocalDevelopmentScreenshots().catch(console.error);
5.4 Testing Responsive Designs
Create a new file src/responsive-testing.js
:
// src/responsive-testing.js
const { chromium } = require('playwright');
const { saveScreenshot } = require('./utils/playwright-helpers');
const fs = require('fs').promises;
const path = require('path');
async function responsiveTestingWorkflow() {
// Define the viewports we want to test
const viewports = [
{ name: 'mobile', width: 375, height: 667, userAgent: 'Mozilla/5.0 (iPhone; CPU iPhone OS 14_0 like Mac OS X) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/14.0 Mobile/15E148 Safari/604.1' },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'desktop', width: 1440, height: 900 },
{ name: 'large-desktop', width: 1920, height: 1080 }
];
// Create a directory for this test run
const timestamp = Date.now();
const testDir = path.join(__dirname, 'screenshots', `responsive_test_${timestamp}`);
await fs.mkdir(testDir, { recursive: true });
// Launch a browser
const browser = await chromium.launch({ headless: true });
try {
// Pages to test
const pagesToTest = [
{ url: 'https://example.com', name: 'homepage' },
{ url: 'https://example.com/about', name: 'about' },
{ url: 'https://example.com/contact', name: 'contact' }
];
// For each page
for (const pageInfo of pagesToTest) {
console.log(`📱 Testing responsive designs for ${pageInfo.name}...`);
// Create a subdirectory for this page
const pageDir = path.join(testDir, pageInfo.name);
await fs.mkdir(pageDir, { recursive: true });
// Capture each viewport
for (const viewport of viewports) {
console.log(` - Capturing ${viewport.name} (${viewport.width}x${viewport.height})...`);
// Create a new context with the viewport size
const context = await browser.newContext({
viewport: { width: viewport.width, height: viewport.height },
userAgent: viewport.userAgent
});
const page = await context.newPage();
// Navigate to the page
await page.goto(pageInfo.url);
await page.waitForLoadState('networkidle');
// Take the screenshot
const buffer = await page.screenshot({ fullPage: true });
await saveScreenshot(buffer, `${viewport.name}.png`, pageDir);
// Close the context
await context.close();
}
}
// Create an HTML report
console.log('📊 Generating HTML report...');
const reportContent = `
<!DOCTYPE html>
<html>
<head>
<title>Responsive Testing Report</title>
<style>
body { font-family: Arial, sans-serif; margin: 0; padding: 20px; }
h1, h2, h3 { margin-top: 30px; }
.page-section { margin-bottom: 40px; border-bottom: 1px solid #eee; padding-bottom: 20px; }
.viewport-grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); gap: 20px; }
.viewport-item { border: 1px solid #ddd; border-radius: 5px; overflow: hidden; }
.viewport-item h3 { margin: 0; padding: 10px; background: #f5f5f5; }
.viewport-item img { max-width: 100%; display: block; }
</style>
</head>
<body>
<h1>Responsive Testing Report</h1>
<p>Generated on: ${new Date().toLocaleString()}</p>
${pagesToTest.map(page => `
<div class="page-section">
<h2>${page.name} (${page.url})</h2>
<div class="viewport-grid">
${viewports.map(viewport => `
<div class="viewport-item">
<h3>${viewport.name} (${viewport.width}x${viewport.height})</h3>
<img src="./${page.name}/${viewport.name}.png" alt="${page.name} on ${viewport.name}">
</div>
`).join('')}
</div>
</div>
`).join('')}
</body>
</html>
`;
await fs.writeFile(path.join(testDir, 'report.html'), reportContent);
console.log(`✅ Report generated at ${path.join(testDir, 'report.html')}`);
console.log('🎉 Responsive testing workflow completed successfully!');
} catch (error) {
console.error('❌ Error during responsive testing workflow:', error);
} finally {
await browser.close();
}
}
responsiveTestingWorkflow().catch(console.error);
5.5 Capturing Screenshots of Specific Elements
Create a new file src/element-screenshots.js
:
// src/element-screenshots.js
const { chromium } = require('playwright');
const { screenshotElement } = require('./utils/playwright-helpers');
async function captureElementScreenshots() {
// Launch a browser
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
try {
// Navigate to a page
console.log('🌐 Navigating to example.com...');
await page.goto('https://example.com');
await page.waitForLoadState('networkidle');
// Capture specific elements
await screenshotElement(page, 'header', 'header_element.png');
await screenshotElement(page, 'main', 'main_content.png');
await screenshotElement(page, 'footer', 'footer_element.png');
// Capture a more specific element (if it exists)
try {
await screenshotElement(page, '.hero-section', 'hero_section.png');
} catch (error) {
console.log('⚠️ Hero section not found, skipping...');
}
console.log('🎉 Element screenshots captured successfully!');
} catch (error) {
console.error('❌ Error during element screenshot capture:', error);
} finally {
await browser.close();
}
}
captureElementScreenshots().catch(console.error);
5.6 Waiting for Dynamic Content
Create a new file src/dynamic-content-screenshots.js
:
// src/dynamic-content-screenshots.js
const { chromium } = require('playwright');
const { saveScreenshot } = require('./utils/playwright-helpers');
async function captureDynamicContentScreenshots() {
// Launch a browser
const browser = await chromium.launch({ headless: true });
const context = await browser.newContext();
const page = await context.newPage();
try {
// Navigate to a page with dynamic content
console.log('🌐 Navigating to a page with dynamic content...');
await page.goto('https://example.com/dynamic-page');
// Take a screenshot before interaction
console.log('📸 Taking screenshot before interaction...');
const beforeBuffer = await page.screenshot({ fullPage: true });
await saveScreenshot(beforeBuffer, `before_interaction_${Date.now()}.png`);
// Interact with the page to trigger dynamic content
console.log('🖱️ Triggering dynamic content...');
await page.click('#load-more-button');
// Wait for the content to load using different strategies
// Option 1: Wait for a specific element to appear
console.log('⏳ Waiting for dynamic content to load...');
await page.waitForSelector('.dynamic-content', { state: 'visible' });
// Option 2: Wait for network activity to settle
// await page.waitForLoadState('networkidle');
// Option 3: Wait for a specific amount of time (less reliable)
// await page.waitForTimeout(2000);
// Take a screenshot after the dynamic content has loaded
console.log('📸 Taking screenshot after dynamic content loaded...');
const afterBuffer = await page.screenshot({ fullPage: true });
await saveScreenshot(afterBuffer, `after_interaction_${Date.now()}.png`);
console.log('🎉 Dynamic content screenshots captured successfully!');
} catch (error) {
console.error('❌ Error during dynamic content capture:', error);
} finally {
await browser.close();
}
}
captureDynamicContentScreenshots().catch(console.error);
6. Conclusion: Mastering Advanced Screenshot Workflows with Playwright
In this tutorial, we've explored how to use Playwright for advanced screenshot workflows, focusing on real-world scenarios that developers encounter daily. Here's a summary of what we've learned:
Key Capabilities of Playwright for Screenshots:
-
Authentication Handling: Playwright makes it easy to automate login flows and capture screenshots of authenticated content.
-
Interaction Support: You can interact with elements (click buttons, fill forms) before taking screenshots, allowing you to capture specific states.
-
Waiting Mechanisms: Playwright provides multiple ways to wait for content to load, ensuring your screenshots capture the right moment.
-
Element Screenshots: You can target specific elements on a page rather than capturing the entire page.
-
Responsive Testing: By configuring different viewport sizes and user agents, you can test how your site looks across devices.
-
Local Development: You can capture screenshots of locally hosted content during development.
Best Practices:
-
Use Helper Functions: Create reusable helper functions to keep your code organized and DRY.
-
Wait Appropriately: Always wait for content to fully load before taking screenshots, using the most appropriate waiting strategy.
-
Error Handling: Implement proper error handling to make your screenshot workflows robust.
-
Generate Reports: Create HTML reports to make it easy to view and compare screenshots.
-
Close Resources: Always close browsers and servers when you're done to prevent resource leaks.
By mastering these techniques, you can create powerful, automated screenshot workflows that handle even the most complex scenarios. Whether you're documenting your application, testing responsive designs, or capturing authenticated content, Playwright provides all the tools you need.
Get free access to the SCRNIFY API during our open beta and start generating screenshots today!
Cheers, Laura & Heidi 🇦🇹
P.S. Have questions about using Playwright for advanced screenshot workflows? Drop us a line at support@scrnify.com or check out our other tutorials for more examples and best practices.