👍

Covered in this doc

  • Installing Percy for React
  • Adding Percy snapshots to your React app for visual testing
  • Configuring your snapshots and React test setup

Package Status Build Status

❗️

react-percy is deprecated

The react-percy SDK has been deprecated in favor of our Storybook, Puppeteer or Cypress SDKs.

Setup


  • If you use Storybook for React, please see Storybook docs.
  • If you use react-rails, please see the Rails docs.

Install @percy/react using npm:

npm install --save-dev @percy/react

Or using yarn:

yarn add --dev @percy/react

Add the following section to your package.json:

{
  "scripts": {
    "percy": "react-percy"
  }
}

Let's get started by creating a snapshot for a React component in your project. Suppose you have a Button component in a file called Button.js:

import React from 'react';

export default class Button extends React.Component {
  render() {
    return <button>{this.props.children}</button>;
  }
}

Create a new file Button.percy.js. This will contain our Percy snapshot:

import React from 'react';
import Button from './Button';

percySnapshot('Button', () => {
  return <Button>My Button</Button>
});

Configuration


ESLint Configuration

If your project uses ESLint, you will want to whitelist the react-percy globals to avoid "percySnapshot is not defined" errors. The easiest way to do this is using eslint-plugin-react-percy.

Install eslint-plugin-react-percy using npm:

npm install --save-dev eslint-plugin-react-percy

Or using yarn:

yarn add --dev eslint-plugin-react-percy

Add react-percy to the plugins section of your .eslintrc configuration file:

{
  "plugins": [
    "react-percy"
  ]
}

You can whitelist the environment variables provided by react-percy by doing:

{
  "env": {
    "react-percy/globals": true
  }
}

If you're using ESLint v4.1.0 or later, you can instead scope the react-percy environment variables to only react-percy snapshot files by doing:

{
  "overrides": [
    {
      "files": [
        "**/*.percy.{js,jsx}"
      ],
      "env": {
        "react-percy/globals": true
      }
    }
  ]
}

Webpack configuration

The default Webpack configuration of react-percy will run Babel on all .js and .jsx files in your project, with support for all modern JS language features.

If your project is a non-ejected create-react-app, we also automatically include create-react-app Webpack rules for CSS and image files.

Otherwise, if you already have your own Webpack setup, react-percy will not use it by default. However, we allow you to customize our Webpack setup by creating a percy.config.js file in your project's root directory and including an object with any custom Webpack settings.

Reusing webpack config

If you have a particularly complex Webpack config, you may wish to simply reuse it rather than rewriting your rules and plugins in percy.config.js.

To do so, you can import your Webpack config in percy.config.js and set it as the webpack field like so:

import config from './webpack.config.js';

export default {
  webpack: config
};

Keep in mind that we ignore the entry and output fields of your Webpack settings, so if your Webpack config has any plugins that rely on these you may encounter errors. For example, the Webpack CommonsChunkPlugin often refers to specific entry names, but as we ignore your original entry settings it will not be able to find them.

Also remember that the webpack field must be a plain object; it cannot be an array or a function.

Custom Webpack config

For example, let's say you wanted to add SCSS support in your snapshots. Simply add the following to a file called percy.config.js in your project's root directory:

export default {
  webpack: {
    module: {
      rules: [
        {
          test: /\.scss$/,
          loaders: ['style-loader', 'css-loader', 'sass-loader']
        }
      ]
    }
  }
};

You can also provide plugins:

import webpack from 'webpack';

export default {
  webpack: {
    plugins: [
      new webpack.DefinePlugin({
        'process.env.CUSTOM_ENV': '"foo"',
      }),
    ]
  }
};

Supported Webpack options

There are a few things to keep in mind when customizing our Webpack configuration.

You can add almost any Webpack config options in percy.config.js, including rules, plugins, and aliases. But you will not be able to modify the entry or output fields.

Also note that the webpack field in percy.config.js must be an object. Unlike a plain webpack.config.js file, it cannot be a function or an array.

Including entry files

As noted above, react-percy ignores the entry section of your Webpack settings and instead only builds Percy snapshot files and their imports. If your original Webpack entry includes additional global files like babel-polyfill or CSS files that you want to include in all snapshots, you can include them by creating a percy.config.js file in your project's root directory and adding an includeFiles setting like so:

export default {
  includeFiles: [
    'babel-polyfill',
    './styles/global.css'
  ]
};

These files can be package names or relative paths to files within your project. Note that relative paths will be resolved from the root directory of your project.

Any packages or files listed there will be loaded before any snapshots.

Best practices


Percy snapshot files

react-percy looks for files in your project ending with .percy.js or .percy.jsx.

We recommend keeping your Percy snapshots close to the components being tested, but it's entirely up to you!

One common pattern is to add Percy snapshot files right next to the component files. So if you have a component in src/components/Button.js, you would add its snapshots in src/components/Button.percy.js.

Another common pattern is to keep Percy snapshots in a __percy__ folder. For the above example, that would be src/components/__percy__/Button.percy.js.

Basic snapshots

percySnapshot(name, fn)

The first argument is the snapshot name; the second argument is a function that returns the React markup to snapshot. For example:

percySnapshot('Button', () => {
  return <Button>My Button</Button>;
});

The name is what you will see when reviewing snapshots in Percy's web interface, and must be unique across all snapshots in your project. In the above example, the snapshot will be named "Button".

The function may include any necessary setup, but must ultimately return the React markup to snapshot.

Responsive snapshot configuration

percySnapshot(name, options, fn)

A powerful feature of Percy is visual regression testing for responsive designs. To take snapshots of your component at multiple breakpoints, simply specify the widths to use via the optional middle options argument:

percySnapshot('Button', { widths: [320, 768, 1024] }, () => {
  return <Button>My Button</Button>;
});

This will take snapshots of "Button" at widths of 320px, 768px, and 1024px.

Hooks

In some cases while writing Percy snapshots you may have some setup work that needs to happen before snapshots run. This is often useful for setting up props that you'd like to share across multiple snapshots.

For example, suppose you had a PlayerInfo component with multiples variations that share many of the same props. You could initialize the common props in beforeEach and use them in each snapshot:

let props;

beforeEach(() => {
  props = {
    firstName: 'Venus',
    lastName: 'Williams',
    age: 37
  };
});

percySnapshot('PlayerInfo: basic', () => {
  return <PlayerInfo {...props} />;
});

percySnapshot('PlayerInfo: expanded', () => {
  return <PlayerInfo {...props} country="USA" sport="Tennis" />;
});

Grouping snapshots

You may also group snapshots together using a suite block.

suite blocks have the added benefit of prefixing the names of all snapshots within them. For example:

suite('Button', () => {
  percySnapshot('primary', () => {
    return <Button primary>Primary Button</Button>;
  });

  percySnapshot('secondary', () => {
    return <Button secondary>Secondary Button</Button>;
  });

  percySnapshot('inverse', () => {
    return <Button inverse>Inverse Button</Button>;
  });
});

This will generate three snapshots: Button: primary, Button: secondary, and Button: inverse.

You can also set the widths for all snapshots within a suite block:

suite('Button', { widths: [320, 768] }, () => {
  percySnapshot('primary', () => {
    return <Button primary>Primary Button</Button>;
  });

  percySnapshot('secondary', () => {
    return <Button secondary>Secondary Button</Button>;
  });

  percySnapshot('inverse', () => {
    return <Button inverse>Inverse Button</Button>;
  });
});

This will generate snapshots of each button at both 320px and 768px wide.

You can override the widths specified on a suite on individual snapshots:

suite('Button', { widths: [320, 768] }, () => {
  percySnapshot('primary', () => {
    return <Button primary>Primary Button</Button>;
  });

  percySnapshot('secondary', () => {
    return <Button secondary>Secondary Button</Button>;
  });

  percySnapshot('inverse', { widths: [480, 1024] }, () => {
    return <Button inverse>Inverse Button</Button>;
  });
});

This will generate snapshots of Button: primary and Button: secondary at 320px and 768px wide, and "Button: inverse" at 480px and 1024px wide.

When they are inside a suite block, beforeEach blocks only apply to the snapshots within that suite block:

beforeEach(() => {
  // this will run before each snapshot in this file
});

suite('Button', () => {
  beforeEach(() => {
    // this will only run before "Button: primary" and "Button: secondary"
  });

  percySnapshot('primary', () => {
    return <Button primary>Primary Button</Button>;
  });

  percySnapshot('secondary', () => {
    return <Button secondary>Secondary Button</Button>;
  });
});

suite('Textbox', () => {
  beforeEach(() => {
    // this will only run before "Textbox: disabled"
  });

  percySnapshot('disabled', () => {
    return <Textbox disabled />;
  });
});

Additionally, you can nest suite blocks within each other, and the names and widths will cascade down:

suite('Button', { widths: [320, 768] }, () => {
  percySnapshot('primary', () => {
    return <Button primary>Primary Button</Button>;
  });

  percySnapshot('secondary', () => {
    return <Button secondary>Secondary Button</Button>;
  });

  suite('submit', () => {
    percySnapshot('enabled', () => {
      return <Button submit>Submit Button</Button>;
    });

    percySnapshot('disabled', () => {
      return <Button submit disabled>Disabled Submit Button</Button>;
    });
  });
});

This will generate 320px and 768px wide snapshots of Button: primary, Button: secondary, Button: submit: enabled, and Button: submit: disabled.

Troubleshooting


Percy is rendering a blank white page

If you're seeing blank white snapshots in Percy, it usually means an error occurred client-side when loading your snapshot. To debug this, you can run react-percy locally with the --debug flag so you can load snapshots in your local browser.

$ react-percy --debug

After running react-percy with the --debug flag, you'll find a .percy-debug folder at the root of your project. Open the index.html file inside in your web browser and specify the snapshot you'd like to view as the snapshot query parameter.

For example, if you wanted to debug a snapshot named "Button: primary", you would open file:///path/to/project/.percy-debug/index.html in your browser and append ?snapshot=Button%3A%20primary to the URL. Note that the snapshot name is URL encoded. The console output of running react-percy with the --debug flag will give you the URL-encoded query strings to use to preview any snapshot.

Invariant Violation: Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined

This error typically occurs when you are incorrectly importing your component.

For example, if your component is exported as a named export, be sure to import it that way:

export class MyComponent extends React.Component {
  render() {
    return <div>My component</div>;
  }
}
import { MyComponent } from './MyComponent';

You may also see this error if you are mixing ES module imports/exports with CommonJS modules. For example, if your component is using an ES default export but your snapshot file is written in CommonJS, remember to append .default to your require import:

export default class MyComponent extends React.Component {
  render() {
    return <div>My component</div>;
  }
}
const MyComponent = require('./MyComponent').default;

What's next

Once you've installed Percy for React and added snapshots to your tests, the next step is to add Percy to your CI service.