Vai al contenuto

Come usare Playwright per lo screenshot testing

Crea test screenshot con Playwright usando baseline visive, progetti per browser, attese stabili, soglie di differenza e revisione degli aggiornamenti.

13 min di letturaCattura siti webDi Temposhot Team

Lo screenshot testing confronta l'aspetto attuale di una pagina con un'immagine approvata in precedenza. Se i pixel differiscono oltre la soglia consentita, il test fallisce e mostra la differenza. Playwright Test include questa funzione tramite expect(page).toHaveScreenshot(), senza plugin aggiuntivi. Il metodo è detto anche visual regression testing.

Gli esempi di questa guida costruiscono una sola suite: 12 pagine di marketing e di app testate in 3 progetti browser, più 8 componenti condivisi. Ogni numero riportato di seguito, dai conteggi delle baseline alle soglie di differenza, deriva da questa suite.

Quando lo screenshot testing conviene

I test visivi rilevano problemi che le asserzioni sul DOM non vedono: un foglio di stile non caricato, un pulsante spinto fuori schermo, un font sostituito con quello di sistema. Il costo di manutenzione conviene quando:

  • l'interfaccia ha un design system o componenti condivisi usati in molte pagine
  • il CSS cambia spesso e i revisori non possono controllare ogni pagina a mano
  • bug di layout sono già arrivati in produzione

Costano più di quanto restituiscono su pagine con dati live, annunci o contenuti utente. Ogni esecuzione produrrebbe risultati diversi e la suite diventerebbe rumore.

Dimensiona la suite prima di scrivere i test

Ogni asserzione crea un'immagine baseline per progetto. La suite di esempio ha 12 asserzioni a pagina intera e 8 asserzioni sui componenti, eseguite ciascuna in 3 progetti. Il totale è di 60 file PNG da creare, rivedere e mantenere nel repository.

Quattro statistiche della suite di esempio: 12 pagine, 3 progetti browser, 36 baseline di pagina e 60 immagini baseline in totale, inclusi i componenti.

Escludi dalla suite le pagine instabili

Alcune pagine falliscono a ogni esecuzione, qualunque sia la scrittura del test. Un dashboard con grafici in tempo reale, un feed di post utente e una pagina con banner a rotazione rientrano in questo gruppo. Per queste pagine, testa invece lo stato vuoto o un componente con dati fissi.

L'esempio ha escluso 5 pagine candidate: 2 dashboard, un indice del blog con nuovi post ogni settimana, una pagina di stato e una pagina di risultati di ricerca. Ciascuna è coperta da asserzioni sugli elementi delle sue parti condivise. L'elenco delle pagine a pagina intera è rimasto a 12.

Una PNG a pagina intera di una lunga pagina di marketing può raggiungere un megabyte o più. Sessanta file di questa dimensione si accumulano nella cronologia Git a ogni modifica delle baseline. Le asserzioni sui componenti producono file più piccoli e cambiano meno spesso, quindi sostengono gran parte della suite.

Installa Playwright Test

npm init playwright@latest

L'installer crea playwright.config.ts, una cartella di test di esempio e un workflow CI facoltativo, poi scarica i browser. Le asserzioni sugli screenshot non richiedono altro.

Fissa la versione di Playwright

Ogni release di Playwright include build specifiche dei browser. Un aggiornamento del browser può cambiare leggermente il rendering del testo e far fallire test screenshot che non sono stati modificati. Fissa una versione esatta in package.json e aggiornala in una pull request separata.

{
  "devDependencies": {
    "@playwright/test": "1.48.0"
  }
}

Quando cambia la versione, rigenera tutte le baseline nella stessa pull request e rivedile in blocco. Unire un aggiornamento di versione a una modifica funzionale rende impossibile capire quale delle due abbia causato una differenza.

Crea il primo test screenshot

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

test('home page matches baseline', async ({ page }) => {
  await page.goto('https://example.com/');
  await expect(page).toHaveScreenshot('home.png', { fullPage: true });
});

Il primo run non ha una baseline con cui confrontarsi. Playwright salva la cattura come immagine attesa e fa fallire il test, così la nuova baseline viene revisionata. Esegui di nuovo npx playwright test e il test passerà confrontandosi con il file salvato.

Quando un run successivo differisce, Playwright salva l'immagine attesa, la cattura effettiva e un'immagine di differenza con i pixel modificati evidenziati. Il report HTML generato da npx playwright show-report mostra tutte e tre.

Testa le 12 pagine da un'unica lista

Scrivere 12 test separati ripete le stesse righe. Una lista di pagine con un ciclo crea un test con nome per ogni pagina. Il titolo del test include il nome della pagina, così -g può selezionarne una in seguito.

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

const pages = [
  { name: 'home', path: '/' },
  { name: 'pricing', path: '/pricing' },
  { name: 'features', path: '/features' },
  { name: 'docs-index', path: '/docs' },
  // ...8 more entries
];

for (const p of pages) {
  test(`page ${p.name} matches baseline`, async ({ page }) => {
    await page.goto(p.path);
    await page.getByRole('heading', { level: 1 }).waitFor();
    await page.evaluate(() => document.fonts.ready);
    await expect(page).toHaveScreenshot(`${p.name}.png`, { fullPage: true });
  });
}

I percorsi relativi richiedono un baseURL nella configurazione. La sezione successiva lo aggiunge insieme a un server web locale.

Esegui i test su una build locale, non in produzione

Le pagine di produzione cambiano per motivi non legati al codice in test: modifiche di contenuto, test A/B e script di terze parti. Una build locale del branch mostra solo ciò che il branch ha modificato. L'opzione webServer la avvia prima dei test.

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

export default defineConfig({
  use: { baseURL: 'http://localhost:3000' },
  webServer: {
    command: 'npm run build && npm run start',
    url: 'http://localhost:3000',
    reuseExistingServer: !process.env.CI,
    timeout: 120_000,
  },
});

reuseExistingServer permette a uno sviluppatore di tenere attivo il proprio server durante le esecuzioni locali. In CI il valore è false, quindi il job avvia sempre una build nuova.

Testa una parte più piccola della pagina

test('pricing card', async ({ page }) => {
  await page.goto('https://example.com/pricing');
  await expect(page.getByTestId('pro-plan')).toHaveScreenshot('pro-card.png');
});

Le asserzioni sugli elementi sono più piccole e falliscono meno spesso per motivi non correlati. Una modifica all'header non fa fallire il test della card dei prezzi. Usa le asserzioni a pagina intera per poche pagine chiave e quelle sugli elementi per i componenti.

Scegli l'asserzione per ogni pagina

Nella suite di esempio, le 12 pagine si dividono in 4 tipi. Solo le pagine che le persone valutano nel loro insieme ricevono un'asserzione a pagina intera. Le pagine costruite con componenti condivisi ricevono invece asserzioni sugli elementi di quei componenti.

Confronto tra asserzioni a pagina intera e su elementi per tipo di pagina: le pagine di presentazione e dei prezzi ricevono controlli a pagina intera, le pagine della documentazione e le schermate dell'app ricevono controlli sui componenti condivisi.

Aggiungi progetti per browser e dispositivi

I progetti in playwright.config.ts eseguono gli stessi test su più browser e profili di dispositivo.

import { defineConfig, devices } from '@playwright/test';

export default defineConfig({
  projects: [
    { name: 'chromium', use: { ...devices['Desktop Chrome'] } },
    { name: 'webkit', use: { ...devices['Desktop Safari'] } },
    { name: 'mobile', use: { ...devices['iPhone 13'] } },
  ],
});

Ogni progetto mantiene le proprie baseline. I nomi dei file includono progetto e piattaforma, per esempio home-chromium-darwin.png. Tre progetti significano quindi tre baseline per asserzione, perciò aggiungi progetti solo dove i layout differiscono davvero.

Tieni le baseline in una cartella prevedibile

La posizione predefinita mette una cartella -snapshots accanto a ogni file di test. Con diversi file di test, le baseline si disperdono nell'albero dei test. L'opzione snapshotPathTemplate le sposta in una cartella per file di test sotto una radice comune.

// playwright.config.ts
export default defineConfig({
  snapshotPathTemplate: '{testDir}/__screenshots__/{testFilePath}/{arg}{-projectName}{ext}',
});

Il template elimina il suffisso della piattaforma, il che funziona perché ogni esecuzione usa la stessa immagine Docker Linux. Se alcune esecuzioni avvengono ancora su macOS, mantieni {-snapshotSuffix} nel template così le due piattaforme non condividono mai un file.

Albero di file di una cartella tests con una cartella screenshots, una sottocartella per ogni file di specifica e file PNG di baseline nominati per pagina e progetto, come home-chromium.png.

Una persona che revisiona può aprire una cartella e vedere tutte le baseline di un file di specifica. Rinominare un file di test sposta le sue baseline in una nuova cartella, quindi rinomina in una pull request che rigenera anche le baseline.

Rendi stabile lo stato della pagina

Uno screenshot è confrontabile solo se la pagina raggiunge lo stesso stato a ogni esecuzione.

  1. Attendi un elemento pronto, come un titolo o un'immagine caricata, prima dell'asserzione.
  2. Usa dati di test fissi invece di account reali.
  3. Imposta una viewport fissa nella configurazione.
  4. Attendi i font web con await page.evaluate(() => document.fonts.ready).

toHaveScreenshot riprova già finché due catture consecutive coincidono e, per impostazione predefinita, blocca le animazioni CSS. Questi due comportamenti eliminano gran parte del rumore temporale senza codice aggiuntivo.

Attendi un segnale, non un tempo fisso

Un page.waitForTimeout(3000) fisso rende la suite più lenta e fallisce comunque su un runner lento. Attendere un elemento specifico collega invece l'asserzione allo stato della pagina. L'attesa del titolo nel ciclo delle pagine sopra ne è un esempio.

Attendere che la rete si fermi è meno affidabile di quanto sembri. Le pagine che interrogano un'API o mantengono aperta una connessione non raggiungono mai quello stato, e l'attesa termina per timeout. Preferisci un elemento che compare solo dopo il caricamento dei dati, come la prima riga di una tabella.

Le immagini con caricamento differito sotto la piega non si caricano finché non scorrono nella vista. Una cattura a pagina intera di una pagina del genere mostra riquadri vuoti. Scorri una volta fino in fondo prima dell'asserzione, oppure disattiva il lazy loading nella build di test.

Blocca il tempo e gli script di terze parti

Date come "Aggiornato 3 minuti fa" cambiano tra un'esecuzione e l'altra. La Clock API fissa l'ora che la pagina vede. Gli script di terze parti per widget di chat e analytics possono inserire elementi in ritardo, quindi il test li blocca a livello di rete.

test.beforeEach(async ({ page }) => {
  await page.clock.setFixedTime(new Date('2026-05-01T10:00:00Z'));
  await page.route(/(analytics|chat-widget)\./, (route) => route.abort());
});

Bloccare uno script rimuove anche ciò che disegna. Qui è voluto: il test visivo verifica il layout del sito, non il widget.

Cause di instabilità e relative correzioni

La suite di esempio è fallita senza modifiche al codice 9 volte nelle prime due settimane. Ogni errore risaliva a una di cinque cause. La tabella associa ciascuna causa alla modifica che l'ha eliminata.

Confronto di cinque cause di instabilità con ciò che mostra l'immagine di differenza e la correzione: font, date relative, immagini con lazy loading, widget di terze parti e cursore di testo lampeggiante.

Nascondi o maschera gli elementi volatili

await expect(page).toHaveScreenshot('dashboard.png', {
  mask: [page.locator('.timestamp'), page.locator('.user-avatar')],
});

Le aree mascherate vengono coperte da un riquadro pieno in entrambe le immagini, quindi il loro contenuto viene ignorato. Per gli elementi che devono scomparire del tutto, passa un foglio di stile con l'opzione stylePath e nascondili lì. La maschera è preferibile alla rimozione degli elementi, perché il layout intorno viene comunque verificato.

Maschera per ruolo, non per layout

Un locator di maschera che dipende dalle classi di layout si rompe quando cambia il markup. Un test ID o un ruolo accessibile resta stabile tra un redesign e l'altro. Nella suite di esempio, ogni elemento volatile ha un data-testid che inizia con volatile-.

await expect(page).toHaveScreenshot('account.png', {
  mask: [page.locator('[data-testid^="volatile-"]')],
  maskColor: '#FF00FF',
});

Un maskColor vivace rende evidenti i riquadri mascherati nel report. I revisori vedono così a colpo d'occhio quali aree il test ignora.

Imposta con attenzione la soglia di differenza

OpzioneSignificato
maxDiffPixelsNumero di pixel autorizzati a differire.
maxDiffPixelRatioQuota di pixel autorizzati a differire, da 0 a 1.
thresholdQuanto deve differire il colore di un pixel per essere conteggiato, da 0 a 1. Predefinito 0,2.

Mantieni le tolleranze basse. Un maxDiffPixelRatio di 0,01 consente che l'1 percento dei pixel cambi, che su una cattura a pagina intera di 1280×4000 supera i 50.000 pixel. È abbastanza per nascondere un'icona mancante. Parti da maxDiffPixels intorno a 100 e modificalo solo quando un falso fallimento specifico lo richiede.

Il calcolo della soglia per una cattura a pagina intera

I numeri seguenti usano una cattura di 1280 per 4000 pixel, la dimensione della pagina dei prezzi nell'esempio. Un'icona di 24 per 24 pixel copre 576 pixel. Una tolleranza basata sul rapporto dell'1 percento permetterebbe a quell'icona di scomparire 88 volte senza far fallire il test.

Quattro statistiche per una cattura: 5.120.000 pixel in totale, 51.200 pixel consentiti con maxDiffPixelRatio 0.01, 576 pixel in un'icona di 24 per 24 pixel, 100 pixel consentiti con maxDiffPixels.

Imposta i valori predefiniti una sola volta nella configurazione

Le opzioni per singola chiamata sono facili da dimenticare in un nuovo test. Il blocco expect.toHaveScreenshot nella configurazione imposta i valori predefiniti per ogni asserzione. I singoli test possono comunque sovrascriverli.

// playwright.config.ts
export default defineConfig({
  expect: {
    toHaveScreenshot: {
      maxDiffPixels: 100,
      threshold: 0.2,
      animations: 'disabled',
      caret: 'hide',
      scale: 'css',
    },
  },
});
Pannello delle impostazioni con i valori predefiniti delle asserzioni screenshot: maxDiffPixels 100, threshold 0.2, animations disabled, caret nascosto, scale css, pagina intera impostata per ogni test.

scale: 'css' fa corrispondere un pixel CSS a un pixel dell'immagine, quindi un profilo di dispositivo in stile Retina non raddoppia la dimensione dell'immagine. Le baseline restano più piccole e i conteggi di pixel sopra restano significativi.

Rivedi e aggiorna le baseline

Quando una modifica al design è intenzionale, rigenera le baseline interessate:

npx playwright test --update-snapshots

Limita gli aggiornamenti ai test che falliscono passando un nome di file o -g con il titolo del test. Un aggiornamento generale su tutta la suite rende facile non notare modifiche accidentali.

npx playwright test tests/pages.visual.spec.ts -g "page pricing" --update-snapshots

Leggi le tre immagini in ordine

Parti dall'immagine effettiva, non dalla differenza. L'immagine effettiva mostra se la pagina appare rotta a una persona. Poi apri la differenza per trovare dove sono cambiati i pixel, e l'immagine attesa per vedere com'era prima.

La differenza evidenzia in rosso i pixel modificati. Un sottile contorno rosso attorno a ogni blocco di testo di solito indica un cambio di font o di anti-aliasing, non un bug di layout. Un rettangolo rosso pieno in un punto indica di solito un elemento spostato, comparso o scomparso.

Il report HTML offre anche una vista a cursore tra atteso ed effettivo. Trascinandolo sull'area modificata si vedono meglio piccoli spostamenti, come una modifica di margine di 2 pixel, rispetto alla differenza.

La vita di una baseline

Una baseline attraversa lo stesso ciclo ogni volta che la pagina cambia di proposito. Il ciclo ha un solo punto decisionale: se la differenza è intenzionale. Tutto il resto è meccanico.

Flusso di cinque riquadri: il test fallisce con una differenza, il revisore apre le immagini attesa, effettiva e di differenza, decide se la modifica è intenzionale, aggiorna solo quel test, esegue il commit del nuovo PNG con il codice.

Rivedi le modifiche alle baseline nella pull request

Un PNG modificato in una pull request è una modifica al codice. GitHub e la maggior parte degli strumenti di review mostrano le differenze delle immagini affiancate. La checklist seguente è ciò che i revisori del team di esempio controllano prima di approvare.

Checklist di sei punti: ogni PNG modificato corrisponde a una modifica descritta, nessuna pagina non correlata è cambiata, tutti e 3 i progetti sono aggiornati, le aree mascherate coprono solo gli elementi volatili, nessuna baseline mostra immagini rotte o font mancanti, il numero di file è invariato.

Esegui gli screenshot test in CI

Baseline create su macOS non corrispondono alle catture di un runner Linux, perché font e anti-aliasing differiscono. La configurazione affidabile genera e confronta le baseline nello stesso ambiente, di solito l'immagine Docker ufficiale di Playwright alla stessa versione del pacchetto installato.

Una routine funzionante:

  1. Gli sviluppatori eseguono la suite visiva dentro l'immagine Docker, non direttamente su macOS.
  2. La CI esegue la stessa immagine e carica il report HTML come artefatto in caso di fallimento.
  3. Le modifiche intenzionali ricevono nuove baseline, committate con la modifica al codice.
  4. I revisori controllano le differenze delle immagini nella pull request.

Genera le baseline in Docker su un Mac

Il tag dell'immagine deve corrispondere alla versione di Playwright fissata. Una discrepanza riporta le differenze di font e rendering che l'immagine serve a eliminare. Esegui il comando dalla radice del progetto.

docker run --rm --ipc=host -v "$(pwd)":/work -w /work \
  mcr.microsoft.com/playwright:v1.48.0-noble \
  /bin/bash -c "npm ci && npx playwright test --update-snapshots -g 'page pricing'"
Cinque passaggi numerati: controlla la versione di Playwright fissata, scarica l'immagine Docker con lo stesso tag, esegui l'aggiornamento per un test, apri il nuovo PNG, esegui il commit insieme al codice.

Un job CI per la suite visiva

Il job gira nella stessa immagine, quindi non serve un passaggio di download del browser. Carica il report solo quando i test falliscono. Il report contiene le immagini attesa, effettiva e di differenza per ogni fallimento.

# .github/workflows/visual.yml
name: visual
on: pull_request
jobs:
  visual:
    runs-on: ubuntu-latest
    container:
      image: mcr.microsoft.com/playwright:v1.48.0-noble
    steps:
      - uses: actions/checkout@v4
      - run: npm ci
      - run: npx playwright test tests/*.visual.spec.ts
        env:
          HOME: /root
      - uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: playwright-report
          path: playwright-report/
Timeline di un run CI: checkout a 0:00, installazione a 0:15, build e avvio del server a 1:00, 60 asserzioni da 2:30, caricamento del report in caso di fallimento a 5:10.

Nell'esempio, il job completo richiede circa 5 minuti. Eseguire le 8 asserzioni sui componenti a ogni pull request e le 36 asserzioni di pagina solo sul branch principale riduce il job della pull request a meno di 3 minuti.

Dove un semplice strumento di cattura resta adatto

I test visivi rispondono a una domanda: questa pagina è cambiata rispetto alla sua baseline? Non producono un set di revisione per una riunione di design, un report per il cliente o un archivio datato, e le persone fuori dall'ingegneria raramente li eseguono.

Per questi usi, uno strumento di cattura semplice è più adatto. Temposhot Website Capture accetta una lista di URL su un Mac e cattura ogni pagina in Desktop 1920×1080, Tablet 768×1024 o Mobile 390×844, senza codice di test. Per confrontare due catture a occhio, Compare Images evidenzia le differenze nel browser. La voce screenshot comparison descrive il metodo in generale.

Due strumenti per due domande diverse

La suite visiva e uno strumento di cattura spesso convivono nello stesso team. Gli ingegneri gestiscono la suite. Designer e product manager usano le catture per revisioni e archivi.

Confronto tra test visivi di Playwright e Temposhot Website Capture per domanda a cui rispondono, chi li esegue, configurazione, pagine con accesso, output e ambiente di esecuzione.

Errori comuni

  • Committare baseline generate su un laptop e confrontarle nella CI Linux.
  • Asserire pagine intere che includono date, carousel o annunci.
  • Alzare le soglie per silenziare un test instabile invece di correggere lo stato della pagina.
  • Eseguire --update-snapshots su tutta la suite dopo una sola modifica intenzionale.
  • Aggiungere molti progetti per dispositivi prima che le baseline desktop siano stabili.

Smista una build visiva rossa

Un job visivo fallito richiede una decisione, non un nuovo tentativo. Rieseguire un test instabile finché non passa nasconde la causa e lascia passare la prossima regressione reale. L'ordine seguente risolve la maggior parte dei fallimenti in pochi minuti.

  1. Scarica l'artefatto del report e conta le asserzioni fallite.
  2. Se ogni asserzione di un progetto è fallita, controlla prima un cambio di browser o di font in quel progetto.
  3. Se una pagina è fallita in tutti e 3 i progetti, apri la sua immagine effettiva: la causa di solito è nella pagina.
  4. Se una pagina è fallita in un solo progetto, cerca un problema CSS specifico del browser o una causa di instabilità nella tabella sopra.
  5. Correggi la causa oppure aggiorna solo quella baseline, mai entrambe nello stesso commit.
Elenco di cose da fare e da non fare per una suite visiva: fissa la versione, genera le baseline in Docker, aggiorna un test alla volta; non alzare le soglie, non asserire pagine live e non unire aggiornamenti di versione a funzionalità.

FAQ

Dove sono salvate le immagini baseline?

Per impostazione predefinita in una cartella accanto al file di test, con il nome del file di test e il suffisso -snapshots. L'opzione di configurazione snapshotPathTemplate ne cambia la posizione.

Posso confrontare screenshot senza Playwright Test?

Sì. Cattura con qualsiasi strumento e confronta i file con uno strumento di differenza tra immagini. Playwright Test aggiunge a quel confronto retry, mascheramento e report.

Gli screenshot test devono girare a ogni pull request?

Per le asserzioni a livello di componente, di solito sì. Le grandi suite a pagina intera possono girare su una pianificazione o prima delle release se rallentano la pipeline.

Perché un test passa in locale e fallisce in CI senza modifiche al codice?

I due ambienti renderizzano il testo in modo diverso. Font, anti-aliasing e build del browser possono differire tra macOS e Linux. Esegui la suite nella stessa immagine Docker sia in locale sia in CI.

Con quanti progetti dovrebbe partire una nuova suite?

Con uno solo. Parti da un unico progetto desktop finché le sue baseline restano verdi per una o due settimane. Poi aggiungi il progetto mobile, perché i layout mobile differiscono di più. Aggiungi un secondo browser desktop per ultimo, e solo se gli utenti segnalano bug di layout specifici di un browser.

La suite può testare pagine dietro un login?

Sì. Accedi una volta in un progetto di setup, salva la sessione con storageState e carica quel file nei progetti visivi. Usa un account di test dedicato con dati fissi, così nomi e numeri nella pagina restano uguali a ogni esecuzione.

Le baseline devono essere salvate con Git LFS?

Diventa utile quando la suite contiene centinaia di immagini o i file PNG cambiano spesso. Per una suite di circa 60 file, Git normale di solito basta. Controlla la dimensione del repository dopo alcuni mesi di aggiornamenti delle baseline.

Riferimenti

Hai bisogno di automazione degli screenshot di siti web senza codice?

Quando l'obiettivo è un insieme di screenshot e non un esito positivo o negativo, Temposhot Website Capture esegue una lista di URL in locale su un Mac. Vedi automazione degli screenshot di siti web oppure la guida rapida di Website Capture.

playwrightvisual-testingscreenshot-comparisonci

Termini utilizzati in questa pagina: playwright, responsive screenshot, screenshot automation, screenshot comparison, viewport screenshot, visual regression testing

Trasforma gli screenshot ripetibili in lavoro completato

Temposhot scatta screenshot a intervalli regolari o a ogni clic ed esporta il risultato come PDF, immagini, ZIP o MP4.

Post correlati