Using Playwright with Scrnify for Advanced Screenshot Workflows
4/1/2025
Hey there, Laura and Heidi here from SCRNIFY! š
If you're a developer working with web automation, you've likely faced the challenges of capturing high-quality screenshots at scale. Maybe you're running Playwright tests across multiple browsers, generating visual documentation, or creating automated reports with website previews. Whatever your use case, managing the infrastructure for reliable screenshot capture can quickly become a headache. š
We've been there! That's why we built SCRNIFY ā to handle the complex infrastructure so you can focus on creating awesome screenshot workflows. In this tutorial, we'll show you how to combine the power of Playwright's local automation capabilities with SCRNIFY's scalable screenshot API for advanced use cases.
By the end of this article, you'll know how to:
- Set up a local Playwright project for complex browser interactions
- Use SCRNIFY's API to offload screenshot generation
- Create advanced workflows combining both technologies
- Save time and resources by letting SCRNIFY handle the heavy lifting
Let's dive in! ā
Prerequisites
Before we get started, make sure you have:
- Node.js installed (version 14 or newer) - Download from nodejs.org
- Playwright installed (we'll cover this in the tutorial)
- SCRNIFY API key - Sign up for the free beta
- Basic familiarity with JavaScript and async/await patterns
Setting Up Your Project
Let's start by creating a new Node.js project and installing the necessary dependencies. Open your terminal and run the following commands:
# Create a new directory and navigate into it
mkdir playwright-scrnify-demo
cd playwright-scrnify-demo
# Initialize a new Node.js project
npm init -y
# Install Playwright and other dependencies
npm install playwright axios dotenv
Next, let's create a .env
file to store your SCRNIFY API key:
# .env file
SCRNIFY_API_KEY=your_api_key_here
Make sure to replace your_api_key_here
with your actual SCRNIFY API key.
Now, let's create a basic project structure:
playwright-scrnify-demo/
āāā .env # Environment variables
āāā package.json # Project configuration
āāā src/ # Source code directory
ā āāā index.js # Main script
ā āāā local.js # Local Playwright functions
ā āāā scrnify.js # SCRNIFY API functions
āāā screenshots/ # Directory for saved screenshots
Let's create these directories and files:
mkdir src screenshots
touch src/index.js src/local.js src/scrnify.js
Local Setup: Configuring Playwright
First, let's set up the local Playwright configuration in src/local.js
. This file will contain functions for taking screenshots using Playwright locally:
// src/local.js
const { chromium } = require('playwright');
/**
* Takes a screenshot of a webpage using local Playwright
* @param {string} url - The URL to capture
* @param {Object} options - Screenshot options
* @returns {Promise<Buffer>} - Screenshot buffer
*/
async function takeLocalScreenshot(url, options = {}) {
// Set default options
const defaultOptions = {
fullPage: false,
type: 'png',
path: null, // If provided, save to this path
viewport: { width: 1280, height: 720 },
waitUntil: 'networkidle'
};
const screenshotOptions = { ...defaultOptions, ...options };
let browser = null;
try {
// Launch a browser
console.log('Launching browser...');
browser = await chromium.launch();
const context = await browser.newContext({
viewport: screenshotOptions.viewport
});
// Create a new page and navigate to URL
const page = await context.newPage();
console.log(`Navigating to ${url}...`);
await page.goto(url, { waitUntil: screenshotOptions.waitUntil });
// Take screenshot
console.log('Taking screenshot...');
const screenshotBuffer = await page.screenshot({
fullPage: screenshotOptions.fullPage,
type: screenshotOptions.type,
path: screenshotOptions.path
});
return screenshotBuffer;
} catch (error) {
console.error('Error taking local screenshot:', error);
throw error;
} finally {
// Always close the browser
if (browser) {
await browser.close();
console.log('Browser closed.');
}
}
}
/**
* Takes a screenshot of a specific element
* @param {string} url - The URL to capture
* @param {string} selector - CSS selector for the element
* @param {Object} options - Screenshot options
* @returns {Promise<Buffer>} - Screenshot buffer
*/
async function takeElementScreenshot(url, selector, options = {}) {
let browser = null;
try {
// Launch a browser
console.log('Launching browser...');
browser = await chromium.launch();
const context = await browser.newContext({
viewport: options.viewport || { width: 1280, height: 720 }
});
// Create a new page and navigate to URL
const page = await context.newPage();
console.log(`Navigating to ${url}...`);
await page.goto(url, { waitUntil: options.waitUntil || 'networkidle' });
// Wait for the element to be visible
console.log(`Waiting for selector: ${selector}`);
await page.waitForSelector(selector, { state: 'visible' });
// Take screenshot of the element
const element = await page.$(selector);
if (!element) {
throw new Error(`Element with selector "${selector}" not found`);
}
console.log('Taking element screenshot...');
const screenshotBuffer = await element.screenshot({
type: options.type || 'png',
path: options.path
});
return screenshotBuffer;
} catch (error) {
console.error('Error taking element screenshot:', error);
throw error;
} finally {
// Always close the browser
if (browser) {
await browser.close();
console.log('Browser closed.');
}
}
}
module.exports = {
takeLocalScreenshot,
takeElementScreenshot
};
Integrating with SCRNIFY API
Now, let's create the SCRNIFY integration in src/scrnify.js
. This file will handle communication with the SCRNIFY API:
// src/scrnify.js
const axios = require('axios');
const fs = require('fs').promises;
const path = require('path');
require('dotenv').config();
// Get API key from environment variables
const SCRNIFY_API_KEY = process.env.SCRNIFY_API_KEY;
const SCRNIFY_API_URL = 'https://api.scrnify.com/capture';
/**
* Takes a screenshot using SCRNIFY API
* @param {string} url - The URL to capture
* @param {Object} options - Screenshot options
* @returns {Promise<Buffer>} - Screenshot buffer
*/
async function takeScrnifyScreenshot(url, options = {}) {
// Set default options
const defaultOptions = {
type: 'image',
format: 'png',
width: 1280,
height: 720,
fullPage: false,
waitUntil: 'firstMeaningfulPaint',
savePath: null // If provided, save to this path
};
const screenshotOptions = { ...defaultOptions, ...options };
// Prepare API request parameters
const params = new URLSearchParams({
key: SCRNIFY_API_KEY,
url: url,
type: screenshotOptions.type,
format: screenshotOptions.format,
width: screenshotOptions.width.toString(),
waitUntil: screenshotOptions.waitUntil
});
// Add height parameter only if fullPage is false
if (!screenshotOptions.fullPage) {
params.append('height', screenshotOptions.height.toString());
}
// Add fullPage parameter if true
if (screenshotOptions.fullPage) {
params.append('fullPage', 'true');
}
try {
console.log('Requesting screenshot from SCRNIFY API...');
const response = await axios({
method: 'get',
url: `${SCRNIFY_API_URL}?${params.toString()}`,
responseType: 'arraybuffer'
});
const buffer = Buffer.from(response.data, 'binary');
// Save the screenshot if a path is provided
if (screenshotOptions.savePath) {
await fs.mkdir(path.dirname(screenshotOptions.savePath), { recursive: true });
await fs.writeFile(screenshotOptions.savePath, buffer);
console.log(`Screenshot saved to: ${screenshotOptions.savePath}`);
}
return buffer;
} catch (error) {
if (error.response) {
console.error(`API Error (${error.response.status}):`,
Buffer.from(error.response.data).toString());
} else {
console.error('Error taking SCRNIFY screenshot:', error.message);
}
throw error;
}
}
module.exports = {
takeScrnifyScreenshot
};
Creating Advanced Workflows
Now, let's create our main script in src/index.js
that demonstrates advanced screenshot workflows by combining local Playwright capabilities with SCRNIFY API:
// src/index.js
const { takeLocalScreenshot, takeElementScreenshot } = require('./local');
const { takeScrnifyScreenshot } = require('./scrnify');
const { chromium } = require('playwright');
const path = require('path');
/**
* Example 1: Compare local and SCRNIFY screenshots
*/
async function compareScreenshots(url) {
try {
console.log(`\nšø Comparing screenshots for: ${url}`);
// Take screenshots using both methods
const localScreenshot = await takeLocalScreenshot(url, {
path: path.join(__dirname, '../screenshots/local.png')
});
const scrnifyScreenshot = await takeScrnifyScreenshot(url, {
savePath: path.join(__dirname, '../screenshots/scrnify.png')
});
console.log('ā
Screenshots captured successfully!');
console.log('Local screenshot size:', localScreenshot.length, 'bytes');
console.log('SCRNIFY screenshot size:', scrnifyScreenshot.length, 'bytes');
} catch (error) {
console.error('ā Error comparing screenshots:', error.message);
}
}
/**
* Example 2: Capture screenshots after complex interactions
*/
async function captureAfterInteraction(url) {
let browser = null;
try {
console.log(`\nšØ Capturing screenshots after interaction: ${url}`);
// Launch a browser for interaction
browser = await chromium.launch();
const context = await browser.newContext();
const page = await context.newPage();
// Navigate to the page
console.log('Navigating to URL...');
await page.goto(url, { waitUntil: 'networkidle' });
// Perform some interactions (example: filling a form)
console.log('Performing interactions...');
await page.fill('input[name="search"]', 'playwright tips');
await page.click('button[type="submit"]');
await page.waitForLoadState('networkidle');
// Now use SCRNIFY to capture the results
console.log('Capturing results with SCRNIFY...');
const currentUrl = page.url();
await takeScrnifyScreenshot(currentUrl, {
fullPage: true,
savePath: path.join(__dirname, '../screenshots/search-results.png')
});
console.log('ā
Interaction and screenshot complete!');
} catch (error) {
console.error('ā Error in interaction workflow:', error.message);
} finally {
// Always close the browser
if (browser) {
await browser.close();
}
}
}
/**
* Example 3: Batch screenshot using SCRNIFY for efficiency
*/
async function batchScreenshots(urls) {
try {
console.log('\nš ļø Starting batch screenshot process...');
const promises = urls.map((url, index) => {
return takeScrnifyScreenshot(url, {
savePath: path.join(__dirname, `../screenshots/batch-${index + 1}.png`)
});
});
await Promise.all(promises);
console.log('ā
Batch screenshots completed successfully!');
} catch (error) {
console.error('ā Error in batch screenshot process:', error.message);
}
}
/**
* Example 4: Advanced responsive testing workflow
*/
async function responsiveTesting(url) {
try {
console.log(`\nš± Testing responsive designs for: ${url}`);
// Define device viewports
const viewports = [
{ name: 'mobile', width: 375, height: 667 },
{ name: 'tablet', width: 768, height: 1024 },
{ name: 'desktop', width: 1440, height: 900 }
];
// Capture screenshots for each viewport
for (const viewport of viewports) {
console.log(`Capturing for ${viewport.name} (${viewport.width}x${viewport.height})...`);
await takeScrnifyScreenshot(url, {
width: viewport.width,
height: viewport.height,
savePath: path.join(__dirname, `../screenshots/${viewport.name}.png`)
});
}
console.log('ā
Responsive testing completed!');
} catch (error) {
console.error('ā Error in responsive testing:', error.message);
}
}
// Run all examples
async function runExamples() {
try {
// Example 1: Compare local and SCRNIFY screenshots
await compareScreenshots('https://example.com');
// Example 2: Capture after interaction
await captureAfterInteraction('https://google.com');
// Example 3: Batch screenshots
await batchScreenshots([
'https://example.com',
'https://playwright.dev',
'https://nodejs.org'
]);
// Example 4: Responsive testing
await responsiveTesting('https://example.com');
console.log('\nš All examples completed successfully!');
} catch (error) {
console.error('ā Error running examples:', error);
}
}
// Run the examples
runExamples();
Running the Project
Now you can run your project with:
node src/index.js
This will execute all the examples, demonstrating the advanced screenshot workflows combining Playwright and SCRNIFY.
Understanding the Advanced Use Cases
Let's break down the advanced workflows we've implemented:
1. Comparative Analysis
The compareScreenshots
function takes screenshots of the same URL using both local Playwright and SCRNIFY API. This is useful for:
- Quality comparison between local and cloud-based rendering
- Validating that your cloud screenshots match local expectations
- Performance benchmarking (timing and file size comparisons)
2. Complex Interaction Workflows
The captureAfterInteraction
function showcases how to:
- Use Playwright locally to perform complex interactions (form filling, clicking)
- Offload the screenshot capture to SCRNIFY once the page is in the desired state
- Save resources by handling only the automation locally while delegating the resource-intensive rendering
This approach is ideal for capturing screenshots after complex user journeys, authenticated sessions, or dynamic content interactions.
3. Batch Screenshot Processing
The batchScreenshots
function demonstrates how to efficiently capture multiple screenshots in parallel using SCRNIFY. This approach:
- Avoids the overhead of launching multiple browser instances locally
- Leverages SCRNIFY's scalable architecture for concurrent processing
- Reduces execution time for large batches of screenshots
4. Responsive Design Testing
The responsiveTesting
function shows how to validate website appearance across different device sizes. Using SCRNIFY for this workflow:
- Eliminates the need to configure and maintain multiple device emulations
- Ensures consistent rendering across viewport sizes
- Enables quick visual verification of responsive behavior
Conclusion
By combining Playwright's automation capabilities with SCRNIFY's screenshot API, you can create powerful, efficient, and scalable screenshot workflows. This hybrid approach gives you the best of both worlds:
- Local Playwright for complex interactions, authentication, and precise control over user flows
- SCRNIFY API for reliable, high-quality screenshot generation without infrastructure overhead
This approach is particularly valuable when:
- Scaling your screenshot processes without scaling your infrastructure
- Working with resource-intensive screenshots (high resolution, full page)
- Running parallel screenshot jobs without overwhelming local resources
- Ensuring consistent rendering across environments
Remember, SCRNIFY handles all the complex infrastructure, browser management, and scaling challenges for you. This means you can focus on creating valuable automation workflows rather than maintaining screenshot generation infrastructure.
Ready to supercharge your screenshot workflows with SCRNIFY? Sign up for our free beta and start exploring these advanced use cases today!
For more information on getting started with SCRNIFY, check out our introductory post.
Cheers, Laura & Heidi š¦š¹
P.S. We'd love to hear about your specific screenshot automation challenges! Tweet us at @scrnify and let us know how you're using Playwright and SCRNIFY together. š