A web application can pass every functional test and still look broken to users. A button may move outside its container, a heading may wrap onto the wrong line, or a modal may cover an important part of the page. The application still loads, and the buttons may still work, but the user experience has changed.
These problems are difficult to catch with traditional automated tests because functional tests usually check behavior rather than appearance.
Playwright visual testing helps identify these changes by capturing screenshots and comparing them with previously approved images. When a page looks different, the test reports a visual difference that developers can investigate.
This approach is useful for applications with complex layouts, shared UI components, responsive designs, and frequent frontend releases.
In this article, you will learn how to create visual regression tests with Playwright, compare screenshots, test responsive layouts, handle dynamic content, and integrate visual checks into a development workflow.
What Is Visual Regression Testing?
Visual regression testing compares the current appearance of a user interface with an approved reference image, often called a baseline screenshot.
Suppose a developer changes a shared CSS class:
.card-title {
margin-bottom: 12px;
}A later change accidentally overrides that spacing:
.card-title {
margin-bottom: 0;
}The application may still pass functional tests. However, card titles may appear too close to the content below them.
A visual regression test can capture the page after the change and compare it with the baseline. If the difference exceeds the configured threshold, the test reports a failure.
The basic workflow is:
- Open the page in a browser.
- Wait until the relevant content is ready.
- Capture a screenshot.
- Compare it with the approved baseline.
- Review any differences.
- Update the baseline only when the visual change is intentional.
A screenshot difference does not automatically mean the application is broken. It means the current rendering differs from the reference and requires review.
Why Use Playwright for Visual Testing?
Playwright is a browser automation framework that supports end-to-end testing across Chromium, Firefox, and WebKit.
It provides built-in screenshot assertions, so teams can add visual checks without building a separate image-comparison system.
For example, Playwright can capture an entire page:
await page.screenshot({
path: "homepage.png",
fullPage: true
});This saves a screenshot to a file. It does not, by itself, compare the image against a baseline.
For automated visual regression testing, Playwright Test provides screenshot assertions through toHaveScreenshot().
These assertions compare the current screenshot with a stored baseline and report differences according to the configured comparison settings.
Step 1: Set Up Playwright
If your project already uses Playwright Test, you can move directly to writing a visual test.
For a new Node.js project, install Playwright Test:
npm init -y
npm install --save-dev @playwright/test
npx playwright installThese commands create a Node.js project, install the testing package, and install the browser binaries required by Playwright.
Add a test script to package.json:
{
"scripts": {
"test:e2e": "playwright test"
},
"devDependencies": {
"@playwright/test": "YOUR_INSTALLED_VERSION"
}
}The version placeholder is illustrative. Keep the actual version installed by your package manager rather than copying the placeholder into the file.
A simple project structure could look like this:
my-web-app/
├── tests/
│ └── homepage.spec.js
├── playwright.config.js
├── package.json
└── package-lock.jsonThe tests directory contains automated tests, while the configuration file defines the browsers, test settings, and application URL.
Step 2: Write Your First Visual Test
Assume your application runs locally at http://localhost:3000.
Create a file named tests/homepage.spec.js:
const { test, expect } = require("@playwright/test");
test("homepage matches the approved screenshot", async ({ page }) => {
await page.goto("http://localhost:3000");
await expect(page).toHaveScreenshot("homepage.png");
});This test opens the homepage and compares its rendered screenshot with the expected baseline.
On the first run, Playwright may report that the baseline screenshot does not exist. Generate the initial baseline using the update-snapshots option:
npx playwright test --update-snapshotsReview the generated screenshot before accepting it. A baseline should represent the intended design, not simply whatever happens to render during the first test run.
After reviewing the baseline, run the test normally:
npx playwright testIf the current screenshot differs beyond the configured comparison limits, the test reports a failure and provides comparison artifacts when available.
For a production workflow, make sure the application is running and its test data is prepared before the test begins.
Step 3: Configure Screenshot Comparisons
Visual comparisons can be affected by browser versions, operating systems, fonts, device settings, and rendering differences.
A Playwright configuration file helps keep test behavior consistent.
Create or update playwright.config.js:
const { defineConfig } = require("@playwright/test");
module.exports = defineConfig({
testDir: "./tests",
use: {
baseURL: "http://localhost:3000",
browserName: "chromium",
viewport: {
width: 1366,
height: 768
},
colorScheme: "light"
},
expect: {
toHaveScreenshot: {
animations: "disabled",
maxDiffPixelRatio: 0.001
}
}
});The configuration sets a fixed viewport, uses Chromium, disables animations during screenshot assertions, and allows a small proportion of differing pixels.
The maxDiffPixelRatio value is an example, not a universal recommendation. The appropriate threshold depends on the application and its rendering environment.
A permissive threshold can hide meaningful visual defects. An excessively strict threshold can cause failures due to harmless rendering differences.
For more stable results, use the same browser version, operating system, fonts, and test environment when generating baselines and running comparisons.
You can now simplify the test:
const { test, expect } = require("@playwright/test");
test("homepage visual regression", async ({ page }) => {
await page.goto("/");
await expect(page).toHaveScreenshot("homepage.png");
});Because the configuration defines baseURL, the test can navigate to the homepage using a relative path.
Step 4: Test Individual Components
Capturing an entire page is useful, but it may make failures difficult to diagnose. A small change in one component can create a large screenshot difference.
Playwright can also capture a specific element.
For example, suppose the homepage contains a pricing section:
const { test, expect } = require("@playwright/test");
test("pricing section matches the baseline", async ({ page }) => {
await page.goto("/pricing");
const pricingSection = page.locator("[data-testid='pricing-section']");
await expect(pricingSection).toBeVisible();
await expect(pricingSection).toHaveScreenshot("pricing-section.png");
});
Join the conversation! Your thoughts help the community grow.