Testy regresji wizualnej za pomocą Cypress: podejście pragmatyczne

Opublikowany: 2022-03-11

Za każdym razem, gdy publikowana jest nowa wersja naszej biblioteki komponentów, Picasso, aktualizujemy wszystkie nasze aplikacje typu front-end, aby jak najlepiej wykorzystać nowe funkcje i dostosować nasze projekty we wszystkich częściach naszej witryny.

W zeszłym miesiącu wprowadziliśmy aktualizację Picassa do portalu talentów Toptal, platformy, z której korzysta nasz talent do znajdowania pracy i interakcji z klientami. Wiedząc, że wydanie przyniesie poważne zmiany projektowe, a starając się zminimalizować nieoczekiwane problemy, sensowne było użycie technik testowania regresji wizualnej, aby pomóc nam znaleźć problemy przed wydaniem.

Testy regresji wizualnej nie są nową koncepcją; wiele innych projektów w Toptal już z niego korzysta, w tym sam Picasso.

Narzędzia takie jak Percy, Happo i Chromatic mogą pomóc zespołom w tworzeniu zdrowego potoku regresji wizualnej i na początku rozważaliśmy dodanie ich. Ostatecznie zdecydowaliśmy, że proces konfiguracji będzie zbyt czasochłonny i może wykoleić nasz harmonogram. Mieliśmy już wyznaczoną datę zamrożenia kodu, aby rozpocząć migrację, a ponieważ do ostatecznego terminu pozostało tylko kilka dni, nie mieliśmy innego wyjścia, jak być kreatywnym.

Testowanie regresji wizualnej poprzez testy interfejsu użytkownika

Chociaż w projekcie nie mieliśmy testów regresji wizualnej, mieliśmy dobre pokrycie testów integracji interfejsu użytkownika przy użyciu Cypress. Mimo że nie do tego najczęściej używa się tego narzędzia, Cypress ma jedną stronę w swojej dokumentacji poświęconą testom wizualnym, a drugą, która zawiera listę wszystkich dostępnych wtyczek, które pomagają skonfigurować Cypress do testowania wizualnego.

Od cyprysa do zrzutów ekranu

Po przejrzeniu dostępnej dokumentacji postanowiliśmy spróbować cypress-snapshot-plugin. Konfiguracja zajęła tylko kilka minut, a gdy już to zrobiliśmy, szybko zdaliśmy sobie sprawę, że nie dążymy do tradycyjnej regresji wizualnej.

Większość narzędzi do regresji wizualnej pomaga zidentyfikować niepożądane zmiany, porównując migawki i wykrywając różnice w pikselach między znaną, zaakceptowaną linią bazową a zmodyfikowaną wersją strony lub komponentu. Jeśli różnica pikseli jest większa niż ustawiony próg tolerancji, strona lub komponent jest oflagowywany do ręcznego sprawdzenia. Jednak w tym wydaniu wiedzieliśmy, że wprowadzimy kilka drobnych zmian w większości naszych komponentów interfejsu użytkownika, więc ustawienie progu nie miało zastosowania. Nawet jeśli dany komponent był w 100% inny, to nadal może być poprawny w kontekście nowej wersji. Podobnie odchylenie tak małe jak kilka pikseli może oznaczać, że komponent nie nadaje się obecnie do produkcji.

Zrzut ekranu przedstawiający oczekiwany wynik i rzeczywisty wynik przebiegu testowego.
Rysunek 1. Przykład niewielkich różnic w pikselach prowadzących do wyników fałszywie ujemnych

W tym momencie stało się jasne, że dwie kontrastujące ze sobą rzeczy: odnotowanie różnic w pikselach nie pomoże zidentyfikować problemów, a porównanie komponentów było dokładnie tym, czego potrzebowaliśmy. Odłożyliśmy wtyczkę migawki na bok i postanowiliśmy utworzyć kolekcję obrazów z naszymi komponentami przed i po zastosowaniu aktualizacji Picassa. W ten sposób mogliśmy szybko przejrzeć wszystkie zmiany, aby określić, czy nowe wersje nadal odpowiadają potrzebom witryny i standardom biblioteki.

Nowy plan polegał na zrobieniu zrzutu ekranu komponentu, zapisaniu go lokalnie, zrobieniu nowego zrzutu ekranu tego samego komponentu w gałęzi ze zaktualizowaną wersją Picassa, a następnie scaleniu ich w jeden obraz. Ostatecznie to nowe podejście nie różniło się zbytnio od tego, od czego zaczęliśmy, ale dało nam większą elastyczność podczas fazy wdrażania, ponieważ nie musieliśmy już importować wtyczki i używać jej nowych poleceń.

Diagram przedstawiający przepływ wizualnego porównania, w jaki sposób obrazy nowej i starej wersji są scalane po przeprowadzeniu testu wizualnego.
Rysunek 2. Przebieg porównania wizualnego

Wykorzystanie interfejsów API do obrazów porównawczych

Mając jasny cel, nadszedł czas, aby przyjrzeć się, w jaki sposób Cypress może pomóc nam uzyskać potrzebne zrzuty ekranu. Jak wspomnieliśmy, mieliśmy sporo testów interfejsu użytkownika obejmujących większość portalu talentów, więc starając się zebrać jak najwięcej krytycznych komponentów, po każdej interakcji postanowiliśmy wykonać zrzuty ekranu poszczególnych elementów.

Alternatywnym podejściem byłoby zrobienie zrzutów ekranu całej strony w kluczowych momentach testu, ale uznaliśmy, że te obrazy byłyby zbyt trudne do porównania. Ponadto takie porównania mogą być bardziej podatne na błędy ludzkie, takie jak brak zmiany stopki.

Trzecią opcją byłoby przejrzenie każdego przypadku testowego, aby zdecydować, co przechwycić, ale zajęłoby to znacznie więcej czasu, więc trzymanie się wszystkich elementów użytych na stronach wydawało się praktycznym kompromisem.

Zwróciliśmy się do API Cypress, aby wygenerować obrazy. Komenda cy.screenshot() może, po wyjęciu z pudełka, tworzyć indywidualne obrazy komponentów, a API After Screenshot pozwala nam zmieniać nazwy plików, zmieniać katalogi i odróżniać przebiegi regresji wizualnej od standardowych. Łącząc te dwa, stworzyliśmy przebiegi, które nie wpłynęły na nasze testy funkcjonalne i umożliwiły przechowywanie obrazów w odpowiednich folderach.

Najpierw rozszerzyliśmy plik index.js w naszym katalogu wtyczek, aby obsługiwał dwa nowe typy uruchamiania (linia bazowa i porównanie). Następnie ustalamy ścieżkę dla naszych obrazów zgodnie z typem uruchomienia:

 // 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 }

Następnie wywołaliśmy każdy z przebiegów, dodając odpowiednią zmienną środowiskową do wywołania Cypress w package.json projektu:

 "scripts": { "cypress:baseline": "BASELINE=true yarn cypress:open", "cypress:comparison": "COMPARISON=true yarn cypress:open" }

Po uruchomieniu naszych nowych poleceń mogliśmy zobaczyć, że wszystkie zrzuty ekranu wykonane podczas uruchamiania zostały przeniesione do odpowiednich folderów.

Zrzut ekranu przedstawiający zdjęcia wykonane podczas biegu i przeniesione do folderów.
Rysunek 3. Wizualne wyniki przebiegu

Następnie próbowaliśmy nadpisać cy.get() , główne polecenie Cypress, aby zwrócić elementy DOM, i zrobić zrzut ekranu wszystkich elementów wywołanych wraz z jego domyślną implementacją. Niestety, cy.get() jest trudnym poleceniem do zmiany, ponieważ wywołanie oryginalnego polecenia w jego własnej definicji prowadzi do nieskończonej pętli. Sugerowanym podejściem do obejścia tego ograniczenia jest utworzenie oddzielnego polecenia niestandardowego, a następnie wykonanie zrzutu ekranu po znalezieniu elementu:

 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") })

Jednak nasze wywołania interakcji z elementami na stronie były już opakowane w wewnętrzną funkcję getElement() . Więc wszystko, co musieliśmy zrobić, to upewnić się, że zrzut ekranu został zrobiony po wywołaniu wrappera.

Wyniki uzyskane za pomocą testów regresji wizualnej

Gdy mieliśmy już zrzuty ekranu, jedyne, co pozostało do zrobienia, to je połączyć. W tym celu stworzyliśmy prosty skrypt węzła przy użyciu Canvas. Ostatecznie skrypt umożliwił nam wygenerowanie 618 zdjęć porównawczych! Niektóre różnice można było łatwo zauważyć, otwierając Portal talentów, ale niektóre problemy nie były tak oczywiste.

Przed i po przykład nieprawidłowego użycia Picassa, pokazujący czerwone i czarne kolory w elemencie.
Rysunek 4. Przykład nieprzestrzegania nowych wytycznych Picassa; spodziewano się różnicy, ale nowa wersja powinna mieć czerwone tło i biały tekst

Przykład nieco uszkodzonego układu komponentu przed i po, pokazujący źle wyrównany tekst obok pola wyboru na obrazie „Po”.
Rysunek 5. Przykład lekko zepsutego układu komponentów

Dodawanie wartości do testowania interfejsu użytkownika

Po pierwsze, dodane testy regresji wizualnej okazały się przydatne i ujawniły kilka problemów, które moglibyśmy przeoczyć bez nich. Chociaż spodziewaliśmy się różnic w naszych komponentach, wiedza o tym, co faktycznie zostało zmienione, pomogła zawęzić problematyczne przypadki. Jeśli więc Twój projekt ma interfejs, ale nie wykonujesz jeszcze tych testów, przejdź do niego!

Druga lekcja, być może ważniejsza, jest taka, że ​​po raz kolejny przypomniano nam, że doskonałe jest wrogiem dobra. Gdybyśmy wykluczyli możliwość przeprowadzania testów regresji wizualnej dla tego wydania, ponieważ nie było wcześniejszej konfiguracji, moglibyśmy przeoczyć kilka błędów podczas migracji. Zamiast tego uzgodniliśmy plan, który choć nie był idealny, był szybki w realizacji, pracowaliśmy nad nim i to się opłaciło.

Aby uzyskać więcej informacji na temat implementacji solidnego potoku regresji wizualnej w swoim projekcie, odwiedź stronę testowania wizualnego Cypress, wybierz narzędzie, które najlepiej odpowiada Twoim potrzebom, i obejrzyj filmy instruktażowe.