QA Engineer Skills 2026QA-2026Commercial Visual Regression Tools

Commercial Visual Regression Tools

Percy (BrowserStack)

Percy captures screenshots across browsers and viewports, then highlights visual changes with smart diffing. It integrates with all major test frameworks and provides a web dashboard for reviewing and approving changes.

// tests/visual/homepage.spec.ts
import { test } from '@playwright/test';
import percySnapshot from '@percy/playwright';

test('homepage visual regression', async ({ page }) => {
    await page.goto('/');
    await page.waitForLoadState('networkidle');

    // Capture at multiple widths
    await percySnapshot(page, 'Homepage', {
        widths: [375, 768, 1280],
        minHeight: 1024,
        percyCSS: `
            /* Stabilize dynamic content for consistent screenshots */
            [data-testid="timestamp"] { visibility: hidden; }
            [data-testid="avatar"] { visibility: hidden; }
            .animated { animation: none !important; }
        `,
    });
});

test('product card visual regression', async ({ page }) => {
    await page.goto('/products');

    // Wait for images to load
    await page.waitForFunction(() => {
        const images = document.querySelectorAll('img');
        return Array.from(images).every(img => img.complete);
    });

    await percySnapshot(page, 'Product Listing', {
        widths: [375, 1280],
    });
});

test('checkout flow visual regression', async ({ page }) => {
    // Navigate through checkout steps
    await page.goto('/cart');
    await percySnapshot(page, 'Cart Page', { widths: [375, 1280] });

    await page.click('[data-testid="proceed-to-checkout"]');
    await percySnapshot(page, 'Checkout - Shipping', { widths: [375, 1280] });

    await page.fill('[data-testid="shipping-name"]', 'John Doe');
    await page.fill('[data-testid="shipping-address"]', '123 Main St');
    await page.click('[data-testid="continue-to-payment"]');
    await percySnapshot(page, 'Checkout - Payment', { widths: [375, 1280] });
});

Percy CI Configuration

# .github/workflows/percy.yml
name: Percy Visual Tests
on: [pull_request]

jobs:
  visual:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx playwright install --with-deps

      - name: Run Percy snapshots
        env:
          PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}
        run: npx percy exec -- npx playwright test tests/visual/

Percy's approval workflow: screenshots from the PR branch are compared against the base branch. Unchanged screenshots are auto-approved. Changed screenshots appear in the Percy dashboard for review. Approved changes become the new baseline when the PR merges.


Chromatic (Storybook)

Chromatic integrates directly with Storybook to test components in isolation. This catches visual regressions at the component level before they compound into page-level issues.

// Button.stories.tsx
import type { Meta, StoryObj } from '@storybook/react';
import { Button } from './Button';

const meta: Meta<typeof Button> = {
    title: 'Components/Button',
    component: Button,
    parameters: {
        // Chromatic-specific configuration
        chromatic: {
            viewports: [320, 768, 1200],
            delay: 300, // Wait for animations
            diffThreshold: 0.063, // Sensitivity (default is 0.063)
        },
    },
};
export default meta;

type Story = StoryObj<typeof Button>;

export const Primary: Story = {
    args: { variant: 'primary', children: 'Click me' },
};

export const Disabled: Story = {
    args: { variant: 'primary', children: 'Disabled', disabled: true },
};

export const Loading: Story = {
    args: { variant: 'primary', children: 'Loading', loading: true },
    parameters: {
        chromatic: {
            // Capture after loading spinner appears
            delay: 500,
        },
    },
};

export const AllVariants: Story = {
    render: () => (
        <div style={{ display: 'flex', gap: '1rem', flexWrap: 'wrap' }}>
            <Button variant="primary">Primary</Button>
            <Button variant="secondary">Secondary</Button>
            <Button variant="danger">Danger</Button>
            <Button variant="ghost">Ghost</Button>
            <Button size="sm">Small</Button>
            <Button size="lg">Large</Button>
        </div>
    ),
};

Chromatic CI Integration

# In your CI pipeline
- name: Run Chromatic
  uses: chromaui/action@latest
  with:
    projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
    exitOnceUploaded: true  # Don't wait for review
    onlyChanged: true       # Only snapshot changed stories
    traceChanged: true      # Detect which stories are affected by code changes

Applitools Eyes

Applitools uses a proprietary AI engine ("Visual AI") that provides the most sophisticated intelligent comparison. It can test across multiple browsers and devices simultaneously from a single test run.

// tests/visual/applitools-checkout.spec.ts
import { test } from '@playwright/test';
import {
    Eyes, Target, BatchInfo, Configuration,
    BrowserType, DeviceName, ScreenOrientation
} from '@applitools/eyes-playwright';

test.describe('Checkout Flow Visual Tests', () => {
    let eyes: Eyes;

    test.beforeEach(async () => {
        eyes = new Eyes();
        const config = new Configuration();
        config.setBatch(new BatchInfo('Checkout Flow'));

        // Test across multiple browsers and devices simultaneously
        config.addBrowser(1200, 800, BrowserType.CHROME);
        config.addBrowser(1200, 800, BrowserType.FIREFOX);
        config.addBrowser(1200, 800, BrowserType.SAFARI);
        config.addDeviceEmulation(DeviceName.iPhone_15, ScreenOrientation.PORTRAIT);
        config.addDeviceEmulation(DeviceName.Pixel_7, ScreenOrientation.PORTRAIT);

        eyes.setConfiguration(config);
    });

    test('cart page visual check', async ({ page }) => {
        await eyes.open(page, 'E-Commerce', 'Cart Page');
        await page.goto('/cart');

        // Full page check with layout matching (ignores text content changes)
        await eyes.check('Cart with items', Target.window()
            .fully()
            .layout()  // Match layout, not exact content
            .ignoreDisplacements()  // Ignore minor position shifts
        );

        // Specific region check with strict matching
        await eyes.check('Price total', Target.region('#order-summary')
            .strict()  // Exact match for financial data
        );

        await eyes.close();
    });

    test.afterEach(async () => {
        await eyes.abort();
    });
});

Applitools Match Levels

Match Level What It Compares Use Case
Strict Pixel-level, AI-filtered Financial data, legal text, exact content
Layout Element positions, sizes, relationships Pages with dynamic content (user names, dates)
Content Text content and structure Pages where styling may vary but content matters
Ignore colors Layout + content, ignoring color changes Theming changes, dark mode testing

Tool Comparison Matrix

Feature Percy Chromatic Applitools
AI-powered diff Basic Basic Advanced (Visual AI)
Cross-browser rendering Yes (cloud rendering) Yes (cloud rendering) Yes (Ultrafast Grid)
Storybook integration Via Percy CLI Native (built for Storybook) Via SDK
Playwright integration Native Via Storybook Native SDK
Match sensitivity control Threshold percentage Threshold percentage Multiple match levels
Approval workflow Web dashboard Web dashboard Web dashboard
Free tier 5,000 snapshots/month 5,000 snapshots/month Limited
Best for Full-page testing, multiple frameworks Storybook-centric workflows Enterprise, cross-browser at scale
Pricing Per snapshot Per snapshot Per checkpoint

Choose based on your workflow: Chromatic if you use Storybook extensively, Percy if you need broad framework support, and Applitools if you need the most sophisticated AI comparison and cross-browser rendering.