Testarea regresiei vizuale cu Cypress: O abordare pragmatică
Publicat: 2022-03-11De fiecare dată când este lansată o nouă versiune a bibliotecii noastre de componente, Picasso, actualizăm toate aplicațiile noastre front-end pentru a profita la maximum de noile funcții și pentru a ne alinia design-urile în toate părțile site-ului nostru.
Luna trecută, am lansat o actualizare Picasso a Toptal Talent Portal, platforma pe care talentul nostru o folosește pentru a găsi locuri de muncă și a interacționa cu clienții. Știind că lansarea va veni cu modificări majore de design și, într-un efort de a minimiza problemele neașteptate, era logic să folosim tehnici de testare a regresiei vizuale pentru a ne ajuta să găsim probleme înainte de lansare.
Testarea regresiei vizuale nu este un concept nou; multe alte proiecte de la Toptal îl folosesc deja, inclusiv Picasso însuși.
Instrumente precum Percy, Happo și Chromatic pot fi folosite pentru a ajuta echipele să construiască o conductă sănătoasă de regresie vizuală și ne-am gândit să le adăugăm la început. În cele din urmă, am decis că procesul de configurare va consuma prea mult timp și ar putea să ne deraieze programul. Aveam deja stabilită o dată pentru înghețarea codului pentru a începe migrarea și, cu doar câteva zile rămase până la termenul limită, nu am avut de ales decât să fim creativi.
Testarea regresiei vizuale prin testarea UI
Deși nu am avut teste de regresie vizuală în proiect, am avut o bună acoperire a testelor de integrare a UI folosind Cypress. Chiar dacă nu pentru asta este folosit cel mai mult instrumentul, Cypress are o pagină în documentația sa dedicată testării vizuale și o altă pagină care listează toate pluginurile disponibile pentru a ajuta la configurarea Cypress pentru testarea vizuală.
De la Cypress la Capturi de ecran
După ce am parcurs documentația disponibilă, am decis să încercăm cypress-snapshot-plugin. Configurarea a durat doar câteva minute și, odată ce am făcut-o, ne-am dat seama rapid că nu eram în căutarea unei rezultate tradiționale de regresie vizuală.
Majoritatea instrumentelor de regresie vizuală ajută la identificarea modificărilor nedorite prin compararea instantaneelor și detectarea diferențelor de pixeli între o linie de bază cunoscută și acceptată și versiunea modificată a unei pagini sau a unei componente. Dacă diferența de pixeli este mai mare decât un prag de toleranță setat, pagina sau componenta este marcată pentru a fi examinată manual. În această ediție, totuși, știam că vom avea câteva modificări mici la majoritatea componentelor noastre UI, așa că setarea unui prag nu era aplicabilă. Chiar dacă o anumită componentă s-a întâmplat să fie 100% diferită, ar putea fi totuși corectă în contextul noii versiuni. În mod similar, o abatere cât mai mică de câțiva pixeli ar putea însemna că o componentă nu este în prezent adecvată pentru producție.
În acel moment, două lucruri contrastante au devenit clare: observarea diferențelor de pixeli nu va ajuta la identificarea problemelor și a avea o comparație alăturată a componentelor era exact ceea ce aveam nevoie. Am lăsat deoparte plug-in-ul instantaneu și ne-am propus să creăm o colecție de imagini cu componentele noastre înainte și după aplicarea actualizării Picasso. Astfel, am putea scana rapid toate modificările pentru a determina dacă noile versiuni încă se potriveau nevoilor site-ului și standardelor bibliotecii.
Noul plan a fost să faceți o captură de ecran a unei componente, să o stocați local, să faceți o nouă captură de ecran a aceleiași componente în ramura cu versiunea Picasso actualizată și apoi să le îmbinați într-o singură imagine. În cele din urmă, această nouă abordare nu a fost prea diferită de ceea ce am început, dar ne-a oferit mai multă flexibilitate în timpul fazei de implementare, deoarece nu mai era nevoie să importăm plug-in-ul și să folosim noile sale comenzi.
Valorificarea API-urilor pentru imagini de comparație
Cu un obiectiv clar în minte, era timpul să ne uităm la modul în care Cypress ne-ar putea ajuta să obținem capturile de ecran de care aveam nevoie. După cum am menționat, am avut o cantitate bună de teste de interfață care acoperă cea mai mare parte a Portalului de talente, așa că, într-un efort de a colecta cât mai multe componente critice posibil, am decis să facem capturi de ecran ale elementelor individuale după fiecare interacțiune.
O abordare alternativă ar fi fost să facem capturi de ecran ale întregii pagini în momentele cheie din timpul testului, dar am decis că acele imagini ar fi prea dificil de comparat. De asemenea, astfel de comparații ar putea fi mai predispuse la erori umane, cum ar fi lipsa faptului că un subsol sa schimbat.
O a treia opțiune ar fi fost să parcurgeți fiecare caz de testare pentru a decide ce să capturați, dar asta ar fi durat mult mai mult timp, așa că rămânerea la toate elementele folosite în pagini părea un compromis practic.
Am apelat la API-ul Cypress pentru a genera imaginile. Comanda cy.screenshot() poate crea imagini individuale ale componentelor, iar API-ul After Screenshot ne permite să redenumim fișierele, să schimbăm directoarele și să distingem rulările de regresie vizuală de cele standard. Combinând cele două, am creat rulări care nu ne-au afectat testele funcționale și ne-au permis să stocăm imagini în folderele corespunzătoare.
În primul rând, am extins fișierul index.js în directorul nostru de pluginuri pentru a accepta cele două noi tipuri de rulare (de bază și comparație). Apoi, setăm calea pentru imaginile noastre în funcție de tipul de rulare:

// 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 } Apoi am invocat fiecare dintre rulări adăugând variabila de mediu corespunzătoare la apelul Cypress din package.json proiectului.json :
"scripts": { "cypress:baseline": "BASELINE=true yarn cypress:open", "cypress:comparison": "COMPARISON=true yarn cypress:open" }Odată ce am rulat noile noastre comenzi, am putut vedea că toate capturile de ecran făcute în timpul rulării au fost mutate în folderele corespunzătoare.
Apoi, am încercat să suprascriem cy.get() , comanda principală a Cypress pentru a returna elementele DOM și să facem o captură de ecran a oricăror elemente apelate împreună cu implementarea implicită. Din păcate, cy.get() este o comandă dificil de schimbat, deoarece apelarea comenzii originale în propria definiție duce la o buclă infinită. Abordarea sugerată pentru a rezolva această limită este de a crea o comandă personalizată separată și apoi de a face ca noua comandă să facă o captură de ecran după ce a găsit elementul:
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") }) Cu toate acestea, apelurile noastre de a interacționa cu elementele din pagină au fost deja incluse într-o funcție internă getElement() . Așa că tot ce trebuia să facem a fost să ne asigurăm că a fost făcută o captură de ecran atunci când a fost sunat wrapper-ul.
Rezultate obținute prin testarea regresiei vizuale
Odată ce am avut capturile de ecran, singurul lucru rămas de făcut a fost să le îmbinam. Pentru asta, am creat un script de nod simplu folosind Canvas. În cele din urmă, scriptul ne-a permis să generăm 618 imagini de comparație! Unele dintre diferențe au fost ușor de observat prin deschiderea Portalului de talente, dar unele dintre probleme nu au fost la fel de evidente.
Adăugarea de valoare testării UI
În primul rând, testele de regresie vizuală adăugate s-au dovedit a fi utile și au descoperit câteva probleme pe care le-am fi putut rata fără ele. Chiar dacă ne așteptam la diferențe între componentele noastre, cunoașterea a ceea ce a fost schimbat de fapt a ajutat la reducerea cazurilor problematice. Deci, dacă proiectul dvs. are o interfață, dar nu efectuați încă aceste teste, treceți la el!
A doua lecție aici, și poate cea mai importantă, este că ni s-a reamintit încă o dată că perfectul este dușmanul binelui. Dacă am fi exclus posibilitatea de a rula teste de regresie vizuală pentru această versiune, deoarece nu a existat o configurare anterioară, este posibil să fi ratat câteva erori în timpul migrării. În schimb, am convenit asupra unui plan care, deși nu era ideal, a fost rapid de executat, am lucrat la el și a dat roade.
Pentru mai multe detalii despre implementarea unei conducte robuste de regresie vizuală în proiectul dvs., vă rugăm să consultați pagina de testare vizuală a Cypress, selectați instrumentul care se potrivește cel mai bine nevoilor dvs. și vizionați videoclipurile tutorial.
