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:

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:

  1. Use Playwright locally to perform complex interactions (form filling, clicking)
  2. Offload the screenshot capture to SCRNIFY once the page is in the desired state
  3. 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? Join the open beta and review current pricing.

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. šŸš€

Additional Resources

Ready to Get Started?

Join the open beta and start capturing screenshots in minutes.

Join Open Beta