GUIDE / automation
How to Take Screenshots in Playwright
Learn how to take screenshots in Playwright for full page, element, and failure captures, plus visual checks and practical CI debugging tips.
If you want reliable screenshots in Playwright, you need more than one API call. Screenshots help you debug failures, document bugs, and optionally power visual comparisons. Playwright can capture the viewport, the full page, a single element, or automatic images on failure. Used well, they shorten root cause time. Used carelessly, they create noisy artifacts and flaky visual tests.
This guide shows practical patterns for page.screenshot, locator.screenshot, config level capture, CI artifacts, visual assertions, masking dynamic data, and common mistakes. If you are still learning the broader tool, pair this with the Playwright tutorial.
Quick Reference
| Goal | API or setting |
|---|---|
| Viewport screenshot | page.screenshot({ path }) |
| Full page screenshot | page.screenshot({ path, fullPage: true }) |
| Element screenshot | locator.screenshot({ path }) |
| Binary buffer, no file | omit path and use returned buffer |
| Auto on failure | use: { screenshot: 'only-on-failure' } |
| Visual assertion | expect(page).toHaveScreenshot() |
| Hide dynamic bits | mask, CSS, or stable test data |
Keep this table nearby while you implement.
Why Screenshots Matter in Playwright Suites
Automation failures are often hard to understand from stack traces alone. A screenshot answers immediate questions:
- Did the page reach the expected screen?
- Was a modal open?
- Did validation appear?
- Was the button disabled?
- Did the wrong environment load?
Screenshots also improve collaboration. Developers and manual testers can see the same evidence without reproducing every failure locally first.
Screenshots are not a replacement for assertions. An image can show a red banner, but a precise assertion still proves the business rule.
Basic Page Screenshots
Viewport capture
import { test, expect } from '@playwright/test';
test('capture login viewport', async ({ page }) => {
await page.goto('/login');
await expect(page.getByRole('heading', { name: 'Sign in' })).toBeVisible();
await page.screenshot({ path: 'artifacts/login-viewport.png' });
});
This captures what is visible in the current viewport.
Full page capture
await page.screenshot({
path: 'artifacts/login-full.png',
fullPage: true,
});
Use full page when layout issues live below the fold, such as footer links, long forms, or sticky section problems.
Important full page caveats
- Infinite scroll feeds may never have a true end
- Lazy loaded images may need scrolling into view first
- Very long pages create huge files
- Some fixed position elements can look odd in stitched output
If the page loads content on scroll, script the scroll before capture.
async function scrollToBottom(page) {
await page.evaluate(async () => {
await new Promise<void>((resolve) => {
let total = 0;
const distance = 400;
const timer = setInterval(() => {
const scrollHeight = document.body.scrollHeight;
window.scrollBy(0, distance);
total += distance;
if (total >= scrollHeight) {
clearInterval(timer);
resolve();
}
}, 100);
});
});
}
Then screenshot.
Element Screenshots
Element level captures reduce noise.
const banner = page.getByRole('alert');
await expect(banner).toBeVisible();
await banner.screenshot({ path: 'artifacts/login-error.png' });
Good element screenshot targets:
- Toast messages
- Form error summaries
- Price summary cards
- Navigation bars
- Chart widgets
- Permission denied panels
Element screenshots are often better bug attachments than full desktops of unrelated chrome.
Screenshot Options You Should Know
Common options:
await page.screenshot({
path: 'artifacts/checkout.png',
fullPage: true,
type: 'png', // or 'jpeg'
quality: 80, // jpeg only
omitBackground: true, // useful for transparent element shots
animations: 'disabled',
caret: 'hide',
scale: 'css',
mask: [page.locator('.timestamp'), page.getByTestId('user-avatar')],
timeout: 5000,
});
Why these options matter
| Option | Why use it |
|---|---|
animations: 'disabled' | Reduces motion related visual flake |
caret: 'hide' | Avoids blinking cursor differences |
mask | Hides dynamic regions for comparisons |
omitBackground | Cleaner component images |
type: 'jpeg' | Smaller artifacts when perfect sharpness is less critical |
For debugging, PNG is a safe default. For high volume archives, JPEG can reduce storage.
Automatic Screenshots on Failure
Manual screenshots in every test become clutter. Prefer config driven failure captures.
// playwright.config.ts
import { defineConfig } from '@playwright/test';
export default defineConfig({
use: {
screenshot: 'only-on-failure',
video: 'retain-on-failure',
trace: 'on-first-retry',
},
});
Recommended beginner friendly combination:
- Screenshots on failure for quick glance evidence
- Trace on first retry for deep debugging
- Video retained on failure if your team finds video useful
This trio is more powerful than scattering ad hoc page.screenshot calls everywhere.
Screenshots vs Traces vs Videos
| Artifact | Best for | Weakness |
|---|---|---|
| Screenshot | Immediate UI state evidence | One moment only |
| Trace | Step by step root cause | Larger artifact, needs viewer |
| Video | Watching interaction sequence | Heavier, less precise than trace for DOM detail |
When a flake is timing related, traces often beat a single screenshot. When a visual defect is obvious, a screenshot is enough. Learn more flake diagnosis patterns in how to fix flaky tests.
Organizing Screenshot Paths
Unstructured files become garbage quickly.
Practical conventions:
artifacts/
local/
ci/
run-2026-07-09/
login-invalid-password.png
checkout-coupon-error.png
In tests, include feature names:
await page.screenshot({ path: `artifacts/checkout/${test.info().title}.png` });
Or rely mostly on Playwright's output directory and failure attachments instead of hand managed paths.
Using Screenshots in Assertions: Visual Testing
Playwright supports screenshot comparisons:
await expect(page).toHaveScreenshot('dashboard.png');
Element level:
await expect(page.getByTestId('price-summary')).toHaveScreenshot('price-summary.png');
When visual assertions help
- Marketing landing pages with strict design review
- Component gallery regressions
- Hard to express pixel layout bugs
- Chart rendering smoke checks with stable fixtures
When visual assertions hurt
- Pages with highly dynamic timestamps, ads, avatars, or live metrics
- Early UI development with constant design churn
- Environments with font or anti aliasing differences across machines
Visual testing is a discipline, not a checkbox. Stable data and masking are mandatory.
Stabilizing Screenshots for Comparison
1. Control test data
Use fixed names, prices, and dates where possible.
2. Mask dynamic regions
await expect(page).toHaveScreenshot({
mask: [
page.getByTestId('clock'),
page.getByTestId('notification-count'),
],
});
3. Disable animations
await page.screenshot({ animations: 'disabled' });
Or set this consistently in visual projects.
4. Freeze time when the app allows
If the UI shows "Updated 3 seconds ago," time travel helpers or mocked clocks reduce noise.
5. Standardize viewport
use: {
viewport: { width: 1280, height: 720 },
}
Different viewport sizes guarantee visual diffs.
6. Run visual suites in consistent environments
CI containers are usually more stable than every developer laptop.
Capturing Screenshots After Specific Actions
Useful pattern for debugging long flows:
await page.getByRole('button', { name: 'Apply coupon' }).click();
await page.screenshot({ path: 'artifacts/after-coupon.png' });
await expect(page.getByText('Coupon applied')).toBeVisible();
Do not leave temporary debug screenshots in permanent suites without a clear reason. Prefer failure auto capture once the test is stable.
Screenshot Helpers in Fixtures or Page Objects
If multiple tests need branded captures, encapsulate them.
export class Debug {
constructor(private page: Page) {}
async shot(name: string) {
await this.page.screenshot({
path: `artifacts/${name}.png`,
fullPage: true,
animations: 'disabled',
});
}
}
Keep page objects focused on product actions. A small debug helper is fine, but do not turn page objects into screenshot museums. The Page Object Model still prioritizes business interactions.
CI: Publishing Screenshot Artifacts
A local screenshot that nobody can open after CI fails has limited value.
In GitHub Actions style pipelines:
- Configure Playwright output directories
- Upload
test-resultsandplaywright-reporton failure - Keep retention reasonable
- Ensure paths are stable enough for reviewers to find images quickly
Also confirm container dependencies for browsers are installed. Missing browser OS libs can cause capture or launch failures that look mysterious if you only stare at test code.
Common Bug Report Workflow With Playwright Screenshots
When an automated check finds a defect:
- Keep the failure screenshot
- Capture a focused element screenshot of the broken control if useful
- Attach both to the bug
- Include URL, browser project, and test name
- State expected vs actual in text, not only images
Images support the report. They do not replace a clear expected result.
Advanced: Full Page, Clip, and Multiple Contexts
Clip a region
await page.screenshot({
path: 'artifacts/clip.png',
clip: { x: 0, y: 0, width: 600, height: 400 },
});
Multiple pages
const popupPromise = page.waitForEvent('popup');
await page.getByRole('link', { name: 'Open report' }).click();
const popup = await popupPromise;
await popup.waitForLoadState();
await popup.screenshot({ path: 'artifacts/report-popup.png' });
Mobile project screenshot
If you run iPhone or Pixel projects, screenshots inherit that viewport and device metrics. Name artifacts with project names to avoid confusion.
Performance and Storage Considerations
Screenshots are cheap individually and expensive in bulk.
Practical limits:
- Do not screenshot every step in every test by default
- Prefer only-on-failure in large suites
- Delete local artifact folders regularly
- For visual baselines, store only intentional golden images in git or a managed service
- Avoid full page captures in extremely long pages unless needed
A suite that writes thousands of images per CI run will slow feedback and raise storage cost.
Debugging When Screenshots Are Blank or Wrong
Blank or dark images
Possible causes:
- Page still navigating
- Auth wall not passed
- Capture before render
- Wrong page or context
Wait for a key locator before capture.
Wrong page content
You may be capturing before navigation settles or on an unexpected tab.
Differences only in CI
Fonts, timezones, locale, and animation timing differ. Stabilize environment and mask dynamic areas.
Element screenshot clipped oddly
Overflow, sticky headers, or elements outside viewport can produce surprising crops. Scroll into view first:
await locator.scrollIntoViewIfNeeded();
await locator.screenshot({ path: 'artifacts/button.png' });
Worked Example: Login Failure Evidence
import { test, expect } from '@playwright/test';
test('invalid password shows safe error', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('qa.user@example.com');
await page.getByLabel('Password').fill('WrongPass#1');
await page.getByRole('button', { name: 'Sign in' }).click();
const alert = page.getByRole('alert');
await expect(alert).toContainText('Invalid email or password');
// Optional focused evidence for a known intermittent UI bug investigation
await alert.screenshot({ path: 'artifacts/login-invalid-password-alert.png' });
});
In normal regression, config level failure screenshots may make the manual capture unnecessary. Keep manual shots for investigations and visual cases.
Visual Regression Mini Strategy
If you adopt toHaveScreenshot:
- Start with a few high value components, not the whole app
- Freeze data and viewport
- Mask dynamic regions
- Review diffs like code reviews
- Update baselines intentionally, never casually
- Separate visual project from pure functional smoke if needed
Functional automation should still assert text, URLs, and state directly. Screenshots complement those checks.
Common Mistakes With Screenshots in Playwright
Mistake 1: Screenshot before the UI is ready
Always wait for the state you intend to document.
Mistake 2: Relying on screenshots instead of assertions
A green test that only captures an image proves almost nothing.
Mistake 3: Full page captures for every failure by custom code when config already handles it
Prefer platform features.
Mistake 4: Committing thousands of debug images
Keep baselines intentional. Keep debug artifacts out of git.
Mistake 5: Unstable visual tests with live clocks and avatars
Mask or stabilize first.
Mistake 6: Ignoring project and browser differences
Chromium and WebKit can render shadows and fonts differently. Decide which projects own visual baselines.
Mistake 7: No CI upload
If reviewers cannot open the image, the screenshot did not help the team.
Mistake 8: Using screenshots as the only flake investigation tool
Add traces. Timing bugs need timelines.
Practical Checklist
Before you call your screenshot strategy done:
- Failure screenshots enabled in config
- Traces available on retry or failure
- Artifact upload configured in CI
- Debug screenshots use clear names and folders
- Element screenshots used for focused evidence
- Visual assertions limited to stable targets
- Dynamic regions masked
- Animations disabled for comparisons
- Viewport standardized
- Team knows where to find artifacts
Practice Path
- Write one test that captures viewport and full page images
- Capture one element alert screenshot
- Enable only-on-failure in config and remove temporary debug shots
- Fail a test on purpose and inspect artifacts
- Add one stable
toHaveScreenshotfor a static component - Upload artifacts from CI once
For hands-on product flows before automation, explore challenges in QABattle battles, note the UI states worth evidencing, then automate those states with focused captures.
Final Guidance
Playwright gives you strong screenshot primitives, but the winning strategy is intentional:
- Use automatic failure screenshots for suite wide evidence
- Use manual or element screenshots for investigation and docs
- Use visual comparisons only where design stability is real
- Prefer traces when you need the story of the failure, not only the final frame
If you remember one rule for screenshots in Playwright, remember this: capture the state after it is true, store only what helps humans decide, and never let images replace clear assertions about product behavior.
Screenshot Strategy by Test Layer
Not every suite needs the same capture policy.
Smoke suite
- only-on-failure screenshots
- traces on retry
- no visual baselines
Smoke should be fast and decisive. Artifacts matter when red, not when green.
Full regression suite
- only-on-failure screenshots
- selective manual captures around historically buggy screens
- maybe a small visual project for critical branding pages
Visual suite
- dedicated project
- strict viewport
- masked dynamic regions
- reviewed baselines
- fewer functional side effects per test
Separating visual intent from functional intent prevents one noisy pixel diff from blocking a pure behavior check.
Naming Conventions That Survive CI
Good names help humans scan artifacts.
{feature}.{case}.{result}.{browser}.png
Examples:
login.invalid-password.failure.chromium.png
checkout.coupon-apply.success.webkit.png
dashboard.header.baseline.chromium.png
If Playwright manages failure screenshots automatically, focus your custom names on intentional captures and baselines.
Masking Patterns Beyond Simple Locators
Dynamic content can appear in many forms:
- timestamps
- greeting with user first name
- unread notification badges
- ads and recommendations
- map tiles
- chart lines with live metrics
- emails with unique ids
Strategies:
- Mask locators
- Replace text in the page before screenshot via
evaluate - Use stable seed data
- Stub network responses for feeds
- Hide elements with CSS for the capture moment
Example temporary hide:
await page.addStyleTag({
content: '[data-dynamic="true"] { visibility: hidden !important; }',
});
await page.screenshot({ path: 'artifacts/stable-dashboard.png', fullPage: true });
Use this carefully. Hiding too much can make the screenshot meaningless.
Comparing Local and CI Captures
If local and CI screenshots differ constantly:
- compare OS fonts
- compare device scale factor
- compare timezone and locale
- compare Playwright and browser versions
- compare container vs host rendering
For visual suites, treat CI as the source of truth and update baselines from CI artifacts, not from every laptop.
Using Screenshots in Pull Request Review
A practical team habit:
- For UI heavy changes, attach before/after screenshots in the PR
- Prefer element or section captures over giant full desktops when possible
- Pair images with text: what changed and what should stay the same
- Keep captures from the same viewport and theme
This is not automated visual testing, but it improves review quality and costs little.
Playwright Config Recipes
Capture more on failure in non local environments
export default defineConfig({
use: {
screenshot: process.env.CI ? 'only-on-failure' : 'off',
trace: process.env.CI ? 'on-first-retry' : 'retain-on-failure',
},
});
Separate visual project
projects: [
{ name: 'functional-chromium', use: { ...devices['Desktop Chrome'] } },
{
name: 'visual-chromium',
testMatch: /.*visual.spec.ts/,
use: {
...devices['Desktop Chrome'],
viewport: { width: 1280, height: 720 },
},
},
]
This keeps screenshots in Playwright workflows intentional instead of accidental.
Accessibility and Dark Mode Notes
If your product supports themes:
- capture light and dark only when both are in scope
- name artifacts with theme
- do not compare dark baselines to light baselines
- watch for images that look "failed" only because theme setup did not run
Theme setup belongs in fixtures, not in ad hoc test lines repeated everywhere.
Storage Hygiene Script Idea
Add a local cleanup command in package scripts:
rm -rf test-results playwright-report artifacts/local
Document it in README so developers do not commit junk. Baseline images that matter should live in a deliberate directory such as visual-baselines/ with review ownership.
Step by Step: Add Failure Screenshots to an Existing Repo
- Open
playwright.config.ts - Set
use.screenshottoonly-on-failure - Set
use.tracetoon-first-retry - Run a deliberately failing test
- Open the HTML report and confirm the image is attached
- Configure CI to upload
test-resultsand the report folder - Remove ad hoc debug screenshots that are no longer needed
This sequence alone improves most beginner Playwright repos.
Element Screenshot Gallery for Bug Reports
When filing bugs from automation, consider attaching:
- Full viewport for context
- Element crop for the defective control
- Optional second browser capture if the bug is browser specific
Label them clearly in the ticket. Screenshots without labels create guesswork.
Handling Canvas, Video, and Map Elements
Some surfaces do not screenshot meaningfully with default approaches:
- Video frames may be dark or mixed
- WebGL/canvas content can differ by GPU
- Maps move and tile load asynchronously
For these, prefer functional assertions, network stubs, or dedicated visual tools. Do not force flaky full page baselines over live maps.
Collaboration Norms
Agree as a team:
- Where baselines live
- Who approves baseline updates
- Which projects are visual versus functional
- How long CI keeps failure artifacts
- Whether videos are required or optional
Without norms, screenshots in Playwright become personal preference chaos.
Final Decision Rule for Capture Volume
Ask one question before adding another screenshot call: will a human use this image to make a decision that the assertion text and trace cannot already support? If the answer is no, skip the capture. If the answer is yes, make the image easy to find, easy to understand, and stable enough that it does not create new noise. That discipline keeps screenshots in Playwright valuable as evidence rather than as clutter in every green run.
One More Practical Tip
Before you trust a failure screenshot, confirm the test waited for the state you care about. A beautiful image of a loading spinner is not evidence of a product bug. Wait for the heading, alert, table row, or URL that defines the outcome, then capture. That single habit dramatically improves the signal quality of screenshots in Playwright across local runs and CI.
FAQ
Questions testers ask
How do you take a screenshot in Playwright?
Use page.screenshot({ path: 'shot.png' }) for a viewport capture, set fullPage: true for the full scrollable page, or call locator.screenshot() for one element. You can also enable automatic screenshots on failure in the Playwright config.
How do I capture a full page screenshot in Playwright?
Call await page.screenshot({ path: 'full.png', fullPage: true }). Playwright scrolls and stitches the page content into one image. Be careful with infinite scroll pages and lazy loaded sections that need deliberate scrolling first.
Can Playwright take a screenshot of a single element?
Yes. Use a locator and locator.screenshot({ path: 'element.png' }). Element screenshots are useful for components, error banners, and visual checks where the full page adds noise.
How do I screenshot only when a test fails?
Set screenshot: 'only-on-failure' in playwright.config use options, or handle failures in fixtures/hooks. Automatic failure screenshots are usually better than manual captures in every test.
What is the difference between screenshots and traces in Playwright?
A screenshot is a single image of UI state. A trace can include timelines, DOM snapshots, network, and multiple screenshots around actions. Use screenshots for quick evidence and traces for deep debugging.
Are Playwright screenshots good enough for visual regression?
They are a solid capture mechanism, but visual regression also needs baseline management, stable environments, font consistency, and masking dynamic regions. Use toHaveScreenshot assertions when you intentionally adopt visual testing practices.
RELATED GUIDES
Continue the route
Playwright Tutorial: End-to-End Testing from Scratch
Playwright tutorial for beginners: install, write your first test, TypeScript setup, codegen, fixtures, API testing, debugging, and CI tips in one guide.
Flaky Tests: Causes and How to Fix Them
Learn how to fix flaky tests with root cause analysis, stable waits, quarantine strategy, CI retries policy, and practical Playwright examples.
Page Object Model: A Pattern for Maintainable Tests
Learn the page object model for maintainable UI automation tests, with Playwright examples, best practices, and common POM mistakes to avoid.
Selenium vs Playwright vs Cypress in 2026
Compare Selenium vs Playwright vs Cypress in 2026 for speed, browsers, CI, flakiness, ecosystem, and which web automation framework beginners should choose.