Skip to content
ExponaBLOG
PLAYWRIGHT & BROWSER AUTOMATION · DAY 065 / 100

Screenshots for documentation for Playwright developers

Keep docs images up to date.

Expona Admin8 min read

Screenshots for documentation for Playwright developers

Product documentation falls out of sync faster than you'd think. A UI redesign ships, your docs still show the old interface, and users get confused. Screenshots for documentation for Playwright developers solves this by automating the capture of key screens alongside your test suite, keeping visuals fresh and accurate without manual effort. We'll walk through how to script screenshots, integrate them into your CI pipeline, handle sensitive data, and organize them with your docs.

Why screenshots matter in documentation

Visual documentation is your first impression. When users see a screenshot that doesn't match their screen, they lose confidence. The traditional approach—take a screenshot manually, paste it, hope nobody changes the UI—breaks immediately. A better strategy is to treat screenshots as an artifact of your test suite, generated on demand, versioned alongside your code. This keeps your docs synchronized with reality.

The cost of stale screenshots is underrated. Support gets questions about features that look different. Users waste time hunting for buttons. Onboarding slows down. By automating screenshot generation, you invest once and reap benefits every release.

Script screenshots of key screens

Start by identifying which screens deserve documentation. Typically this is the happy path: login, dashboard, main workflow, settings. Write a Playwright test that navigates to each screen and captures it. The test doesn't assert anything; it's purely for documentation.

typescript

import { test } from '@playwright/test';

test.describe('Documentation Screenshots', () => {
  test('capture login screen', async ({ page }) => {
    await page.goto('http://localhost:3000/login');
    await page.screenshot({ path: 'docs/screenshots/login.png' });
  });

  test('capture dashboard after login', async ({ page, context }) => {
    // Set authentication cookie to skip login flow
    await context.addCookies([{
      name: 'auth_token',
      value: 'test_token_xyz',
      url: 'http://localhost:3000'
    }]);
    await page.goto('http://localhost:3000/dashboard');
    // Wait for data to load
    await page.waitForLoadState('networkidle');
    await page.screenshot({ path: 'docs/screenshots/dashboard.png' });
  });

  test('capture user settings panel', async ({ page, context }) => {
    await context.addCookies([{
      name: 'auth_token',
      value: 'test_token_xyz',
      url: 'http://localhost:3000'
    }]);
    await page.goto('http://localhost:3000/settings');
    await page.waitForLoadState('networkidle');
    await page.screenshot({ path: 'docs/screenshots/settings.png' });
  });

  test('capture notification center', async ({ page, context }) => {
    await context.addCookies([{
      name: 'auth_token',
      value: 'test_token_xyz',
      url: 'http://localhost:3000'
    }]);
    await page.goto('http://localhost:3000/notifications');
    await page.locator('[data-testid="notification-badge"]').click();
    await page.screenshot({ path: 'docs/screenshots/notifications.png' });
  });
});

Keep screenshots focused. A full-page screenshot of a complex dashboard can be overwhelming. Consider capturing specific sections or components instead. Use page.locator().screenshot() to capture just the relevant area.

typescript

test('capture form section only', async ({ page, context }) => {
  await context.addCookies([{
    name: 'auth_token',
    value: 'test_token_xyz',
    url: 'http://localhost:3000'
  }]);
  await page.goto('http://localhost:3000/settings');
  await page.waitForLoadState('networkidle');
  
  // Capture only the profile form, not the entire page
  const formSection = page.locator('[data-testid="profile-form"]');
  await formSection.screenshot({ path: 'docs/screenshots/profile-form.png' });
});

Wait for the page to reach a stable state before capturing. Use waitForLoadState('networkidle') to ensure data has loaded, or wait for specific elements to be visible. This prevents flaky screenshots where content is still loading.

Run in CI on releases

Screenshots should regenerate on every release, not manually. Add a CI workflow that runs these tests when you cut a release tag or merge to main. GitHub Actions makes this straightforward.

typescript

name: Generate Documentation Screenshots

on:
  push:
    tags:
      - 'v*'
  workflow_dispatch:

jobs:
  screenshots:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      
      - name: Install Node dependencies
        run: npm ci
      
      - name: Install Playwright browsers
        run: npx playwright install --with-deps
      
      - name: Start test server
        run: npm run dev &
        env:
          NODE_ENV: test
      
      - name: Wait for server
        run: npx wait-on http://localhost:3000 --timeout 30000
      
      - name: Generate screenshots
        run: npx playwright test --grep "Documentation Screenshots"
      
      - name: Commit and push screenshots
        run: |
          git config user.name "Screenshot Bot"
          git config user.email "bot@example.com"
          git add docs/screenshots/
          git commit -m "chore: update documentation screenshots"
          git push
        if: always()

This workflow runs when you push a release tag, starts your dev server, generates all screenshots, and commits them back to the repository. The updated images are now part of your release.

For environments where your app needs specific data, seed a test database before running screenshots. Create stable test accounts and data that won't change between runs, so screenshots look consistent.

Mask personal data

Real user data has no place in public documentation. Before shipping screenshots, mask or remove sensitive information: email addresses, phone numbers, credit card details, names, anything that identifies someone.

Playwright provides several approaches. The simplest is to overwrite sensitive elements with placeholder text using the web API:

typescript

test('capture dashboard with masked data', async ({ page, context }) => {
  await context.addCookies([{
    name: 'auth_token',
    value: 'test_token_xyz',
    url: 'http://localhost:3000'
  }]);
  await page.goto('http://localhost:3000/dashboard');
  await page.waitForLoadState('networkidle');
  
  // Mask email display
  await page.locator('[data-testid="user-email"]').evaluate(
    el => el.textContent = 'user@example.com'
  );
  
  // Mask account numbers
  await page.locator('[data-testid="account-number"]').evaluate(
    el => el.textContent = '••••••7890'
  );
  
  // Mask user names
  await page.locator('[data-testid="user-name"]').evaluate(
    el => el.textContent = 'John Doe'
  );
  
  await page.screenshot({ path: 'docs/screenshots/dashboard-masked.png' });
});

For more sophisticated masking, blur sensitive regions using CSS before capturing:

typescript

async function maskSensitiveData(page) {
  // Blur email addresses
  await page.addStyleTag({
    content: `
      [data-sensitive="email"] {
        filter: blur(5px);
      }
      [data-sensitive="phone"] {
        filter: blur(5px);
      }
      [data-sensitive="payment"] {
        filter: blur(5px);
      }
    `
  });
}

test('capture payment settings with blur', async ({ page, context }) => {
  await context.addCookies([{
    name: 'auth_token',
    value: 'test_token_xyz',
    url: 'http://localhost:3000'
  }]);
  await page.goto('http://localhost:3000/settings/payment');
  await page.waitForLoadState('networkidle');
  
  await maskSensitiveData(page);
  await page.screenshot({ path: 'docs/screenshots/payment-settings.png' });
});

A test database with fake, public-safe data is your friend here. Use names like "Jane Smith", emails like "support@example.com", phone numbers like "+1 (555) 000-0000". Never use real customer data, even anonymized.

Store alongside docs

Keep screenshots versioned with your documentation. A common structure is:

typescript

docs/
├── guides/
│   ├── getting-started.md
│   ├── dashboard.md
│   └── settings.md
└── screenshots/
    ├── login.png
    ├── dashboard.png
    ├── profile-form.png
    ├── notifications.png
    └── payment-settings.png

Reference screenshots in your Markdown using relative paths:

typescript

# Dashboard Guide

After logging in, you'll see the main dashboard:

![Dashboard overview](../screenshots/dashboard.png)

The dashboard shows your recent activity and key metrics at a glance.

## Profile Settings

Navigate to settings to update your profile:

![Profile form](../screenshots/profile-form.png)

Enter your information and click Save.

Commit screenshots to your repository so they're part of the release. This makes them available in your docs site and in offline documentation tools. Some teams prefer storing images in a CDN to keep repository size down, but for most projects, versioning them in Git is simpler and more reliable.

If your docs are generated from Markdown (like with Docusaurus or MkDocs), screenshot updates trigger documentation rebuilds automatically. Your docs stay in sync with your releases effortlessly.

Common mistakes

Taking screenshots too early. Pages that are still loading show incomplete content. Always wait for data to arrive. Use waitForLoadState('networkidle') or explicitly wait for key elements.

Capturing with real user data. Screenshots end up in documentation, GitHub, and CDNs. Never use actual customer information. Even test accounts should use obviously fake data that users recognize as examples.

Forgetting about responsive design. Set a consistent viewport size for all screenshots so they look uniform. Most documentation sites assume desktop layouts, so use something like 1280x720 for consistency.

Not waiting for fonts and images. A screenshot taken before web fonts load or images are rendered looks different from what users see. Add explicit waits or use waitForLoadState('networkidle').

Running screenshots locally during development. Local environments have different styling, data, or configurations. Always generate screenshots in CI with a clean environment so they match what users experience.

Key takeaways

  • Write dedicated Playwright tests that navigate to key screens and capture them; don't mix documentation screenshots with functional tests.
  • Run screenshot generation in CI on every release to keep images synchronized with your code automatically.
  • Mask or replace all sensitive data—email, names, phone numbers—with publicly safe placeholders or blur filters before committing.
  • Store screenshots in your repository alongside documentation Markdown so they're versioned and distributed with your docs site.
  • Wait for pages to fully load and use consistent viewport sizes to avoid flaky, inconsistent screenshots.
  • Use a stable test database with obviously fake data to ensure screenshots look the same across runs.
ShareXLinkedInReddit

Found this useful? Support the writing.

Buy me a coffee
Written by
Expona Admin
Site administrator

Comments

Loading comments…

Leave a comment

Comments are reviewed before they appear. Be kind and stay on topic.

Related articles