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
- Prepare a known application state. Use controlled data, viewport, browser and theme.
- Capture the baseline. This is the approved reference image.
- Run the test after a change. Capture the same state again.
- Compare the images. The tool highlights changed pixels or meaningful visual differences.
- Review the result. Fix unexpected differences or approve an intentional change as the new baseline.
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,
});
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
Login, dashboard, search, checkout and other high-value user journeys.
Default, hover, focus, disabled, loading, empty, error and success.
Selected mobile, tablet and desktop widths, especially near breakpoints.
Light, dark, high-contrast and supported brand themes.
Long text, translations, large values, missing images and many rows.
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
- Create the baseline from a reviewed, trusted state.
- Commit baseline images with the test when using local Playwright snapshots.
- Run visual tests in CI for each pull request.
- Open the actual, expected and difference images.
- Decide whether each difference is a defect or an intended change.
- Fix defects and rerun the test.
- 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
Tool options
| Tool | Best fit | Approach |
|---|---|---|
| Playwright | Teams already using Playwright that want baselines stored with the tests. | Local pixel comparison with configurable masks, styles and thresholds. |
| Percy | Cross-browser visual review connected to pull requests. | Cloud snapshots, baseline comparison and team approval workflow. |
| Applitools Eyes | Large suites that benefit from Visual AI and flexible match levels. | AI-assisted comparison intended to reduce irrelevant pixel noise. |
| Chromatic | Storybook component libraries and UI review. | Cloud-rendered component or page snapshots with approved baselines. |
| BackstopJS | Open-source, configuration-driven browser visual regression. | Reference and test screenshots with image diffs and reports. |
| Argos | Playwright, 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
| Problem | Likely cause | Fix |
|---|---|---|
| Almost every pixel changes | Different viewport, browser, OS, font or scale. | Standardise the execution environment. |
| Small text differences | Font rendering or missing fonts. | Install and wait for the same fonts. |
| Random failures | Animations, time, live data or late loading. | Control the state and wait for readiness. |
| Diff is difficult to review | Full-page screenshot covers too much. | Capture a smaller component or region. |
| Real defects pass | Tolerance or masking is too broad. | Reduce exclusions and use focused assertions. |
| Constant baseline updates | Low-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
- Playwright visual comparisons ↗ — screenshots, baselines, thresholds and styles.
- Playwright screenshot assertion options ↗ — masks, animations and comparison settings.
- Percy documentation ↗ — hosted visual testing and review.
- Applitools Eyes documentation ↗ — Visual AI testing concepts and SDKs.
- Chromatic visual tests ↗ — Storybook, Playwright and Cypress workflows.
- BackstopJS ↗ — open-source visual regression testing.
- Argos documentation ↗ — screenshot testing and pull-request review.