К основному содержанию
скилл CX Codex

visual-regression-tester — скилл для Claude Code: визуальные

Скилл настраивает визуальное регрессионное тестирование: снимает скриншоты, сравнивает их с базовыми и ловит случайные изменения интерфейса.

Установить в Claude Code · Через Skills CLI (Vercel) Все способы и платформы ↓
npx skills add patricio0312rev/skills --skill visual-regression-tester -a claude-code
Открыть на GitHub

Что делает visual-regression-tester

Скилл проводит агента по шагам: выбрать инструмент (Playwright, Chromatic или Percy), снять базовые скриншоты, задать порог допустимых отличий, подключить проверку в CI, разобрать найденные расхождения и обновить базы после намеренных правок.

Для Playwright скилл даёт готовую конфигурацию playwright.config.ts: каталог тестов, шаблон путей к снимкам, пороги maxDiffPixels и maxDiffPixelRatio, отключение анимаций. В примерах — полная страница, отдельные секции вроде hero и футера, состояния кнопок и полей, четыре вьюпорта от мобильного до широкого, тёмная тема. Тесты опираются на локаторы вида [data-testid="hero-section"], поэтому разметку придётся подготовить заранее.

Перед запуском нужен Node.js и установка @playwright/test с браузерами. Скилл советует начинать с порога maxDiffPixels: 100 и threshold: 0.2 — на живом проекте эти значения часто приходится поднимать, потому что шрифты и картинки грузятся не мгновенно.

Какие задачи решает

  • поймать случайное изменение вёрстки после правки стилей
  • сравнить скриншоты страницы до и после релиза
  • настроить проверку интерфейса в CI
  • протестировать состояния кнопок и полей формы
  • снять снимки страницы на мобильном, планшете и десктопе

Примеры запросов агенту

  • настрой визуальное регрессионное тестирование через Playwright
  • добавь визуальные тесты для главной страницы и навигации
  • подключи visual diff в CI и обнови базовые скриншоты

Как установить visual-regression-tester

1Через Skills CLI (Vercel)

npx skills add patricio0312rev/skills --skill visual-regression-tester -a claude-code

2Вручную: положить файлы

~/.claude/skills/visual-regression-tester/SKILL.md

Папка скилла целиком; для одного проекта — .claude/skills/visual-regression-tester/

По шагам, с командами для каждой платформы: Как установить скилл в Claude Code: пять способов

Вопросы о visual-regression-tester

Как установить visual-regression-tester в Claude?

Выполните npx skills add patricio0312rev/skills --skill visual-regression-tester -a claude-code. Скилл окажется в ~/.claude/skills/visual-regression-tester/SKILL.md.

Что делает visual-regression-tester?

Помогает настроить визуальное регрессионное тестирование: базовые скриншоты, сравнение с порогом отличий, разбор диффов и подключение к CI на Playwright или Chromatic.

Какие инструменты поддерживает скилл?

В основе Playwright, также упомянуты Chromatic и Percy. Для Playwright в скилле есть готовая конфигурация и примеры тестов.

Исходный текст

SKILL.md · MIT
---
name: visual-regression-tester
description: Implements visual regression testing with screenshot comparison, diff detection, and CI integration using Playwright or Chromatic. Use when users request "visual testing", "screenshot testing", "UI regression", "visual diff", or "Chromatic setup".
---

# Visual Regression Tester

Catch unintended UI changes with automated visual regression testing.

## Core Workflow

1. **Choose tool**: Playwright, Chromatic, Percy
2. **Setup baseline**: Capture initial screenshots
3. **Configure thresholds**: Define acceptable diff
4. **Integrate CI**: Automated testing
5. **Review changes**: Approve or reject
6. **Update baselines**: Accept intentional changes

## Playwright Visual Testing

### Installation

```bash
npm install -D @playwright/test
npx playwright install
```

### Configuration

```typescript
// playwright.config.ts
import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  testDir: './tests/visual',
  testMatch: '**/*.visual.ts',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: [
    ['html', { open: 'never' }],
    ['json', { outputFile: 'test-results/results.json' }],
  ],

  // Snapshot configuration
  snapshotDir: './tests/visual/__snapshots__',
  snapshotPathTemplate: '{snapshotDir}/{testFilePath}/{arg}{ext}',

  expect: {
    toHaveScreenshot: {
      maxDiffPixels: 100,
      maxDiffPixelRatio: 0.01,
      threshold: 0.2,
      animations: 'disabled',
    },
    toMatchSnapshot: {
      maxDiffPixelRatio: 0.01,
    },
  },

  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
    video: 'retain-on-failure',
  },

  projects: [
    {
      name: 'Desktop Chrome',
      use: {
        ...devices['Desktop Chrome'],
        viewport: { width: 1280, height: 720 },
      },
    },
    {
      name: 'Desktop Firefox',
      use: {
        ...devices['Desktop Firefox'],
        viewport: { width: 1280, height: 720 },
      },
    },
    {
      name: 'Mobile Safari',
      use: {
        ...devices['iPhone 13'],
      },
    },
    {
      name: 'Tablet',
      use: {
        viewport: { width: 768, height: 1024 },
      },
    },
  ],

  webServer: {
    command: 'npm run start',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
    timeout: 120 * 1000,
  },
});
```

### Visual Test Examples

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

test.describe('Homepage Visual Tests', () => {
  test.beforeEach(async ({ page }) => {
    await page.goto('/');
    // Wait for fonts and images to load
    await page.waitForLoadState('networkidle');
    // Disable animations for consistent screenshots
    await page.addStyleTag({
      content: `
        *, *::before, *::after {
          animation-duration: 0s !important;
          animation-delay: 0s !important;
          transition-duration: 0s !important;
        }
      `,
    });
  });

  test('full page screenshot', async ({ page }) => {
    await expect(page).toHaveScreenshot('homepage-full.png', {
      fullPage: true,
    });
  });

  test('hero section', async ({ page }) => {
    const hero = page.locator('[data-testid="hero-section"]');
    await expect(hero).toHaveScreenshot('hero-section.png');
  });

  test('navigation bar', async ({ page }) => {
    const nav = page.locator('nav');
    await expect(nav).toHaveScreenshot('navigation.png');
  });

  test('footer', async ({ page }) => {
    const footer = page.locator('footer');
    await footer.scrollIntoViewIfNeeded();
    await expect(footer).toHaveScreenshot('footer.png');
  });
});
```

### Component Visual Tests

```typescript
// tests/visual/components.visual.ts
import { test, expect } from '@playwright/test';

test.describe('Button Component', () => {
  test('all variants', async ({ page }) => {
    await page.goto('/storybook/button');

    // Primary button
    const primary = page.locator('[data-testid="button-primary"]');
    await expect(primary).toHaveScreenshot('button-primary.png');

    // Secondary button
    const secondary = page.locator('[data-testid="button-secondary"]');
    await expect(secondary).toHaveScreenshot('button-secondary.png');

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

    // Focus state
    await primary.focus();
    await expect(primary).toHaveScreenshot('button-primary-focus.png');

    // Disabled state
    const disabled = page.locator('[data-testid="button-disabled"]');
    await expect(disabled).toHaveScreenshot('button-disabled.png');
  });
});

test.describe('Form Components', () => {
  test('input states', async ({ page }) => {
    await page.goto('/storybook/input');

    const input = page.locator('[data-testid="input-default"]');

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

    // Focused state
    await input.focus();
    await expect(input).toHaveScreenshot('input-focused.png');

    // With value
    await input.fill('Test value');
    await expect(input).toHaveScreenshot('input-with-value.png');

    // Error state
    const errorInput = page.locator('[data-testid="input-error"]');
    await expect(errorInput).toHaveScreenshot('input-error.png');
  });
});
```

### Responsive Testing

```typescript
// tests/visual/responsive.visual.ts
import { test, expect, devices } from '@playwright/test';

const viewports = [
  { name: 'mobile', width: 375, height: 667 },
  { name: 'tablet', width: 768, height: 1024 },
  { name: 'desktop', width: 1280, height: 720 },
  { name: 'wide', width: 1920, height: 1080 },
];

for (const viewport of viewports) {
  test.describe(`${viewport.name} viewport`, () => {
    test.use({ viewport: { width: viewport.width, height: viewport.height } });

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

      await expect(page).toHaveScreenshot(`homepage-${viewport.name}.png`, {
        fullPage: true,
      });
    });

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

      if (viewport.width < 768) {
        // Mobile: test hamburger menu
        const menuButton = page.locator('[data-testid="mobile-menu-button"]');
        await menuButton.click();
        await expect(page.locator('[data-testid="mobile-menu"]')).toHaveScreenshot(
          `mobile-menu-${viewport.name}.png`
        );
      } else {
        // Desktop: test full nav
        await expect(page.locator('nav')).toHaveScreenshot(
          `nav-${viewport.name}.png`
        );
      }
    });
  });
}
```

### Dark Mode Testing

```typescript
// tests/visual/dark-mode.visual.ts
import { test, expect } from '@playwright/test';

test.describe('Dark Mode', () => {
  test('homepage in dark mode', async ({ page }) => {
    await page.goto('/');

    // Enable dark mode via color scheme
    await page.emulateMedia({ colorScheme: 'dark' });

    await expect(page).toHaveScreenshot('homepage-dark.png', {
      fullPage: true,
    });
  });

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

    await page.emulateMedia({ colorScheme: 'light' });

    await expect(page).toHaveScreenshot('homepage-light.png', {
      fullPage: true,
    });
  });

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

    // Toggle theme via button
    const themeToggle = page.locator('[data-testid="theme-toggle"]');
    await themeToggle.click();

    // Wait for transition
    await page.waitForTimeout(300);

    await expect(page).toHaveScreenshot('homepage-toggled-theme.png');
  });
});
```

## Chromatic Integration

### Setup

```bash
npm install -D chromatic
```

### Configuration

```typescript
// .storybook/main.ts
export default {
  stories: ['../src/**/*.stories.@(js|jsx|ts|tsx)'],
  addons: ['@chromatic-com/storybook'],
};
```

### CI Configuration

```yaml
# .github/workflows/chromatic.yml
name: Chromatic

on:
  push:
    branches: [main]
  pull_request:
    branches: [main]

jobs:
  chromatic:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - run: npm ci

      - name: Publish to Chromatic
        uses: chromaui/action@latest
        with:
          projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
          exitZeroOnChanges: true
          exitOnceUploaded: true
          onlyChanged: true
```

### Chromatic Story Configuration

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

const meta: Meta<typeof Button> = {
  component: Button,
  parameters: {
    chromatic: {
      // Capture multiple viewports
      viewports: [375, 768, 1280],
      // Delay for animations
      delay: 300,
      // Disable animations
      pauseAnimationAtEnd: true,
    },
  },
};

export default meta;

export const Primary: StoryObj<typeof Button> = {
  args: { variant: 'primary', children: 'Button' },
};

export const AllStates: StoryObj<typeof Button> = {
  parameters: {
    chromatic: {
      // Test interaction states
      modes: {
        hover: { pseudo: { hover: true } },
        focus: { pseudo: { focus: true } },
        active: { pseudo: { active: true } },
      },
    },
  },
  render: () => (
    <div style={{ display: 'flex', gap: '1rem' }}>
      <Button variant="primary">Primary</Button>
      <Button variant="secondary">Secondary</Button>
      <Button disabled>Disabled</Button>
    </div>
  ),
};
```

## CI Integration

```yaml
# .github/workflows/visual-tests.yml
name: Visual Regression Tests

on:
  pull_request:
    branches: [main]

jobs:
  visual-tests:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - uses: actions/setup-node@v4
        with:
          node-version: 20
          cache: 'npm'

      - run: npm ci

      - name: Install Playwright Browsers
        run: npx playwright install --with-deps chromium

      - name: Build application
        run: npm run build

      - name: Run visual tests
        run: npx playwright test --project="Desktop Chrome"

      - name: Upload test results
        if: always()
        uses: actions/upload-artifact@v4
        with:
          name: playwright-report
          path: |
            playwright-report/
            test-results/
          retention-days: 30

      - name: Upload diff images
        if: failure()
        uses: actions/upload-artifact@v4
        with:
          name: visual-diffs
          path: tests/visual/__snapshots__/*-diff.png
          retention-days: 7
```

### Update Baselines Script

```json
// package.json
{
  "scripts": {
    "test:visual": "playwright test --project='Desktop Chrome'",
    "test:visual:update": "playwright test --update-snapshots",
    "test:visual:ui": "playwright test --ui",
    "test:visual:report": "playwright show-report"
  }
}
```

## Best Practices

1. **Disable animations**: Consistent screenshots
2. **Wait for network**: Ensure content loaded
3. **Use stable selectors**: data-testid attributes
4. **Test multiple viewports**: Responsive coverage
5. **Set thresholds**: Allow minor pixel differences
6. **Review in CI**: Block merges on failures
7. **Organize snapshots**: Clear naming convention
8. **Update intentionally**: Review all baseline changes

## Output Checklist

Every visual testing setup should include:

- [ ] Playwright/Chromatic configuration
- [ ] Baseline screenshots
- [ ] Multi-viewport testing
- [ ] Dark mode coverage
- [ ] Component state testing
- [ ] Animation disabling
- [ ] CI integration
- [ ] Diff threshold configuration
- [ ] Baseline update workflow
- [ ] Artifact storage

© patricio0312rev · MIT · patricio0312rev/skills

Похожие скиллы

Всё по теме «Тесты и QA» →
visual-regression-testing aj-geddes Ловит случайные визуальные изменения интерфейса: снимает скриншоты компонентов и страниц и сравнивает их между версиями. CX Codex +1 718установок visual-testing kindlmann Скилл для визуального регрессионного тестирования: сравнивает скриншоты с эталонами и ловит различия на уровне пикселей, которые пропускают функциональные тесты. CX Codex +1 1 200установок browser-qa affaan-m Гоняет живой интерфейс через браузерный MCP: ошибки в консоли, Core Web Vitals, формы, скриншоты на трёх ширинах и axe-core. Выносит вердикт SHIP или DO NOT SHIP. CX Codex +1 4 900установок game-qa OpusGameLabs Скилл для автотестов браузерных игр: проверяет визуал, геймплей, производительность и доступность через Playwright. Пригодится, когда тесты надо писать или разбирать их падения. CX Codex +1 1 100установок setup-visual-testing davila7 Слэш-команда разворачивает визуальное регрессионное тестирование: снимки компонентов и страниц, адаптивные брейкпоинты, проверка доступности. 300установок Playwright Tester Mode официальный github Субагент для GitHub Copilot, который сначала ходит по сайту через Playwright MCP, а потом пишет и прогоняет тесты на TypeScript. ★ 40 418звёзд репо

Из того же набора26

responsive-design-system Скилл для ИИ-агента, который помогает строить мобильную вёрстку: брейкпоинты, container queries, текучая типографика и адаптивные сетки на Tailwind CSS. CX Codex +1 343установок scalability-playbook Разбирает узкие места по метрикам и выдаёт порядок действий: индексы, кэш, реплики, шардинг. С оценкой стоимости и триггерами. CX Codex +1 342установок auth-security-reviewer Разбирает сессии, CSRF, cookie и потоки входа в вашем коде, находит дыры и предлагает правки с оценкой серьёзности. CX Codex +1 340установок mermaid-diagram-generator Скилл рисует диаграммы Mermaid прямо в markdown: блок-схемы, sequence, ERD, состояния и классы. Пригодится, когда нужно показать поток запроса или схему базы в документации. CX Codex +1 331установок form-wizard-builder Скилл собирает многошаговые формы: схемы валидации, шаги, общее состояние, индикатор прогресса и шаг проверки перед отправкой. CX Codex +1 328установок skill-creator Скилл, который пишет новые скиллы по конвенциям репозитория: структура, frontmatter, воркфлоу и примеры кода. CX Codex +1 321установок cost-latency-optimizer Скилл разбирает, куда уходят деньги и время в LLM-приложении, и предлагает кеш, выбор модели, батчинг и сжатие промптов. CX Codex +1 322установок rate-limiting-abuse-protection Скилл добавляет в проект ограничение частоты запросов: политики на маршрут, лимиты по IP и пользователю, скользящее окно и понятные ответы об ошибке. CX Codex +1 319установок

Спонсоры проекта

Компании, которые помогают держать аналитику открытой