Back to guides

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.

By The Testing AcademyPublished July 9, 2026Updated July 9, 202616 min read

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

GoalAPI or setting
Viewport screenshotpage.screenshot({ path })
Full page screenshotpage.screenshot({ path, fullPage: true })
Element screenshotlocator.screenshot({ path })
Binary buffer, no fileomit path and use returned buffer
Auto on failureuse: { screenshot: 'only-on-failure' }
Visual assertionexpect(page).toHaveScreenshot()
Hide dynamic bitsmask, 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

OptionWhy use it
animations: 'disabled'Reduces motion related visual flake
caret: 'hide'Avoids blinking cursor differences
maskHides dynamic regions for comparisons
omitBackgroundCleaner 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

ArtifactBest forWeakness
ScreenshotImmediate UI state evidenceOne moment only
TraceStep by step root causeLarger artifact, needs viewer
VideoWatching interaction sequenceHeavier, 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:

  1. Configure Playwright output directories
  2. Upload test-results and playwright-report on failure
  3. Keep retention reasonable
  4. 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:

  1. Keep the failure screenshot
  2. Capture a focused element screenshot of the broken control if useful
  3. Attach both to the bug
  4. Include URL, browser project, and test name
  5. 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:

  1. Start with a few high value components, not the whole app
  2. Freeze data and viewport
  3. Mask dynamic regions
  4. Review diffs like code reviews
  5. Update baselines intentionally, never casually
  6. 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

  1. Write one test that captures viewport and full page images
  2. Capture one element alert screenshot
  3. Enable only-on-failure in config and remove temporary debug shots
  4. Fail a test on purpose and inspect artifacts
  5. Add one stable toHaveScreenshot for a static component
  6. 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:

  1. Mask locators
  2. Replace text in the page before screenshot via evaluate
  3. Use stable seed data
  4. Stub network responses for feeds
  5. 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:

  1. For UI heavy changes, attach before/after screenshots in the PR
  2. Prefer element or section captures over giant full desktops when possible
  3. Pair images with text: what changed and what should stay the same
  4. 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

  1. Open playwright.config.ts
  2. Set use.screenshot to only-on-failure
  3. Set use.trace to on-first-retry
  4. Run a deliberately failing test
  5. Open the HTML report and confirm the image is attached
  6. Configure CI to upload test-results and the report folder
  7. Remove ad hoc debug screenshots that are no longer needed

This sequence alone improves most beginner Playwright repos.

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.