sodcalculator

10 Tech How-To

Visual Testing React Apps: Step-by-Step

Guide 10-15834 · Updated · Step by step

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

  1. Install Playwright Test
    Run npm install -D @playwright/test in your project root. This adds the test runner and its APIs. If you’re using TypeScript, it works out of the box.
  2. Configure Playwright
    Create a playwright.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 },
      },
    };
  3. Start Your App
    Ensure your React app is running on the port you set in the config. For a Vite app, run npm run dev. For Next.js, use npm run dev or npm start after building. Keep this terminal open.
  4. Write Your First Visual Test
    Create a file like visual-tests/home.spec.js. Use the test function from Playwright and the toHaveScreenshot() 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');
    });
  5. Generate a Baseline
    Run npx playwright test the 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.
  6. Approve the Baseline
    After checking the screenshot, run the test again with npx playwright test --update-snapshots. This stores the current image as the baseline. Subsequent runs will compare against this.
  7. Run Tests on Every Change
    Execute npx playwright test after any UI change. The runner will report mismatches with a diff image showing the exact pixel differences.
  8. Handle Dynamic Content
    For components with animations, timers, or random data, use Playwright’s page.clock or 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.
  9. Test Individual Components (Optional)
    Use Storybook if you have it. Install @storybook/addon-playwright and write tests that visit each story URL. This isolates components from layout and routing.
  10. Integrate with CI
    Add a GitHub Action or similar that runs npx playwright install --with-deps and npx playwright test on 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-snapshots blindly 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.route to 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 maxDiffPixelRatio in 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-snapshots after 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.js or .test.js extensions 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

The whole Tech How-To bed →