CtrlK
BlogDocsLog inGet started
Tessl Logo

visual-testing

Visual regression testing with Chromatic, Lost Pixel, and Playwright snapshots. Use when detecting UI changes, maintaining visual consistency, reviewing design changes, or setting up screenshot comparison in CI/CD.

61

Quality

72%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Fix and improve this skill with Tessl

tessl review fix ./.claude/skills/visual-testing/SKILL.md

The canonical home for this skill is visual-testing in fernandezbaptiste/Skrillz

SKILL.md
Quality
Evals
Security

Visual Testing

Overview

Visual regression testing catches unintended UI changes by comparing screenshots. This skill covers three approaches:

  1. Chromatic - Cloud-based, unlimited parallelization (recommended)
  2. Lost Pixel - Self-hosted, open source alternative
  3. Playwright Built-in - Simple snapshot testing

Quick Start: Chromatic (5 Minutes)

1. Install Chromatic

npm install --save-dev chromatic

2. Get Project Token

  1. Go to chromatic.com
  2. Sign up with GitHub (free tier: 5000 snapshots/month)
  3. Create project → Copy project token

3. Run First Build

# Set token
export CHROMATIC_PROJECT_TOKEN=your_token_here

# Run visual tests with Playwright
npx chromatic --playwright

4. Review Changes

  • Chromatic UI shows visual diffs
  • Approve or reject changes
  • Approved changes become new baselines

Chromatic with Playwright

Configuration

// playwright.config.ts
import { defineConfig } from '@playwright/test';

export default defineConfig({
  testDir: './tests/visual',
  use: {
    screenshot: 'on',
  },
  reporter: [
    ['html'],
    ['chromatic', { projectToken: process.env.CHROMATIC_PROJECT_TOKEN }],
  ],
});

Visual Test Example

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

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

  // Full page screenshot
  await expect(page).toHaveScreenshot('homepage.png');
});

test('dashboard visual', async ({ page }) => {
  await page.goto('/dashboard');

  // Wait for data to load
  await page.waitForSelector('.dashboard-loaded');

  // Component screenshot
  const chart = page.locator('.revenue-chart');
  await expect(chart).toHaveScreenshot('revenue-chart.png');
});

CI Integration (GitHub Actions)

# .github/workflows/visual.yml
name: Visual Tests
on: [push, pull_request]

jobs:
  visual:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0  # Required for Chromatic
      - uses: actions/setup-node@v4
        with:
          node-version: 20
      - run: npm ci
      - run: npx playwright install --with-deps
      - run: npx chromatic --playwright
        env:
          CHROMATIC_PROJECT_TOKEN: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}

Chromatic Features (Free Tier)

FeatureFree Tier
Snapshots5000/month
ParallelizationUnlimited
BrowsersChrome, Firefox, Safari
Review UIYes
GitHub IntegrationYes

Lost Pixel (Self-Hosted)

For confidential projects that can't use cloud services.

Installation

npm install --save-dev lost-pixel

Configuration

// lostpixel.config.ts
import { CustomProjectConfig } from 'lost-pixel';

export const config: CustomProjectConfig = {
  pageShots: {
    pages: [
      { path: '/', name: 'home' },
      { path: '/login', name: 'login' },
      { path: '/dashboard', name: 'dashboard' },
    ],
    baseUrl: 'http://localhost:3000',
  },
  // Store baselines locally
  imagePathBaseline: '.lostpixel/baseline',
  imagePathCurrent: '.lostpixel/current',
  imagePathDifference: '.lostpixel/difference',

  // Fail on difference
  generateOnly: false,
  failOnDifference: true,

  // Comparison settings
  threshold: 0.1,  // 0.1% pixel difference allowed
};

Run Lost Pixel

# Generate baselines (first run)
npx lost-pixel update

# Run comparison
npx lost-pixel

# View differences in .lostpixel/difference/

Docker Setup (for CI)

# Dockerfile.lostpixel
FROM mcr.microsoft.com/playwright:v1.40.0-focal

WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY . .

CMD ["npx", "lost-pixel"]
# .github/workflows/visual-selfhosted.yml
jobs:
  visual:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - run: docker build -f Dockerfile.lostpixel -t visual-tests .
      - run: docker run visual-tests

Playwright Built-in Snapshots

Simplest approach, no external services.

Basic Usage

// tests/visual/basic.spec.ts
import { test, expect } from '@playwright/test';

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

  // Full page
  await expect(page).toHaveScreenshot('homepage.png');

  // With options
  await expect(page).toHaveScreenshot('homepage-full.png', {
    fullPage: true,
    maxDiffPixels: 100,  // Allow 100 pixel difference
  });

  // Specific element
  const header = page.locator('header');
  await expect(header).toHaveScreenshot('header.png');
});

Update Baselines

# Update all baselines
npx playwright test --update-snapshots

# Update specific test
npx playwright test tests/visual/homepage.spec.ts --update-snapshots

Configuration

// playwright.config.ts
export default defineConfig({
  expect: {
    toHaveScreenshot: {
      maxDiffPixels: 100,
      maxDiffPixelRatio: 0.01,  // 1%
      threshold: 0.2,  // Color difference threshold
    },
  },
  snapshotPathTemplate: '{testDir}/__screenshots__/{testFilePath}/{arg}{ext}',
});

Cross-Platform Issues

Problem: Screenshots differ between Mac/Linux/Windows.

Solutions:

  1. Use Docker (recommended):
# Run tests in consistent environment
docker run --rm -v $(pwd):/app mcr.microsoft.com/playwright:v1.40.0 \
  npx playwright test --update-snapshots
  1. Platform-specific baselines:
// playwright.config.ts
export default defineConfig({
  snapshotPathTemplate: '{testDir}/__screenshots__/{platform}/{testFilePath}/{arg}{ext}',
});
  1. Increase threshold:
await expect(page).toHaveScreenshot('page.png', {
  maxDiffPixelRatio: 0.05,  // 5% tolerance
});

Comparison Matrix

FeatureChromaticLost PixelPlaywright
Free Tier5000 snapshotsUnlimitedUnlimited
Self-HostedNoYesYes
ParallelizationUnlimitedManualManual
Review UIYes (web)No (local files)No (local files)
Cross-BrowserYesYesYes
AI DetectionYesNoNo
Setup Time5 min15 min5 min
Best ForTeams, CI/CDConfidentialSimple projects

Best Practices

1. Stable Selectors

// Wait for dynamic content
await page.waitForSelector('[data-loaded="true"]');
await page.waitForLoadState('networkidle');

// Hide dynamic elements
await page.evaluate(() => {
  document.querySelectorAll('.timestamp').forEach(el => el.style.visibility = 'hidden');
});

await expect(page).toHaveScreenshot('page.png');

2. Consistent Viewport

// playwright.config.ts
export default defineConfig({
  use: {
    viewport: { width: 1280, height: 720 },
  },
});

// Or per-test
test('mobile view', async ({ page }) => {
  await page.setViewportSize({ width: 375, height: 667 });
  await expect(page).toHaveScreenshot('mobile.png');
});

3. Mock Time/Data

// Freeze time
await page.addInitScript(() => {
  Date.now = () => new Date('2024-01-15T10:00:00').getTime();
});

// Mock API responses
await page.route('**/api/user', route => {
  route.fulfill({
    json: { name: 'Test User', avatar: '/default-avatar.png' },
  });
});

await expect(page).toHaveScreenshot('profile.png');

4. Component-Level Testing

// Test individual components
test('button states', async ({ page }) => {
  await page.goto('/storybook/button');

  const button = page.locator('.button');

  // Default state
  await expect(button).toHaveScreenshot('button-default.png');

  // Hover state
  await button.hover();
  await expect(button).toHaveScreenshot('button-hover.png');

  // Active state
  await button.click({ force: true });
  await expect(button).toHaveScreenshot('button-active.png');
});

Workflow: Visual Testing in CI

PR Workflow

1. Developer pushes code
   ↓
2. CI runs visual tests
   ↓
3. Chromatic detects changes
   ↓
4. Review visual diff in Chromatic UI
   ↓
5. Approve → New baseline
   Reject → Fix and re-push
   ↓
6. Merge PR

Baseline Management

# Update baselines locally
npx playwright test --update-snapshots

# Commit baselines to git
git add tests/__screenshots__/
git commit -m "Update visual baselines"

# Or use Chromatic (baselines stored in cloud)
npx chromatic --auto-accept-changes  # Accept all changes

References

  • references/chromatic-setup.md - Complete Chromatic configuration
  • references/lost-pixel-self-hosted.md - Self-hosted setup guide
  • references/playwright-snapshots.md - Built-in snapshot testing

Visual testing catches UI regressions before users do - choose Chromatic for teams, Lost Pixel for confidential projects.

Repository
fernandezbaptiste/Skrillz
Last updated
First committed

Canonical home

fernandezbaptiste/Skrillz
In sync

since Sep 12, 2026

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.