Percy vs Applitools vs Chromatic: Visual Regression Tools Compared
Percy vs Applitools vs Chromatic visual regression comparison for QA teams choosing screenshot, AI, or Storybook review workflows before release.
Percy vs Applitools vs Chromatic: Visual Regression Tools Compared
Percy vs Applitools vs Chromatic is a choice between three visual regression workflows: Percy focuses on screenshot diffs for web apps, Applitools focuses on AI-assisted visual validation across apps and browsers, and Chromatic focuses on Storybook component review plus visual regression. Pick based on what you need to protect: deployed pages, cross-platform user journeys, or component states.
For QA engineers, the best tool is the one that catches meaningful visual regressions without turning every spacing change into an approval meeting. That depends on baseline ownership, test entry point, component coverage, browser needs, and how your developers review changes.
The Short Decision
If your team already uses Storybook heavily, Chromatic is usually the fastest path to useful component-level visual regression. If you need broad app-page screenshot coverage tied to CI and you are already in the BrowserStack ecosystem, Percy is straightforward. If you need advanced visual matching across complex journeys, dynamic regions, PDFs, mobile, or enterprise review workflows, Applitools deserves a serious trial.
That is the practical answer. The deeper answer is that these tools are not exact substitutes. They overlap on screenshots and baselines, but they enter the product at different layers.
| Tool | Best entry point | Strongest fit | Watch-outs |
|---|---|---|---|
| Percy | Browser automation and snapshots | Web page regression in CI | Needs disciplined snapshot scope |
| Applitools | SDK checkpoints during tests | Complex visual validation across surfaces | More configuration choices to govern |
| Chromatic | Storybook stories | Component state review and design systems | Weak if Storybook coverage is thin |
Official docs are the right place for current SDK details: https://www.browserstack.com/docs/percy, https://applitools.com/docs/, and https://www.chromatic.com/docs/. Tool vendors change package names, examples, and plan details over time, so pin your implementation to the docs and your lockfile, not a blog snippet alone.
For focused setup detail, use a Percy visual testing complete guide when Percy is the candidate, and a Chromatic Storybook visual testing guide when component review is the main target.
What Each Tool Is Really Optimizing For
Visual regression tools all compare images, but the workflow around the image is the product. Percy wants you to take snapshots at meaningful points during browser tests or static builds. Applitools wants you to define visual checkpoints and let its matching system separate important differences from noise. Chromatic wants Storybook stories to become the visual source of truth for component states.
| Workflow question | Percy | Applitools | Chromatic |
|---|---|---|---|
| Where do snapshots come from? | Web app tests or static pages | Test checkpoints across supported SDKs | Storybook stories |
| Who reviews changes? | Developers and QA in Percy UI | Developers, QA, design, release teams | Developers and designers around Storybook |
| What is the natural unit? | Page or selected region | Checkpoint, window, page, component, app state | Story |
| What does it reward? | Clear snapshot naming and stable states | Intentional match levels and ignore regions | Complete story coverage |
| What makes it fail badly? | Too many broad snapshots | Poorly governed match settings | Stories that do not represent production states |
The wrong way to evaluate these tools is to run the home page once in each and compare screenshots. That tests almost nothing. Use a real flow with dynamic data, a loading state, a responsive breakpoint, and one deliberate visual bug. Then see which tool gives the cleanest signal and review path.
Coverage Layer: Pages, Flows, or Components
Start with the layer you are most afraid of breaking. Marketing pages, dashboards, and checkout flows benefit from page-level screenshots. Design systems and shared components benefit from Storybook states. Regulated or high-value flows may need both component and page coverage because a component can pass alone and fail inside real layout.
| Coverage target | Better first choice | Reason |
|---|---|---|
| Storybook design system | Chromatic | Stories are already the coverage surface |
| Checkout and account flows | Percy or Applitools | Browser flow state matters |
| Dynamic enterprise dashboard | Applitools | Ignore regions and smarter matching may reduce noise |
| Static content pages | Percy | Simple snapshots are often enough |
| Component library with design review | Chromatic | Review fits pull-request component changes |
A Playwright screenshot baseline with no service can still catch local changes, but hosted visual tools solve review, storage, baseline approvals, collaboration, and CI summaries. That is what you are paying for.
Here is a plain Playwright screenshot test that shows the minimum concept before a vendor SDK enters:
import { test, expect } from '@playwright/test';
test('checkout summary stays visually stable', async ({ page }) => {
await page.goto('/checkout/summary?fixture=visual');
await expect(page.getByTestId('checkout-summary')).toHaveScreenshot(
'checkout-summary.png',
{
animations: 'disabled',
caret: 'hide'
}
);
});
That works for small teams, but it becomes painful when designers need to review baselines or when multiple branches touch the same surfaces. Percy, Applitools, and Chromatic earn their keep when visual review becomes a shared workflow, not just a test assertion.
Percy: Strengths, Weaknesses, and Test Shape
Percy is a good fit when you want page and region snapshots from browser tests without designing a large visual AI program. It is especially practical for QA teams already running Playwright, Cypress, Selenium, or WebdriverIO against stable test data.
A good Percy suite names snapshots by product meaning, not route alone. "Billing settings with tax ID error" is better than "settings page." The reviewer should know what changed before opening the diff.
import { test } from '@playwright/test';
import percySnapshot from '@percy/playwright';
test('billing settings visual states', async ({ page }) => {
await page.goto('/settings/billing?fixture=visual');
await percySnapshot(page, 'Billing settings - default');
await page.getByRole('button', { name: 'Add tax ID' }).click();
await page.getByLabel('Tax ID').fill('bad-value');
await page.getByRole('button', { name: 'Save tax ID' }).click();
await percySnapshot(page, 'Billing settings - tax ID error');
});
Percy works best when you freeze volatile data. Dates, avatars, random recommendations, ads, third-party widgets, and live charts can turn every run into noise. You can hide or stabilize dynamic regions in the app, route fixtures, or test setup. The cleanest approach is usually deterministic test data, because it keeps the screenshot honest.
Percy's weak spot is not that it lacks value. It is that teams take too many broad page snapshots and drown reviewers. Be selective. Snapshot states that matter: permission dialogs, empty states, form errors, responsive navigation, checkout totals, data-heavy tables, and layout boundaries that developers frequently break.
Applitools: Strengths, Weaknesses, and Test Shape
Applitools is strongest when the visual question is more nuanced than pixel equality. Its SDKs and matching modes are designed for teams that need to compare app states while tolerating expected rendering differences. That can be valuable in cross-browser testing, enterprise apps with dynamic regions, and suites that include more than simple web pages.
A typical test places visual checkpoints inside an existing functional flow:
import { test } from '@playwright/test';
import { Eyes, Target } from '@applitools/eyes-playwright';
test('account overview visual checkpoint', async ({ page }) => {
const eyes = new Eyes();
await eyes.open(page, 'Customer Portal', 'Account overview');
await page.goto('/account?fixture=visual');
await eyes.check('Overview loaded', Target.window().fully());
await page.getByRole('tab', { name: 'Invoices' }).click();
await eyes.check('Invoices tab', Target.window().fully());
await eyes.close();
});
The exact setup depends on the current SDK docs and your runner. The important pattern is stable: open a visual session, drive the app to meaningful states, checkpoint those states, then close the session so results are reported.
Applitools gives teams more control, which means more governance. Match levels, ignore regions, layout regions, branch baselines, and batch naming should be documented. Without rules, one team turns matching strict and blocks harmless antialiasing, while another ignores entire panels and misses real bugs.
Use Applitools when visual validation is a first-class QA discipline, not just a sidecar. If all you need is a few page screenshots on PRs, the extra power may be more than you want to operate.
Chromatic: Strengths, Weaknesses, and Test Shape
Chromatic is different because Storybook is the center. That is excellent when components are the product surface: design systems, shared UI packages, marketing blocks, checkout widgets, and reusable forms. It is weaker when the bug only appears after routing, real data loading, auth, or several user actions.
Chromatic rewards complete stories. If a button has default, hover-like, loading, disabled, destructive, and long-label states, those states need stories. If a data table has empty, loading, error, one row, many rows, and overflow states, those need stories too.
import type { Meta, StoryObj } from '@storybook/react';
import { InvoiceTable } from './InvoiceTable';
const meta = {
component: InvoiceTable,
title: 'Billing/InvoiceTable'
} satisfies Meta<typeof InvoiceTable>;
export default meta;
type Story = StoryObj<typeof meta>;
export const WithOverdueInvoice: Story = {
args: {
invoices: [
{
id: 'inv-101',
customer: 'ACME Ltd',
amount: '$320.00',
status: 'Overdue'
}
]
}
};
export const Empty: Story = {
args: {
invoices: []
}
};
The CI command is simple once the project is connected, but exact tokens and project setup belong in Chromatic's current docs.
name: chromatic
on:
pull_request:
jobs:
visual:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm run build-storybook
- run: npx chromatic --project-token=${{ secrets.CHROMATIC_PROJECT_TOKEN }}
Chromatic's biggest weakness is inherited from Storybook coverage. If developers only write happy-path stories, your visual suite only protects happy paths. QA should review the story inventory the same way they review test cases.
Baseline Governance and Review Workflows
Visual regression fails when nobody owns baseline changes. A baseline is a claim that "this is now acceptable." Treat it with the same seriousness as approving a test expectation.
| Governance rule | Why it matters |
|---|---|
| Name snapshots by user-visible state | Reviewers can judge intent |
| Require design review for shared components | QA should not approve design system changes alone |
| Keep dynamic data deterministic | Reduces false diffs |
| Use ignore regions sparingly | Prevents hiding real regressions |
| Track accepted diffs | Accepted changes are product decisions |
Percy and Chromatic often fit PR review loops where developers approve expected changes and designers review sensitive components. Applitools often appears in more formal QA flows with batches, environments, and cross-browser checkpoints. Those are tendencies, not laws.
One practical policy: if a diff changes layout, spacing, color, typography, or hierarchy in a shared component, the code reviewer should not approve the baseline alone unless they own design quality. If a diff changes dynamic content that should have been frozen, reject the baseline and fix the fixture.
Dynamic Data and False Positives
False positives are not harmless. They train teams to rubber-stamp visual diffs. The best visual suites aggressively remove randomness and only ignore areas that cannot be made deterministic.
Use this priority order:
| Problem | Preferred fix | Last resort |
|---|---|---|
| Current date changes | Inject fixed test clock | Ignore tiny date region |
| User avatars change | Use fixture avatars | Mask avatar region |
| Chart animation differs | Disable animation or use fixed data | Snapshot final stable state |
| Ads or third-party widgets | Disable in test env | Mask widget area |
| Font rendering differs | Pin environment and fonts | Use less strict matching if tool supports it |
A visual helper can wait for app readiness before snapshotting:
import type { Page } from '@playwright/test';
export async function prepareVisualState(page: Page) {
await page.addStyleTag({
content: [
'* { animation-duration: 0s !important; }',
'* { transition-duration: 0s !important; }',
'[data-visual-ignore="true"] { visibility: hidden !important; }'
].join('\n')
});
await page.waitForLoadState('networkidle');
await page.getByTestId('app-ready').waitFor();
}
That helper does not fake the product. It removes timing noise and waits for a deliberate readiness signal. If the app never becomes ready, the test should fail. A screenshot of half-loaded UI is usually not useful evidence.
AI Matching Versus Pixel Diff
Pixel diffs are easy to understand. AI-assisted matching can reduce noise and catch layout issues that raw pixels express poorly. The tradeoff is explainability and configuration discipline. QA engineers should be comfortable asking, "What exact difference will this setting ignore?"
| Difference | Pixel diff behavior | AI-assisted matching behavior |
|---|---|---|
| One-pixel font smoothing | Often noisy | Often tolerated depending on settings |
| Button shifted below fold | Clear diff | Clear layout difference |
| Text changed but same shape | May be subtle | Depends on content and match mode |
| Dynamic table rows | Very noisy | Can be managed with regions or layout matching |
| Missing icon | Clear if visible | Clear if checkpoint covers it |
My opinion: use stricter checks for critical transactional UI and looser layout-aware checks for data-heavy screens where content changes constantly. Do not make the whole suite permissive because one dashboard is noisy. That is how a missing submit button becomes an "accepted visual variance."
A Failure Story: The Approved Diff That Was a Bug
Symptom: a product card lost its price label in production. The visual test had caught a diff, but it was approved during a busy release week.
Wrong theory: the team blamed the visual tool for being noisy. They said nobody could inspect hundreds of diffs, so the process was doomed.
Actual cause: the suite snapshotted the entire catalog page with live product data. Every run changed badges, images, positions, and prices. Reviewers had been trained to approve noise. The missing price was one small change inside a wall of unrelated diffs.
Fix: QA replaced the broad live-data page snapshot with fixture-backed product card stories, one catalog page with deterministic data, and a policy that rejected baseline updates containing unrelated data churn. The next real price-label regression produced one focused diff and was fixed before merge.
The lesson is uncomfortable: a noisy visual suite is not neutral. It actively weakens review judgment.
Evaluation Plan Before You Buy
Run a two-week proof of concept against the same targets in each tool. Do not evaluate with toy pages. Use one stable component set, one dynamic dashboard, one critical flow, and one responsive layout.
| Evaluation item | What to measure |
|---|---|
| Setup time | Hours to first useful CI result |
| Signal quality | Real bugs caught versus false diffs |
| Review speed | Time to approve expected changes |
| Developer fit | Whether failures are understood in PRs |
| Design fit | Whether designers can review the right surfaces |
| Maintenance | How often snapshots need fixture updates |
Seed one deliberate bug in each target:
.checkout-total {
display: none;
}
.primary-action {
background: #6b7280;
}
.invoice-table th {
text-align: center;
}
Then ask three people to review the results: a QA engineer, a frontend developer, and a designer. The best tool is the one that produces the least argument about whether the failure matters.
CI Integration and Failure Ownership
Visual regression tools work best when CI failure policy matches the maturity of the suite. A brand-new visual suite should usually report changes without blocking every merge on day one. After fixtures are stable and ownership is clear, critical snapshots can become required checks. Blocking too early creates resentment. Never blocking creates wallpaper.
Use staged enforcement:
| Maturity | CI behavior | Owner action |
|---|---|---|
| Pilot | Non-blocking report | Tune fixtures and snapshot scope |
| Stabilizing | Block only critical surfaces | Assign reviewers for diffs |
| Mature | Block protected visual suites | Track noise and stale baselines |
| Degraded | Temporarily narrow scope | Fix causes before broadening again |
Percy and Chromatic often plug into pull requests as visual checks. Applitools can also integrate into CI, with results grouped into batches. The important policy is the same: a failed visual run needs an owner who can decide whether the difference is expected, a product bug, or test noise.
Here is a generic GitHub Actions shape that keeps functional tests and visual snapshots separate. The exact vendor command belongs to the tool you choose, but the split is useful across all three.
name: visual-regression
on:
pull_request:
jobs:
functional:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm test
visual:
needs: functional
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 22
- run: npm ci
- run: npm run test:visual
Separating the jobs makes ownership clear. A unit failure is not a visual baseline decision. A visual failure should carry screenshot evidence and a link to the review UI, not force developers to dig through generic logs.
Security and Privacy in Screenshots
Visual testing captures the UI, which means it can capture secrets, customer data, internal names, email addresses, tokens rendered by mistake, and private messages. QA teams should treat screenshots and traces as sensitive artifacts unless proven otherwise.
Create screenshot-safe fixtures:
| Sensitive data | Safer fixture |
|---|---|
| Real customer names | Fictional company names |
| Personal email addresses | Reserved test-domain addresses |
| Payment details | Provider-approved test values |
| Access tokens | Never render in UI |
| Private messages | Synthetic text with no real user content |
Masking regions can help, but it should not be the first fix for sensitive data. If the app displays production-like private data in test, your risk is bigger than the visual tool. Use seed data that is safe to show in PR comments, vendor dashboards, and artifacts.
Also check retention. A visual diff from a pull request can outlive the branch. That is useful for audit and debugging, but risky if screenshots contain private data. Before adopting any tool, ask where images are stored, who can view them, how long they remain, and how access is revoked for former contractors.
Component Coverage Inventory
For Chromatic, the coverage inventory is the product. For Percy and Applitools, it is still useful because page snapshots often miss component states that only appear under specific data. QA can maintain a small inventory that maps user risk to visual coverage.
| Component or surface | Required states | Coverage location |
|---|---|---|
| Primary button | Default, disabled, loading, destructive | Storybook |
| Invoice table | Empty, loading, overdue, overflow | Storybook and billing page |
| Checkout summary | Discount, tax error, long address | Browser flow |
| Navigation shell | Mobile menu, collapsed sidebar, active item | Storybook and app page |
| Alert banner | Info, warning, error, long text | Storybook |
This inventory prevents a common argument: developers say "visual tests cover that," while QA means page screenshots and design means component states. Name the state and the location. If a state has no story and no page checkpoint, it is uncovered.
A simple story-generation discipline helps:
import type { Meta, StoryObj } from '@storybook/react';
import { AlertBanner } from './AlertBanner';
const meta = {
component: AlertBanner,
title: 'Feedback/AlertBanner'
} satisfies Meta<typeof AlertBanner>;
export default meta;
type Story = StoryObj<typeof meta>;
export const LongError: Story = {
args: {
tone: 'error',
message: 'Payment authorization failed because the billing address needs review.'
}
};
Long text stories are underrated. They catch wrapping, overflow, clipped icons, and button crowding before a real localized string breaks the page.
What People Get Wrong in Tool Bakeoffs
The most common mistake is comparing default screenshots instead of comparing operating cost. Any of the three tools can catch an obvious CSS bug in a stable demo. The question is which one your team will still maintain six months later.
During a bakeoff, measure these chores:
| Chore | Why it predicts success |
|---|---|
| Adding a new meaningful state | Shows whether coverage will grow |
| Reviewing a noisy diff | Shows whether reviewers can separate signal from churn |
| Updating an expected baseline | Shows approval clarity |
| Debugging a CI-only diff | Shows artifact and environment quality |
| Removing obsolete snapshots | Shows maintenance friction |
Do not let the vendor demo drive the test. Bring your own ugly states: real dashboards, long names, feature flags, missing images, failed payments, loading skeletons, and mobile navigation. A visual tool that looks great on a polished landing page may struggle with the dense operational UI where your regressions actually happen.
My practical bias: start narrower than your ambition. Protect the ten surfaces that would embarrass the team if they broke. Make those reliable. Then expand. A small visual suite that everyone trusts beats a huge suite that everyone clicks through without reading.
Frequently Asked Questions
Is Percy better than Applitools for visual regression testing?
Percy is often better for straightforward web screenshot regression in CI, especially when teams already run browser tests and want a clean hosted review workflow. Applitools is often better when matching needs are more advanced, cross-browser differences are noisy, or the organization wants richer visual validation controls. The right answer depends on screenshot scope, dynamic content, review ownership, and how much configuration your team can govern well.
When should I choose Chromatic instead of Percy or Applitools?
Choose Chromatic when Storybook is already a serious part of development and component states are the main surface you need to protect. It is excellent for design systems and reusable UI packages. It is not a replacement for full-flow visual checks when bugs depend on routing, auth, API data, or several user actions. Many teams use Chromatic for components and another tool for critical pages.
Do visual regression tools replace functional end-to-end tests?
No. Visual regression tools can catch layout, styling, and content presentation changes, but they do not prove business logic by themselves. A checkout screenshot might show the right total, but a functional test should still assert the API call, order creation, and error handling. The strongest suites combine functional assertions with targeted visual checkpoints at states users actually care about.
How do I reduce false positives in visual tests?
Start with deterministic data, fixed clocks, disabled animations, stable fonts, and deliberate app-ready signals. Use ignore or mask regions only when the content cannot reasonably be stabilized. Keep snapshots focused on meaningful states instead of capturing every page at full width. Most false-positive problems come from broad screenshots of live data, not from the visual tool itself or its reviewer UI.