Visual Regression Testing with Playwright & Percy: Ending the Fear of “Broken Layouts” Every Time You Deploy

Development tutorial - IT technology blog
Development tutorial - IT technology blog

The Nightmare of “Broken Layouts” After Every CSS Update

I once participated in a system refactor project with over 50,000 lines of code. The most painful lesson I learned was: even if Unit Tests or Integration Tests cover 90% of the logic, the interface can still fall apart. Just a small change in a variables.scss file or adjusting the padding of a shared component can cause dozens of other pages to shift by a few pixels. These minor bugs are extremely difficult to notice with the naked eye during a quick browser check.

The common result is that bugs are only discovered when a customer tries the app on Safari or an unusual tablet screen. At that point, finding the cause and fixing the error is not only time-consuming but also reduces the team’s credibility with partners.

Why Standard Unit Tests and E2E Tests Aren’t Enough?

Tools like Jest or React Testing Library primarily focus on testing logic. They answer the question: “If I click this button, does the modal appear?”. However, they are completely blind to whether that modal is overflowing its borders, if the font is broken, or if the colors match the branding.

Three main reasons why UI bugs are a tough nut to crack:

  • The Butterfly Effect of CSS: CSS is inherited. Changing a property in a parent component can ruin the layout of dozens of child components.
  • Browser Differences: An interface might look perfect on Chrome but could be completely broken on Safari or Firefox.
  • Limitations of Manual Review: When reviewing a Pull Request, we usually focus on the code logic. Testing the UI in every corner of the project manually is nearly impossible.

Three Common Approaches to Interface Testing

During my projects, I’ve experimented with these three methods:

  1. Manual QA: Hiring testers to manually check every page. This is too slow, expensive, and doesn’t scale as the project grows.
  2. Snapshot Testing (with Jest): Saving the HTML structure of the component. However, identical HTML doesn’t mean identical rendering. Simply changing a color code in CSS changes the UI, but the HTML remains the same.
  3. Visual Regression Testing: Taking a screenshot of the interface as a baseline. The system compares this image with the new version after code changes. If there is even a 0.1% pixel deviation, you get an immediate alert.

After many trials, the duo of Playwright and Percy has become the “perfect match” for modern React applications.

The Power of the Playwright + Percy Duo

Playwright acts as an automated “driver” for the browser. Meanwhile, Percy is a professional “photographer” in the cloud. Percy doesn’t just take photos; it handles storage, comparison, and management of snapshots between builds.

Step 1: Install Libraries

First, install the necessary packages into your React project (Vite, Next.js, or CRA):

npm install --save-dev @playwright/test @percy/cli @percy/playwright

Then, initialize the Playwright configuration if you haven’t already:

npx playwright install

Step 2: Write the Test Script

Instead of writing complex image comparison code, you just need to call the percySnapshot function. Create a file tests/visual.spec.ts with the following content:

import { test } from '@playwright/test';
import percySnapshot from '@percy/playwright';

test.describe('Visual Regression Testing', () => {
  test('Homepage displays correctly according to design', async ({ page }) => {
    await page.goto('http://localhost:3000');
    
    // Wait for data or animations to complete
    await page.waitForLoadState('networkidle');

    // Take a snapshot and send it to Percy cloud
    await percySnapshot(page, 'Homepage - Desktop');
  });

  test('Product page displays correctly', async ({ page }) => {
    await page.goto('http://localhost:3000/products');
    await percySnapshot(page, 'Product List');
  });
});

Step 3: Configure the Percy Token

You need to create a free account at percy.io to get a PERCY_TOKEN. This token identifies your project on Percy’s servers.

# Linux/macOS
export PERCY_TOKEN=your_token_here

# Windows (PowerShell)
$env:PERCY_TOKEN = "your_token_here"

Step 4: Run Tests and View Results

To run the tests, wrap the Playwright command within the Percy command:

npx percy exec -- npx playwright test

Once finished, Percy will provide a dashboard link. There, any changed areas will be highlighted in bright red. If it’s an intentional change (e.g., changing a button color according to a new design), just click “Approve”. If it’s an accidental error, you’ll know exactly where you broke it before the customer ever sees it.

Real-world Experience to Avoid “False Alarms”

When applying this to real projects, you’ll encounter failing tests even when the UI isn’t broken (flaky tests). Here is how to handle them:

1. Handling Dynamic Data

If the website displays the current date and time, Percy will report errors constantly because the time always changes. Use percyCSS to hide these elements when taking the snapshot:

await percySnapshot(page, 'Dashboard', {
  percyCSS: '.current-date { visibility: hidden; }'
});

2. Disabling Animations

Moving effects often make snapshots unstable. You should turn off all animations in the test environment for the most accurate comparison results.

3. Leveraging CI/CD

Don’t just run tests locally. Integrate Percy into GitHub Actions. Every time a Pull Request is opened, Percy will automatically check and comment the results on the PR. This approach gives the team great peace of mind during code reviews because every UI deviation is exposed.

Conclusion

Investing in Visual Testing is the best way to protect your sleep before every major deployment. It shortens UI review time and completely eliminates silly layout breakage. If you are developing applications that require high precision in user experience, try applying Playwright and Percy today.

Share: