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:

  1. Authentication Handling: Playwright makes it easy to automate login flows and capture screenshots of authenticated content.

  2. Interaction Support: You can interact with elements (click buttons, fill forms) before taking screenshots, allowing you to capture specific states.

  3. Waiting Mechanisms: Playwright provides multiple ways to wait for content to load, ensuring your screenshots capture the right moment.

  4. Element Screenshots: You can target specific elements on a page rather than capturing the entire page.

  5. Responsive Testing: By configuring different viewport sizes and user agents, you can test how your site looks across devices.

  6. Local Development: You can capture screenshots of locally hosted content during development.

Best Practices:

  1. Use Helper Functions: Create reusable helper functions to keep your code organized and DRY.

  2. Wait Appropriately: Always wait for content to fully load before taking screenshots, using the most appropriate waiting strategy.

  3. Error Handling: Implement proper error handling to make your screenshot workflows robust.

  4. Generate Reports: Create HTML reports to make it easy to view and compare screenshots.

  5. 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.

Additional Resources

Ready to Get Started?

Sign up now and start capturing stunning screenshots in minutes.

Sign Up Now