Salta al contenuto principale

Visual regression con Playwright + Percy

Automatizza il testing visuale dei tuoi componenti con Percy e Playwright. Impara a catturare snapshot, gestire baselines, e integrare il visual regression nella CI/CD.

Cosa ne pensi di questo articolo?

Visual regression con Playwright + Percy
Playwright ComponentsFoto:Brett SaylesPexels

In Breve

Automatizza il testing visuale dei tuoi componenti con Percy e Playwright. Impara a catturare snapshot, gestire baselines, e integrare il visual regression nella CI/CD.

Visual regression con Playwright + Percy

Il visual regression testing è una tecnica che cattura screenshot di componenti e pagine durante i test, confrontandoli con una baseline per rilevare differenze involontarie. Con Playwright e Percy, automatizzi la cattura e il confronto dei cambiamenti visivi, garantendo che nessuna modifica al CSS o al layout rompa l'interfaccia utente.

In questa guida della serie Playwright Component Testing Avanzato, imparerai a configurare Percy con Playwright per catturare snapshot in componenti, creare baselines, gestire variazioni intenzionali (ad es. temi, stati hover), e integrare il tutto in una pipeline CI/CD. Scoprirai come ridurre i falsi positivi, debuggare fallimenti di regression, e scalare il visual testing a centinaia di componenti senza rallentare i tuoi test.

Cosa Imparerai

  • Come funziona il visual regression testing e perché è complementare ai test funzionali
  • Setup di Percy con Playwright in un progetto Angular
  • Scrittura di test che catturano snapshot con Percy.snapshot()
  • Creazione e aggiornamento di baselines di confronto
  • Gestione di variazioni intenzionali (breakpoint, temi, stato del componente)
  • Integrazione CI/CD: automazione su ogni PR e commit
  • Strategie per ridurre falsi positivi (tolleranza di pixel, mascheratura di aree dinamiche)
  • Debugging di regressioni visive fallite e rollback consapevoli
  • Best practice di performance: batch test, cache, esecuzione parallela
  • Scaling a 50+ componenti senza degradazione del tempo di build

1. Visual Regression Testing: Concetti Fondamentali

Un test funzionale verifica il comportamento: "Se clicco questo pulsante, deve aprirsi il modale." Un test visual verifica l'aspetto: "Questo pulsante deve apparire sempre blu, con angoli arrotondati a 8px, e il testo deve rimanere centrato anche quando il viewport è stretto."

Il visual regression testing automatizza questo controllo confrontando pixel per pixel. Quando modifichi il CSS di un pulsante o il layout di una card, il test cattura un nuovo screenshot, lo confronta con la baseline salvata, e segnala differenze sospette. Il vantaggio principale è la rapidità: invece di testare manualmente ogni pagina dopo ogni cambio, una macchina lo fa in secondi su decine di componenti.

1.1 Visual Regression vs. Altre Forme di Test

Test unitari (Vitest) — verificano logica e output: funzioni pure, pipe, directive. Test funzionali (Playwright e2e) — verificano flussi utente: click, form submission, navigazione. Visual regression (Percy + Playwright) — verificano aspetto visivo: CSS, layout, grafica.

Questi tre sono complementari. Un test funzionale può passare (il pulsante è cliccabile) mentre il visual regression fallisce (il pulsante è invisibile perché il testo è bianco su sfondo bianco).

1.2 Quando Usare il Visual Regression Testing

  • Componenti con styling complesso: card, navbar, modal, tabella con vari stati
  • Responsive design: testare layout su mobile, tablet, desktop
  • Temi multipli: light theme, dark theme, brand alternativo
  • Stati interattivi: hover, focus, disabled, loading
  • Internazionalizzazione: layout con testi in lingue diverse (IT vs. EN vs. DE)
  • Refactoring CSS: rinominare classi, migrare a utility-first (Tailwind)

2. Configurazione di Percy con Playwright

Percy è un servizio cloud (di Browserstack) che memorizza e confronta gli screenshot. Integrarsi con Playwright richiede: SDK Percy, API token, e configurazione nel tuo test file.

2.1 Installazione Dipendenze

Aggiungi Percy CLI e l'SDK Playwright:

Shell
npm install --save-dev @percy/cli @percy/playwright

Crea un file di configurazione Percy nella root del progetto:

.percy.yml
version: 2
static:
  cleanUrls: true
  include: "**/*.html"
  exclude:
    - node_modules/**/*
discovery:
  network-idle-timeout: 750

percy:
  version: 2
  static-assets:
    base-url: http://localhost:4200

  browsers:
    - name: chrome
      widths: [1280, 768, 375]  # desktop, tablet, mobile
    - name: firefox
      widths: [1280]

2.2 Token Autenticazione

Registrati su percy.io, crea un nuovo progetto, e copia il token di autenticazione. Salvalo come variabile d'ambiente nel tuo CI/CD:

Bash (.env.local)
PERCY_TOKEN=your_percy_token_here

Assicurati che .env.local sia nel .gitignore per evitare di committere il token.

3. Scrivere il Primo Test Visual

3.1 Test Base: Snapshot Singolo

Ecco un test Playwright Component che cattura uno snapshot di un componente Button:

button.spec.ts
import { test, expect } from '@playwright/experimental-ct-angular';
import { percySnapshot } from '@percy/playwright';

test.describe('Button Component - Visual Regression', () => {
  test('primary button snapshot', async ({ mount, page }) => {
    // Monta il componente
    await mount(`Click Me`, {
      props: {}
    });

    // Scatta uno snapshot Percy
    await percySnapshot(page, 'button-primary');

    // Verifica funzionalmente che sia cliccabile
    const button = page.locator('button');
    await expect(button).toBeVisible();
  });

  test('secondary button snapshot', async ({ mount, page }) => {
    await mount(`Cancel`);
    await percySnapshot(page, 'button-secondary');
  });

  test('disabled button snapshot', async ({ mount, page }) => {
    await mount(`Disabled`);
    await percySnapshot(page, 'button-disabled');
  });

  test('button with icon', async ({ mount, page }) => {
    await mount(`
      →
      Send
    `);
    await percySnapshot(page, 'button-with-icon');
  });
});

3.2 Lanciare i Test e Creare la Baseline

La prima volta che esegui i test, Percy crea automaticamente la baseline (gli screenshot iniziali).

Bash
# Esegui i test component di Playwright con Percy
npx percy exec -- npm run test:ct

# Percy aggiungerà gli screenshot a percy.io e
# li markerà come "approved" (baseline iniziale)

Accedi a percy.io nel tuo browser per visualizzare gli snapshot creati. Se tutto è corretto, approva la baseline cliccando "Approve All".

4. Baseline e Aggiornamenti Consapevoli

Una volta approvata la baseline, ogni esecuzione futura dei test confronterà i nuovi screenshot con quella baseline. Se differiscono oltre una soglia di tolleranza, il test fallisce.

4.1 Aggiornare la Baseline Intenzionalmente

Se modifichi deliberatamente il CSS (ad es. cambio colore primario), i test visual falliranno. Devi approvare manualmente i nuovi screenshot su Percy:

Workflow in Percy
1. Modifica il CSS del componente
   src/app/components/button/button.component.css
   Cambia: color: #0066FF -> color: #FF6600

2. Esegui i test component
   npm run test:ct

3. Percy rileva le differenze e crea una nuova "Build"
   Accedi a percy.io

4. Visualizza i diff (split view old vs. new)
   Verifica che il cambio è intenzionale

5. Clicca "Approve" per aggiornare la baseline

6. La prossima esecuzione considererà questo come nuovo baseline

4.2 Tolleranza di Pixel e Mascheramento Aree Dinamiche

Piccole differenze di rendering (anti-aliasing, font rendering) possono causare falsi positivi. Percy supporta tolleranza di pixel e mascheramento di aree.

Tolleranza di Pixel
import { percySnapshot } from '@percy/playwright';

test('button with pixel tolerance', async ({ mount, page }) => {
  await mount(`Click Me`);

  // Permetti differenze fino a 5 pixel per totale
  await percySnapshot(page, 'button-primary', {
    widths: [1280],
    minHeight: 100,
    // tolerance: 0-255 scala di grigi (default 0 = esatto)
    // Nota: usare solo se necessario per evitare di mascherare veri bug
  });
});

Per mascherare aree con contenuto dinamico (timestamp, numero casuale):

Mascheratura di Aree Dinamiche
test('card with dynamic timestamp', async ({ mount, page }) => {
  await mount(`
    

Last updated: 2026-07-19T10:30:00Z

`); // Maschera l'elemento timestamp prima di snapshottare await page.locator('.timestamp').evaluate(el => { el.textContent = '[MASKED]'; }); await percySnapshot(page, 'card-with-timestamp'); });

5. Test Responsivo: Mobile, Tablet, Desktop

Percy cattura automaticamente snapshot a più viewport (definiti nel .percy.yml). Ma puoi anche controllare il viewport manualmente nel test:

test-responsive.spec.ts
import { test, expect } from '@playwright/experimental-ct-angular';
import { percySnapshot } from '@percy/playwright';

test.describe('Navbar - Responsive Design', () => {
  // Desktop (1280px)
  test('navbar desktop layout', async ({ mount, page }) => {
    await page.setViewportSize({ width: 1280, height: 720 });
    await mount(``);
    await percySnapshot(page, 'navbar-desktop');
  });

  // Tablet (768px)
  test('navbar tablet layout', async ({ mount, page }) => {
    await page.setViewportSize({ width: 768, height: 1024 });
    await mount(``);
    await percySnapshot(page, 'navbar-tablet');
  });

  // Mobile (375px)
  test('navbar mobile layout (hamburger menu)', async ({ mount, page }) => {
    await page.setViewportSize({ width: 375, height: 667 });
    await mount(``);

    // Su mobile, il menu hamburger deve essere visibile
    const hamburgerMenu = page.locator('[aria-label="Toggle menu"]');
    await expect(hamburgerMenu).toBeVisible();

    await percySnapshot(page, 'navbar-mobile');
  });

  // Mobile con menu aperto
  test('navbar mobile with menu open', async ({ mount, page }) => {
    await page.setViewportSize({ width: 375, height: 667 });
    await mount(``);

    const hamburgerMenu = page.locator('[aria-label="Toggle menu"]');
    await hamburgerMenu.click();

    // Menu deve diventare visibile
    const navMenu = page.locator('nav[role="navigation"]');
    await expect(navMenu).toBeVisible();

    await percySnapshot(page, 'navbar-mobile-menu-open');
  });
});

6. Test di Stati Interattivi (Hover, Focus, Disabled)

I componenti hanno spesso stati visivi diversi: hover, focus, disabled, loading. Percy ti permette di snapshottare ogni stato:

interactive-states.spec.ts
import { test } from '@playwright/experimental-ct-angular';
import { percySnapshot } from '@percy/playwright';

test.describe('Button Interactive States', () => {
  test('button default state', async ({ mount, page }) => {
    await mount(`Click Me`);
    await percySnapshot(page, 'button-default');
  });

  test('button hover state', async ({ mount, page }) => {
    await mount(`Click Me`);
    const button = page.locator('button');
    await button.hover();
    await percySnapshot(page, 'button-hover');
  });

  test('button focus state', async ({ mount, page }) => {
    await mount(`Click Me`);
    const button = page.locator('button');
    await button.focus();
    await percySnapshot(page, 'button-focus');
  });

  test('button active/pressed state', async ({ mount, page }) => {
    await mount(`Click Me`);
    const button = page.locator('button');

    // Simula pressione con keyboard
    await button.focus();
    await page.keyboard.press('Space');

    await percySnapshot(page, 'button-active');
  });

  test('button disabled state', async ({ mount, page }) => {
    await mount(`Disabled`);
    const button = page.locator('button');
    await expect(button).toBeDisabled();
    await percySnapshot(page, 'button-disabled');
  });

  test('button loading state', async ({ mount, page }) => {
    await mount(`Please Wait...`);
    await percySnapshot(page, 'button-loading');
  });
});

7. Integrazione in CI/CD (GitHub Actions, GitLab CI)

Il visual regression testing è più potente quando automatizzato in CI/CD. Ogni PR trigghera i test, e Percy genererà un commento automatico con i diff.

7.1 GitHub Actions Workflow

.github/workflows/visual-regression.yml
name: Visual Regression Tests

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

jobs:
  visual-regression:
    runs-on: ubuntu-latest

    steps:
      - uses: actions/checkout@v4

      - name: Setup Node.js
        uses: actions/setup-node@v4
        with:
          node-version: '20.x'
          cache: 'npm'

      - name: Install dependencies
        run: npm ci

      - name: Install Playwright browsers
        run: npx playwright install --with-deps

      - name: Run component tests with Percy
        run: npx percy exec -- npm run test:ct
        env:
          PERCY_TOKEN: ${{ secrets.PERCY_TOKEN }}

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

Aggiungi il tuo token Percy come secret su GitHub (Settings → Secrets → New repository secret).

7.2 Bloccare i PR se la Regressione Fallisce

Percy creerà automaticamente un "check" sulla PR che passa o fallisce a seconda dei test. Se vuoi bloccare il merge quando ci sono differenze non approvate:

branch protection rule (GitHub)
1. Vai su Settings → Branches → Add rule
2. Branch name pattern: main
3. Spunta "Require status checks to pass"
4. Aggiungi il check di Percy come obbligatorio
   (percyio/percy-check o simile, dipende dalla tua setup)

8. Best Practice e Ottimizzazioni di Performance

8.1 Organizzare i Test per Categoria

Struttura i tuoi test in file organizzati per tipo di componente:

Struttura raccomandata
tests/
  components/
    button/
      button.spec.ts          # Snapshot base
      button-states.spec.ts   # Hover, focus, disabled
      button-responsive.spec.ts  # Mobile, tablet, desktop
    card/
      card.spec.ts
      card-responsive.spec.ts
    navbar/
      navbar.spec.ts
      navbar-states.spec.ts
      navbar-responsive.spec.ts
  visual/
    visual-regression.config.ts  # Configurazioni condivise

8.2 Configurazione Timeout e Parallelizzazione

I test visual possono essere lenti se hai molti viewport. Usa la parallelizzazione:

playwright.config.ts
import { defineConfig, devices } from '@playwright/experimental-ct-angular';

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  workers: 4,  // Esegui 4 test in parallelo
  timeout: 30_000,  // 30 secondi per test (snappy!)

  webServer: {
    command: 'npm run dev',
    port: 4200,
    reuseExistingServer: !process.env.CI,
  },

  use: {
    baseURL: 'http://localhost:4200',
    trace: 'on-first-retry',
  },

  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
  ],
});

9. Debugging e Troubleshooting

9.1 Differenze Legittime vs. Bug

Quando un test visual fallisce, devi stabilire se è un bug o una modifica intenzionale. Percy mostra il diff side-by-side che ti aiuta:

  • Spostamento layout: un elemento si è spostato di 2px? Potrebbe essere una media query rotta.
  • Cambio colore: il pulsante era rosso, ora è blu? Qualcuno ha modificato il CSS.
  • Scomparsa elemento: un icona non appare? Display:none accidentale?
  • Rendering font: il testo sembra leggermente diverso? Anti-aliasing — ignora se minore di 1px.

9.2 False Positives: Mascherare il Rumore

Se il test fallisce costantemente per motivi banali (timestamp, numero casuale, ID generato), maschera quell'elemento prima di snapshottare.

10. Domande Frequenti (FAQ)

Quante snapshot posso fare prima che i test diventino lenti?

Con parallelizzazione (4 worker), puoi gestire 50-100 snapshot per suite senza degradazione significativa. Oltre, split in più file di test. Percy carica velocemente, il bottleneck è il rendering del browser.

Cosa succede se Percy è down? I test falliscono?

No. Se Percy è irraggiungibile, il test continua normalmente ma senza catturare lo snapshot (di solito un avviso in console). Il test non fallisce. In CI, puoi configurare retry automatico.

Percy è gratuito?

Percy offre un piano free per 5000 snapshot al mese (più che sufficiente per team piccoli). Sopra quel limite, ci sono piani a pagamento. Valuta se Percy vale il costo per il tuo team o se alternative open-source (BackstopJS, Pixelmatch) sono sufficienti.

Come mantengo le snapshot aggiornate se cambio il tema?

Approva tutti i nuovi screenshot su Percy quando effettui il cambio tema. Poi executa i test su un branch dedecato per catturare i nuovi snapshot, approvali, e mergia nel main. Questo crea una traccia di controllo di tutte le modifiche visive approvate.

Posso usare visual regression testing per e2e oltre che per component testing?

Sì. Usa `percySnapshot()` in qualsiasi test Playwright (component, integration, e2e). Cattura la screenshot della pagina intera, di una sezione, o di componenti specifici. È particolarmente utile per verificare flussi completi (checkout, onboarding) dove l'aspetto visivo conta.

Commenti

Caricamento commenti...

Accedi per commentare

Discuti questo articolo nella community

Unisciti al gruppo Telegram per discutere con altri sviluppatori, fare domande e condividere le tue esperienze.

Ti è piaciuto questo articolo?

Esplora altri contenuti sul blog o scopri i miei progetti