Visual comparison testing captures a page or component and compares it with an approved baseline image. It detects layout, styling and rendering regressions that functional assertions often miss.

How visual comparison works

  1. Prepare a known application state. Use controlled data, viewport, browser and theme.
  2. Capture the baseline. This is the approved reference image.
  3. Run the test after a change. Capture the same state again.
  4. Compare the images. The tool highlights changed pixels or meaningful visual differences.
  5. Review the result. Fix unexpected differences or approve an intentional change as the new baseline.
Visual tests complement functional tests: a button can still be clickable while hidden behind another element, incorrectly styled or outside the viewport.

Playwright example

Playwright includes screenshot comparison through toHaveScreenshot().

import { test, expect } from '@playwright/test'; test('checkout summary matches the baseline', async ({ page }) => { await page.goto('/checkout?testData=visual'); await expect(page.getByTestId('checkout-summary')).toBeVisible(); await expect(page).toHaveScreenshot('checkout.png', { fullPage: true, animations: 'disabled', }); });

The first run creates a reference image. Later runs compare the current screenshot with that reference.

Compare one component

const card = page.getByTestId('product-card').first(); await expect(card).toHaveScreenshot('product-card.png', { animations: 'disabled', caret: 'hide', });

Element screenshots are usually easier to maintain than full-page screenshots. They also make failures easier to understand.

Mask dynamic content

await expect(page).toHaveScreenshot('account.png', { mask: [ page.getByTestId('current-time'), page.getByTestId('user-avatar'), ], maskColor: '#808080', });

Set a difference threshold

await expect(page).toHaveScreenshot('dashboard.png', { maxDiffPixels: 100, });
Use tolerance carefully: a large threshold may hide a real defect. First remove unstable content, then add the smallest justified tolerance.

Create stable screenshots

  • Run baseline and comparison in the same operating system, browser version, viewport and device scale factor.
  • Install the same fonts in local and CI environments.
  • Use fixed test data. Avoid live names, balances, adverts and changing record counts.
  • Freeze or mock dates, timezones and API responses when they affect the UI.
  • Disable animations, transitions, blinking cursors and auto-rotating content.
  • Wait for a meaningful UI state, not a fixed sleep.
  • Wait for required images and fonts before capturing.
  • Hide or mask only content that is genuinely irrelevant to the test.
  • Use Docker when local and CI rendering must be identical.

Optional screenshot stylesheet

/* visual-test.css */ [data-visual-dynamic], iframe, video { visibility: hidden !important; } * { transition: none !important; } await expect(page).toHaveScreenshot('profile.png', { stylePath: 'visual-test.css', });

What to test

Core pages

Login, dashboard, search, checkout and other high-value user journeys.

Component states

Default, hover, focus, disabled, loading, empty, error and success.

Responsive layouts

Selected mobile, tablet and desktop widths, especially near breakpoints.

Themes

Light, dark, high-contrast and supported brand themes.

Content extremes

Long text, translations, large values, missing images and many rows.

Browsers

Use separate baselines when rendering differences between engines matter.

Example viewport projects

export default defineConfig({ projects: [ { name: 'desktop', use: { viewport: { width: 1440, height: 900 } } }, { name: 'mobile', use: { viewport: { width: 390, height: 844 } } }, ], });

Do not snapshot every page at every size. Start with reusable components, high-risk pages and known responsive breakpoints.

Baseline and review workflow

  1. Create the baseline from a reviewed, trusted state.
  2. Commit baseline images with the test when using local Playwright snapshots.
  3. Run visual tests in CI for each pull request.
  4. Open the actual, expected and difference images.
  5. Decide whether each difference is a defect or an intended change.
  6. Fix defects and rerun the test.
  7. Update the baseline only after the intentional change is approved.
# Run visual tests npx playwright test tests/visual # Open the HTML report npx playwright show-report # Update approved baselines npx playwright test tests/visual --update-snapshots
Never update snapshots blindly: review every changed area first. Otherwise the command can turn a regression into the new expected result.

Tool options

ToolBest fitApproach
PlaywrightTeams already using Playwright that want baselines stored with the tests.Local pixel comparison with configurable masks, styles and thresholds.
PercyCross-browser visual review connected to pull requests.Cloud snapshots, baseline comparison and team approval workflow.
Applitools EyesLarge suites that benefit from Visual AI and flexible match levels.AI-assisted comparison intended to reduce irrelevant pixel noise.
ChromaticStorybook component libraries and UI review.Cloud-rendered component or page snapshots with approved baselines.
BackstopJSOpen-source, configuration-driven browser visual regression.Reference and test screenshots with image diffs and reports.
ArgosPlaywright, Cypress or Storybook teams wanting hosted visual diffs.Screenshot upload, branch baselines and pull-request review.

How to choose

  • Choose Playwright snapshots for simple ownership, low cost and repository-based baselines.
  • Choose a hosted service when designers and product owners need an easy review interface.
  • Choose component-level testing when a design system contains many isolated states.
  • Check browser coverage, data residency, pricing, CI integration and baseline management before adopting a cloud tool.

Common problems

ProblemLikely causeFix
Almost every pixel changesDifferent viewport, browser, OS, font or scale.Standardise the execution environment.
Small text differencesFont rendering or missing fonts.Install and wait for the same fonts.
Random failuresAnimations, time, live data or late loading.Control the state and wait for readiness.
Diff is difficult to reviewFull-page screenshot covers too much.Capture a smaller component or region.
Real defects passTolerance or masking is too broad.Reduce exclusions and use focused assertions.
Constant baseline updatesLow-value or unstable areas are included.Remove noisy tests and focus on meaningful states.

Practical QA checklist

  • Baseline was created from the correct branch and environment.
  • Viewport, browser, locale, timezone, theme and data are explicit.
  • Screenshot is taken only after the intended state is ready.
  • Dynamic content is controlled rather than hidden without reason.
  • Difference threshold is small and justified.
  • Failure artefacts include expected, actual and diff images.
  • An accountable reviewer approves intentional changes.
  • Functional and accessibility assertions still cover behaviour and semantics.

Useful links