Test di regressione visiva con Cypress: un approccio pragmatico
Pubblicato: 2022-03-11Ogni volta che viene rilasciata una nuova versione della nostra libreria di componenti, Picasso, aggiorniamo tutte le nostre applicazioni front-end per ottenere il massimo dalle nuove funzionalità e allineare i nostri progetti in tutte le parti del nostro sito.
Il mese scorso, abbiamo implementato un aggiornamento Picasso per il Toptal Talent Portal, la piattaforma che il nostro talento utilizza per trovare lavoro e interagire con i clienti. Sapendo che il rilascio avrebbe comportato importanti modifiche alla progettazione e, nel tentativo di ridurre al minimo i problemi imprevisti, aveva senso utilizzare tecniche di test di regressione visiva per aiutarci a trovare i problemi prima del rilascio.
Il test di regressione visiva non è un concetto nuovo; molti altri progetti in Toptal lo usano già, incluso lo stesso Picasso.
Strumenti come Percy, Happo e Chromatic possono essere utilizzati per aiutare i team a creare una pipeline di regressione visiva sana e all'inizio abbiamo considerato di aggiungerli. Alla fine abbiamo deciso che il processo di installazione sarebbe stato troppo dispendioso in termini di tempo e avrebbe potuto far deragliare il nostro programma. Avevamo già fissato una data per un blocco del codice per avviare la migrazione e, con solo pochi giorni rimanenti alla scadenza, non avevamo altra scelta che essere creativi.
Test di regressione visiva tramite test dell'interfaccia utente
Sebbene non avessimo test di regressione visiva nel progetto, abbiamo avuto una buona copertura dei test di integrazione dell'interfaccia utente utilizzando Cypress. Anche se lo strumento non è principalmente utilizzato per questo, Cypress ha una pagina nella sua documentazione dedicata ai test visivi e un'altra che elenca tutti i plug-in disponibili per aiutare a configurare Cypress per i test visivi.
Dal cipresso agli screenshot
Dopo aver esaminato la documentazione disponibile, abbiamo deciso di provare cypress-snapshot-plugin. Ci sono voluti solo pochi minuti per la configurazione e, una volta fatto, ci siamo subito resi conto che non eravamo alla ricerca di un output di regressione visiva tradizionale.
La maggior parte degli strumenti di regressione visiva aiuta a identificare le modifiche indesiderate confrontando le istantanee e rilevando le differenze di pixel tra una linea di base nota e accettata e la versione modificata di una pagina o di un componente. Se la differenza di pixel è maggiore di una soglia di tolleranza impostata, la pagina o il componente viene contrassegnato per essere esaminato manualmente. In questa versione, tuttavia, sapevamo che sarebbero state apportate diverse piccole modifiche alla maggior parte dei componenti dell'interfaccia utente, quindi l'impostazione di una soglia non era applicabile. Anche se un determinato componente è diverso al 100%, potrebbe comunque essere corretto nel contesto della nuova versione. Allo stesso modo, una deviazione di pochi pixel potrebbe significare che un componente non è attualmente idoneo alla produzione.
A quel punto, sono diventate chiare due cose contrastanti: notare le differenze di pixel non avrebbe aiutato a identificare i problemi e avere un confronto fianco a fianco dei componenti era esattamente ciò di cui avevamo bisogno. Abbiamo messo da parte il plug-in snapshot e abbiamo deciso di creare una raccolta di immagini con i nostri componenti prima e dopo l'applicazione dell'aggiornamento Picasso. In questo modo, potremmo esaminare rapidamente tutte le modifiche per determinare se le nuove versioni soddisfano ancora le esigenze del sito e gli standard della biblioteca.
Il nuovo piano prevedeva di acquisire uno screenshot di un componente, archiviarlo localmente, acquisire un nuovo screenshot dello stesso componente nel ramo con la versione Picasso aggiornata e quindi unirli in un'unica immagine. In definitiva, questo nuovo approccio non era troppo diverso da quello con cui abbiamo iniziato, ma ci ha dato maggiore flessibilità durante la fase di implementazione poiché non avevamo più bisogno di importare il plug-in e di utilizzare i suoi nuovi comandi.
Sfruttare le API per le immagini di confronto
Con un obiettivo chiaro in mente, era giunto il momento di vedere come Cypress poteva aiutarci a ottenere gli screenshot di cui avevamo bisogno. Come accennato, abbiamo effettuato una buona quantità di test dell'interfaccia utente che coprivano la maggior parte del Portale dei talenti, quindi, nel tentativo di raccogliere il maggior numero possibile di componenti critici, abbiamo deciso di acquisire screenshot dei singoli elementi dopo ogni interazione.
Un approccio alternativo sarebbe stato quello di acquisire screenshot dell'intera pagina nei momenti chiave durante il test, ma abbiamo deciso che quelle immagini sarebbero state troppo difficili da confrontare. Inoltre, tali confronti potrebbero essere più soggetti a errori umani, ad esempio mancare che un piè di pagina sia stato modificato.
Una terza opzione sarebbe stata quella di esaminare ogni singolo test case per decidere cosa acquisire, ma ciò avrebbe richiesto molto più tempo, quindi attenersi a tutti gli elementi utilizzati nelle pagine sembrava un compromesso pratico.
Ci siamo rivolti all'API di Cypress per generare le immagini. Il comando cy.screenshot() può, immediatamente, creare singole immagini di componenti e l'API After Screenshot ci consente di rinominare file, cambiare directory e distinguere le esecuzioni di regressione visiva da quelle standard. Combinando i due, abbiamo creato esecuzioni che non hanno influito sui nostri test funzionali e ci hanno consentito di archiviare le immagini nelle loro cartelle appropriate.
Innanzitutto, abbiamo esteso il file index.js nella nostra directory dei plug-in per supportare i due nuovi tipi di esecuzione (baseline e confronto). Quindi, impostiamo il percorso per le nostre immagini in base al tipo di esecuzione:

// plugins/index.js const fs = require('fs') const path = require('path') module.exports = (on, config) => { // Adding these values to your config object allows you to access them in your tests. config.env.baseline = process.env.BASELINE || false config.env.comparison = process.env.COMPARISON || false on('after:screenshot', details => { // We only want to modify the behavior of baseline and comparison runs. if (config.env.baseline || config.env.comparison) { // We keep track of the file name and number to make sure they are saved in the proper order and in their relevant folders. // An alternative would have been to look up the folder for the latest image, but this was the simpler approach. let lastScreenshotFile = '' let lastScreenshotNumber = 0 // We append the proper suffix number to the image, create the folder, and move the file. const createDirAndRename = filePath => { if (lastScreenshotFile === filePath) { lastScreenshotNumber++ } else { lastScreenshotNumber = 0 } lastScreenshotFile = filePath const newPath = filePath.replace( '.png', ` #${lastScreenshotNumber}.png` ) return new Promise((resolve, reject) => { fs.mkdir(path.dirname(newPath), { recursive: true }, mkdirErr => { if (mkdirErr) { return reject(mkdirErr) } fs.rename(details.path, newPath, renameErr => { if (renameErr) { return reject(renameErr) } resolve({ path: newPath }) }) }) }) } const screenshotPath = `visualComparison/${config.env.baseline ? 'baseline' : 'comparison'}` return createDirAndRename(details.path .replace('cypress/integration', screenshotPath) .replace('All Specs', screenshotPath) ) } }) return config } Quindi abbiamo invocato ciascuna delle esecuzioni aggiungendo la variabile di ambiente corrispondente alla chiamata Cypress nel package.json del progetto:
"scripts": { "cypress:baseline": "BASELINE=true yarn cypress:open", "cypress:comparison": "COMPARISON=true yarn cypress:open" }Una volta eseguiti i nostri nuovi comandi, abbiamo potuto vedere che tutti gli screenshot acquisiti durante l'esecuzione sono stati spostati nelle cartelle appropriate.
Successivamente, abbiamo provato a sovrascrivere cy.get() , il comando principale di Cypress per restituire elementi DOM e fare uno screenshot di tutti gli elementi chiamati insieme alla sua implementazione predefinita. Sfortunatamente, cy.get() è un comando difficile da modificare, poiché chiamare il comando originale nella sua stessa definizione porta a un ciclo infinito. L'approccio suggerito per aggirare questa limitazione è creare un comando personalizzato separato e quindi fare in modo che il nuovo comando esegua uno screenshot dopo aver trovato l'elemento:
Cypress.Commands.add("getAndScreenshot", (selector, options) => { // Note: You might need to tweak the command when getting multiple elements. return cy.get(selector).screenshot() }); it("get overwrite", () => { cy.visit("https://example.cypress.io/commands/actions"); cy.getAndScreenshot(".action-email") }) Tuttavia, le nostre chiamate per interagire con gli elementi sulla pagina erano già racchiuse in una funzione interna getElement() . Quindi tutto ciò che dovevamo fare era assicurarci che fosse stato catturato uno screenshot quando veniva chiamato il wrapper.
Risultati ottenuti tramite test di regressione visiva
Una volta ottenuti gli screenshot, l'unica cosa da fare era unirli. Per questo, abbiamo creato un semplice script di nodo usando Canvas. Alla fine, lo script ci ha permesso di generare 618 immagini di confronto! Alcune delle differenze erano facili da individuare aprendo il Portale dei talenti, ma alcuni problemi non erano così evidenti.
Aggiungere valore ai test dell'interfaccia utente
Prima di tutto, i test di regressione visiva aggiunti si sono rivelati utili e hanno scoperto alcuni problemi che avremmo potuto perdere senza di essi. Anche se ci aspettavamo differenze nei nostri componenti, sapere cosa è stato effettivamente modificato ha aiutato a restringere i casi problematici. Quindi, se il tuo progetto ha un'interfaccia ma non stai ancora eseguendo questi test, fallo!
La seconda lezione qui, e forse la più importante, è che ci è stato ricordato ancora una volta che il perfetto è nemico del bene. Se avessimo escluso la possibilità di eseguire test di regressione visiva per questa versione perché non esisteva una configurazione precedente, potremmo aver perso alcuni bug durante la migrazione. Invece, abbiamo concordato un piano che, sebbene non ideale, fosse veloce da eseguire, abbiamo lavorato per raggiungerlo e ha dato i suoi frutti.
Per maggiori dettagli sull'implementazione di una solida pipeline di regressione visiva nel tuo progetto, fai riferimento alla pagina dei test visivi di Cypress, seleziona lo strumento più adatto alle tue esigenze e guarda i video tutorial.
