Visual testing for React apps catches unintended UI changes by comparing screenshots of your components against a baseline. This guide walks you through setting up and running visual regression tests using Playwright, a reliable and widely used tool. You’ll need a React project with Node.js installed and a basic understanding of running npm scripts.
What You’ll Need
- A React application (Create React App, Vite, Next.js, or any setup) with a running dev server or build command.
- Node.js 16 or newer installed on your machine.
- Playwright installed as a dev dependency (
npm install -D @playwright/test). - A browser engine downloaded for Playwright (
npx playwright install chromium). - Basic familiarity with JavaScript/TypeScript and writing test files.
Step-by-Step
- Install Playwright Test
Runnpm install -D @playwright/testin your project root. This adds the test runner and its APIs. If you’re using TypeScript, it works out of the box. - Configure Playwright
Create aplaywright.config.js(or.ts) file at the root. Set the test directory, specify the browser (chromium is enough), and define a base URL for your app. Example:module.exports = { testDir: './visual-tests', use: { baseURL: 'http://localhost:3000', viewport: { width: 1280, height: 720 }, }, }; - Start Your App
Ensure your React app is running on the port you set in the config. For a Vite app, runnpm run dev. For Next.js, usenpm run devornpm startafter building. Keep this terminal open. - Write Your First Visual Test
Create a file likevisual-tests/home.spec.js. Use thetestfunction from Playwright and thetoHaveScreenshot()assertion. Example:const { test, expect } = require('@playwright/test'); test('home page looks correct', async ({ page }) => { await page.goto('/'); await page.waitForLoadState('networkidle'); await expect(page).toHaveScreenshot('home.png'); }); - Generate a Baseline
Runnpx playwright testthe first time. Playwright will fail because there’s no reference image, but it will create a snapshot in a__screenshots__folder. Review the generated image to confirm it looks correct. - Approve the Baseline
After checking the screenshot, run the test again withnpx playwright test --update-snapshots. This stores the current image as the baseline. Subsequent runs will compare against this. - Run Tests on Every Change
Executenpx playwright testafter any UI change. The runner will report mismatches with a diff image showing the exact pixel differences. - Handle Dynamic Content
For components with animations, timers, or random data, use Playwright’spage.clockor freeze the date. For example,await page.clock.setFixedTime(new Date('2024-01-01'))ensures consistent timestamps. For random data, mock the API response or use a fixed seed. - Test Individual Components (Optional)
Use Storybook if you have it. Install@storybook/addon-playwrightand write tests that visit each story URL. This isolates components from layout and routing. - Integrate with CI
Add a GitHub Action or similar that runsnpx playwright install --with-depsandnpx playwright teston every pull request. Store baseline images in your repository so CI can compare.
Common Mistakes
- Forgetting to wait for network idle – Images or fonts loading late cause flaky screenshots. Always use
waitForLoadState('networkidle')or wait for specific elements. - Ignoring viewport differences – Screenshots taken at different viewport sizes will fail. Set a fixed viewport in config and use the same across all tests.
- Including dynamic data in components – Dates, user avatars, or random quotes change every run. Mock them or use fixed fixtures.
- Updating snapshots without reviewing – Running
--update-snapshotsblindly can accept bugs. Always diff the new image against the old manually. - Not excluding third-party widgets – Embedded maps or ads change frequently. Use
page.routeto stub them or hide them with CSS.
Tips
- Keep baseline images in version control. This gives you a history of UI changes and makes reviews easier.
- Use
maxDiffPixelRatioin the assertion to allow tiny anti-aliasing differences:await expect(page).toHaveScreenshot('home.png', { maxDiffPixelRatio: 0.01 }). - Group tests by route or component to make failures easier to trace.
- Run visual tests only on a separate branch or on pull requests to avoid slowing down local development.
- For large apps, consider using a cloud service like Percy or Applitools, but the local Playwright approach is free and sufficient for most projects.
Troubleshooting
- Tests fail with “Screenshot comparison failed” – Open the diff image in
test-results/to see the exact pixels. If changes are intentional, run with--update-snapshotsafter manual review. - Tests hang or time out – Your app might not load due to a port mismatch. Check the baseURL in config and the actual port from your dev server. Also ensure the server is running before tests.
- Fonts or icons render differently – This often happens when the system font stack differs. Install the same fonts in your CI environment, or use a web font loaded from a CDN.
- Animations cause flakiness – Disable CSS transitions in your test setup by injecting a style tag:
await page.addStyleTag({ content: '* { transition: none !important; animation: none !important; }' }). - Snapshots not created – Check that the test directory exists and the test file is named correctly. Playwright only picks up files with
.spec.jsor.test.jsextensions by default.
Frequently Asked Questions
What’s the difference between visual testing and unit testing?
Unit tests check logic and output values, while visual tests verify how the UI looks. They complement each other: unit tests catch broken functions, visual tests catch broken layouts.
Can I use Jest for visual testing?
Jest is primarily for unit tests. For visual regression, you’d need a separate library like Puppeteer or Playwright. Playwright is recommended because it has built-in screenshot comparison.
How often should I update baselines?
Update baselines only when you intentionally change the UI. For every new feature or bug fix that alters visuals, review the diff and update. Avoid updating on every commit.
Do I need a separate server for tests?
Yes, Playwright needs your app to be accessible. You can use webServer in the config to auto-start your dev server before tests. Example: webServer: { command: 'npm run dev', url: 'http://localhost:3000' }.
Can I test responsive designs?
Yes, create multiple tests with different viewport sizes. Set test.use({ viewport: { width: 375, height: 812 } }) for mobile and another for desktop. Each will have its own baseline.
What if my app uses authentication?
Mock the login by setting a cookie or token in the test. Use page.addInitScript to set localStorage or intercept API calls with page.route to return fake user data.
Related
General steps for current mainstream versions; menus can differ by device and version. See the disclaimer or report an error.
More in this bed
- Fix Unfortunately App Has Stopped: Step-by-Step
- Restart a Frozen Samsung Galaxy A12
- HomePod Stereo Pair with AirPlay 2 from iPhone
- Fix 0x8e5e0247 Error on Windows
- Pixel & Pixel XL No Sound: Solved?
- How to Edit Playlists on iPhone