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.
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.
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.
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:
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:
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:
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:
# Dashboard Guide
After logging in, you'll see the main dashboard:

The dashboard shows your recent activity and key metrics at a glance.
## Profile Settings
Navigate to settings to update your profile:

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.






Comments
Loading comments…