przemuh.dev

3 kroki do niesamowitych raport贸w w cypress.io

12/18/2019

W tym wpisie nauczymy si臋 jak wygenerowa膰 rozbudowane raporty z test贸w napisanych w cypress.io. Wzbogacimy je tak偶e o zrzuty ekranu, co powinno pom贸c w znacznie szybszej naprawie potencjalnych bug贸w 馃槃 Wszystko czego potrzebujemy to trzy proste kroki.

Egnyte + testy = 鉂わ笍

Utrzymanie mo偶liwie jak najwy偶szej jako艣ci produktu to jedno z naszych priorytet贸w w Egnyte. Dlatego kochamy testowa膰 馃槏. Nasze aplikacje do ma艂ych nie nale偶膮, dlatego ich r臋czne testowanie by艂o by dla nas bardzo wyczerpuj膮ce. W zwi膮zku z tym, automatyzacja test贸w i technika Continous Integration s膮 naszymi najlepszymi przyjaci贸艂mi. Piszemy wiele rodzaj贸w test贸w: unit, integration, end-to-end, modu艂owe itd. Ale to nie nazwy s膮 tu najwa偶niejsze. To co si臋 liczy najbardziej, to pewno艣膰, 偶e gdy widzisz zielony pipeline na Jenkinsie, to potencjalnie nie popsu艂e艣 偶adnej cz臋艣ci systemu.

Wi臋c w czym problem? Wiedzieli艣cie, 偶e testy nie zawsze przechodz膮? 馃槺 I to jest jak najbardziej ok :) (przynajmniej do czasu znalezienia przyczyny). Nie musimy od razu panikowa膰. Po pierwsze, spok贸j. Tylko spok贸j mo偶e nas uratowa膰. Widzimy czerwony pipeline - wchodzimy na Jenkinsa, sprawdzamy co jest nie tak i fixujemy. To tyle. Problem polega na tym, 偶e raport z test贸w na Jenkinsie to bardzo cz臋sto, zwyk艂y tekst wraz ze zrzutem ze stack-trace. Taki raport jest jak najbardziej ok dla unit test贸w albo test贸w integracyjnych dla naszych komponent贸w Reactowych, po艂膮cze艅 z Reduxem, itd. Z drugiej strony, taka 艣ciana tekstu nie zawsze jest przydatna dla test贸w uruchamianych w przegl膮darce (full UI). Sp贸jrzmy na fragment raportu dla przyk艂adowego testu:

Raport z test贸w na Jenkinsie
Raport z test贸w na Jenkinsie

Tak wygl膮da podstawowy raport z testu na Jenkinsie dla jednego z naszych produkt贸w (Egnyte Protect). Do pisania test贸w integracyjnych-UI, korzystamy z bombowego 馃挘 narz臋dzia cypress.io. Musz臋 przyzna膰, 偶e Cypress wraz z rozszerzeniem cypress-testing-library robi膮 wspania艂膮 robot臋 je艣li chodzi o tzw. error messages. Na pierwszy rzut oka wida膰, 偶e cypress nie potrafi艂 znale藕膰 elementu z zadan膮 warto艣ci膮 tekstow膮. Timed out retrying: Expect to find element: findByText("..."), but never found it. Ok, ale jaki by艂 stan wizualny naszej aplikacji? Jako developer bior膮cy udzia艂 w projekcie Egnyte Protect, wiem, 偶e element kt贸rego szukamy powinien znajdowa膰 si臋 w dialogu. Ale czy dialog zosta艂 otwarty? A mo偶e to tylko liter贸wka? Tyle pyta艅 a tak ma艂o odpowiedzi. Je艣li teraz chcieliby艣my sprawdzi膰 o co biega, musieliby艣my uruchomi膰 testy lokalnie i zobaczy膰 stan wizualny naszej aplikacji. Dopiero wtedy dowiedzieliby艣my si臋, 偶e mamy doczynienia (spoiler alert) z liter贸wk膮 :)

A co by by艂o, gdyby zamiast 艣ciany tekstu, pokaza膰 zrzut ekranu z aplikacji?

Zrzut ekranu z testowanej aplikacji
Zrzut ekranu z testowanej aplikacji

Wow! Teraz wiemy, 偶e dialog zosta艂 otwarty, i 偶e tekst w nag艂贸wku jest niepoprawny! Dostali艣my nieoceniony kontekst, potrzebny do zdiagnozowania b艂臋du, od razu w raporcie wzbogaconym o zrzut ekranu.

Zatem, jak to osi膮gn膮膰? Jak doda膰 zrzuty ekranu do raportu z test贸w? I w ko艅cu - jak taki raport wygenerowa膰? Jedziemy z koksem!

Raport HTML p臋dzi z pomoc膮!

Cypress bazuje na frameworku mocha.js. To dla nas 艣wietna wiadomo艣膰 - poniewa偶 mocha.js to bardzo dojrza艂y projekt, posiadaj膮cy mn贸stwo rozszerze艅. Rezultaty test贸w generowane s膮 w mocha.js za pomoc膮 tzw. reporter贸w. Taki reporter mo偶emy napisa膰 samemu, albo mo偶emy skorzysta膰 ju偶 z istniej膮cego np. mochawesome. Jak sama nazwa wskazuje, generuje on AWESOME raporty! Badum tsss.

Zobaczmy teraz, jak mo偶emy zintegrowa膰 mochawesome z cypress w celu wygenerowania raportu HTML wraz ze zrzutem ekranu dla test贸w zako艅czonych b艂臋dem. Aby zwizualizowa膰 wszystkie zmiany, kt贸re wprowadzi艂em na potrzeby tego wpisu, skorzysta艂em z przyk艂adowego repozytorium cypress-example-kitchensink. Integracj臋 przeprowadzimy w trzech prostych krokach. Do dzie艂a!

Krok 1鈥-鈥妔etup reportera

Po pierwsze, musimy zainstalowa膰 odpowiednie reportery. Tak! Dok艂adnie - liczba mnoga - reportery. Opr贸cz generowania raport贸w HTML, nadal chcieliby艣my wy艣wietla膰 wyniki w konsoli, a by膰 mo偶e tak偶e w specjalnym formacie JUnit XML. Dla ka偶dego, z tego typu wynik贸w, musimy mie膰 osobny reporter. W tym celu skorzystamy z paczki cypress-multi-reporters, kt贸ra umo偶liwia skorzystanie z wielu reporter贸w dla cypress.io. Opr贸cz tego musimy zainstalowa膰 jeszcze pakiet mocha, oraz oczywi艣cie mochawesome.

npm install --save-dev mocha cypress-multi-reporters mochawesome

Lub je艣li korzystacie z yarn:

yarn add -D mocha cypress-multi-reporters mochawesome

Nast臋pnie w pliku konfiguracyjnym cypress.config, musimy wskaza膰 reporter, z kt贸rego b臋dziemy korzysta膰:

{
  "reporter": "cypress-multi-reporters",
    "reporterOptions": {
      "configFile": "reporter-config.json"
    }
}

Pole configFile wskazuje na plik konfiguracyjny reporter-config.json, kt贸ry zawiera opcje dla poszczeg贸lnych reporter贸w. Plik ten powinni艣my doda膰 do repozytorium. Zobaczmy teraz jak wygl膮da jego zawarto艣膰:

{
    "reporterEnabled": "mochawesome",
    "mochawesomeReporterOptions": {
        "reportDir": "cypress/results/json",
        "overwrite": false,
        "html": false,
        "json": true
    }
}

Ustawiamy folder docelowy, do kt贸rego trafi膮 nasze wyniki. Chcemy, aby zosta艂y one zapisane w formacie JSON, osobno dla ka偶dego pliku z testami. Dlatego ustawiamy flag臋 html na warto艣膰 false. Cypress potrafi uruchamia膰 testy r贸wnolegle, dlatego aby nie nadpisa膰 wygenerowanych ju偶 wynik贸w, ustawiamy flag臋 overwrite na false.

Spr贸bujemy teraz uruchomi膰 nasze testy za pomoc膮 komendy npm run local:run.

Running:  examples/location.spec.js                                                      (9 of 19)
Location
    鉁 cy.hash() - get the current URL hash (169ms)
    鉁 cy.location() - get window.location (101ms)
    鉁 cy.url() - get the current URL (78ms)
3 passing (1s)
[mochawesome] Report JSON saved to /Users/przemuh/dev/cypress-example-kitchensink/cypress/results/json/mochawesome_008.json

Jak widzicie, w konsoli dostali艣y wyniki dla poszczeg贸lnych test贸w (domy艣lny spec-reporter). Zaraz pod nimi widzimy informacj臋 na temat utworzonego pliku mochawesome_008.json. Ka偶dy plik z testami wygeneruje nam osobny plik z wynikami.

Lista wygenerowanych plik贸w z wynikami test贸w
Lista wygenerowanych plik贸w z wynikami test贸w

Jeste艣my gotowi do implementacji nast臋pnego kroku.

Krok 2鈥-鈥奼enerowanie raportu

Zebrali艣my pliki z wynikami test贸w. Teraz, musimy po艂膮czy膰 je w jeden plik i na jego podstawie wygenerowa膰 raport HTML. W tym celu skorzysamy z narz臋dzia mochawesome-merge. Zainstalujmy je!

npm i --save-dev mochawesome-merge
yarn add -D mochawesome-merge

Teraz, dodajmy skrypt npm, kt贸ry b臋dzie odpowiedzialny za uruchamianie narz臋dzia do 艂膮czenia wynik贸w.

"report:merge": "mochawesome-merge --reportDir cypress/results/json > cypress/results/mochawesome-bundle.json"

Flaga reportDir m贸wi o tym, w jakim folderze znajduj膮 si臋 pliki do po艂膮czenia. Wynik 艂膮czenia wyrzucony b臋dzie na standardowe wyj艣cie (w naszym przypadku b臋dzie to konsola) dlatego, przekierowujemy wyj艣cie do pliku mochawesome-bundle.json. Jedna uwaga: wynik 艂膮czenia powinien znajdowa膰 si臋 w innym folderze ni偶 pliki z poszczeg贸lnymi wynikami test贸w. W przeciwnym wypadku dostaniemy b艂膮d.

Po zmergowaniu wynik贸w jeste艣my gotowi do wygenerowania raportu HTML. Potrzebujemy do tego jeszcze jednej paczki mochawesome-report-generator.

npm i --save-dev mochawesome-report-generator
yarn add -D  mochawesome-report-generator

Tak jak w przypadku 艂膮czenia, utworzymy sobie teraz skrypt, kt贸ry b臋dzie uruchamia艂 narz臋dzie do generowania raportu HTML:

"report:generate": "marge cypress/results/mochawesome-bundle.json -o cypress/reports/html"

S艂贸wko marge to skr贸t od MochawesomeReportGEnerator - to tak w ramach gdyby艣cie si臋 zastanawiali :)

Po poprawym wykonaniu skryptu, nasz raport HTML powinien pojawi膰 si臋 w katalogu cypress/results/html.

Widok raportu HTML
Widok raportu HTML

Zosta艂a tylko jedna, ma艂a rzecz. Dodanie zrzutu ekranu do wyniku testu zako艅czonego b艂臋dem.

Krok 3鈥-鈥妟rzut ekranu

Cypress automatycznie generuje zrzuty ekranu dla test贸w, kt贸re z jakiego艣 powodu nie przesz艂y. Jest to domy艣lne zachowanie, kt贸re mo偶na wy艂膮czy膰. Wygenerowane obrazki zbierane s膮 w folderach, kt贸re przyjmuj膮 nast臋pujac膮 struktur臋:

path-to-the-specfile/spec.file.js/context - describe - describe - testTitle (failed).png

Rozwa偶my sobie test, kt贸ry znajduje si臋 w katalogu examples/actions.spec.js:

context('Actions', () => {
  context("nested context", () => {
      it('.type() - type into a DOM element', () => {})
   })
})

Wygeneruje on zrzut ekranu, kt贸ry znajdzie si臋 w nast臋puj膮cej strukturze folder贸w:

Struktura folder贸w ze zrzutem ekranu
Struktura folder贸w ze zrzutem ekranu

Ok, to jak po艂膮czy膰 te dwa elementy: zrzut ekranu wygenerowany przez Cypress i wynik testu wygenerowany przez mochawesome reporter?

Po pierwsze, skopiujmy sobie nasze zrzuty ekranu do folderu, w kt贸rym trzymamy nasz raport HTML. W tym celu napiszemy sobie kolejny skrypt npm:

"report:copyScreenshots": "cp -r cypress/screenshots cypress/results/html/screenshots"

Nast臋pnie, w pliku cypress/support/index.js, napiszmy kawa艂ek kodu odpowiedzialny za nas艂uchiwanie na event test:after:run

Cypress.on("test:after:run", (test, runnable) => {
    if (test.state === "failed") {
        // do something
    }
});

Aby doda膰 zrzut ekranu do wyniku testu, musimy skorzysta膰 z metody addContext z pakietu mochawesome. Metoda ta, przyjmuje dwa argumenty: obiekt z testem oraz tzw. context. Je艣li context jest poprawnym adresem URL (mo偶e by膰 lokaln膮 艣cie偶k膮) do obrazka, wtedy obrazek ten b臋dzie wy艣wietlany pod wynikiem testu. Oczywi艣cie to nie musi by膰 tylko obrazek. Po wi臋cej info odsy艂am do dokumentacji.

import addContext from 'mochawesome/addContext'

Cypress.on("test:after:run", (test, runnable) => {
    if (test.state === "failed") {
        const imageUrl = "?";
        addContext({ test }, imageUrl);
    }
});

Wszystko spoko, ale sk膮d mamy wzi膮膰 ten imageUrl ? ...

Pora na odrobin臋 magii

呕artuj臋 :) Skorzystamy z API jakie daje nam mocha, a dok艂adniej z tzw. runnable object. Jak widzieli艣my wcze艣niej, Cypress generuje nazwy obrazk贸w na podstawie struktury testu. Musimy to teraz odtworzy膰:

Cypress.on('test:after:run', (test, runnable) => {
  if (test.state === 'failed') {
    let item = runnable
    const nameParts = [runnable.title]

    // Iterate through all parents and grab the titles
    while (item.parent) {
      nameParts.unshift(item.parent.title)
      item = item.parent
    }

    const fullTestName = nameParts
            .filter(Boolean)
            .join(' -- ')           // this is how cypress joins the test title fragments

    const imageUrl = `screenshots/${
      Cypress.spec.name
    }/${fullTestName} (failed).png`

    addContext({ test }, imageUrl)
  }
})

Od tej chwili, je艣li nasz test zawiedzie, w naszym pliku wynikowym zostanie doklejony context z adresem do wygenerowanego obrazka:

{
  "title": ".type() - type into a DOM element",
  "fullTitle": "Actions .type() - type into a DOM element",
  "timedOut": null,
  "duration": 10395,
  "state": "failed",
  "speed": null,
  "pass": false,
  "fail": true,
  "pending": false,
  "context": "screenshots/examples/actions.spec.js/Actions -- .type() - type into a DOM element (failed).png",
}

Co wi臋cej - obrazek ten zostanie dodany do raportu HTML.

Raport HTML wraz ze zrzutem ekranu
Raport HTML wraz ze zrzutem ekranu

TADA 馃帀 Mamy to!

Wszystkie zmiany, kt贸rych dokonali艣my, mo偶ecie zobaczy膰 w jednym miejscu: https://github.com/przemuh/cypress-example-kitchensink/pull/1/files

Kroki opcjonalne

Dobrym pomys艂em jest dodanie do .gitignore folder贸w cypress/results i cypress/reports.

Fajnie by艂o by te偶 "czy艣ci膰" foldery z generowanymi assetami (wyniki, zrzuty, itp.). Jak to zrobi膰? Zgodnie z "tradycj膮" dodamy teraz skrypt npm odpowiedzialny za t臋 akcj臋:

"precy:run": "rm -rf cypress/screenshots cypress/results cypress/reports"

Te trzy literki "pre", zancz膮 ni mniej ni wi臋cej, jak to, 偶e skrypt ten odpalony b臋dzie przed wywo艂aniem skrypu cy:run. Wi臋cej info w dokumentacji npma.

W u偶ytym, przyk艂adowym repo, zainstalowany zosta艂 pakiet npm-run-all. Mo偶emy z niego skorzysta膰 aby uruchomi膰 wszystkie, stworzone do tej pory skrypty w zadanej kolejno艣ci (sekwencyjnie):

"report": "run-s report:*",
"report:merge": "mochawesome-merge --reportDir cypress/results/json > cypress/results/mochawesome-bundle.json",
"report:generate": "marge cypress/results/mochawesome-bundle.json -o cypress/reports/html",
"report:copyScreenshots": "cp -r cypress/screenshots cypress/reports/html/screenshots"

Pozosta艂a jeszcze jedna sprawa do rozwa偶enia. W wi臋kszo艣ci system贸w operacyjnych, nazwa pliku ograniczona jest do 255 znak贸w. Co si臋 stanie je艣li zagnie藕dzimy nasz test wielopoziomo? To proste! Zostanie przyci臋ty :) Cypress sam przycina nazwy wygenerowanych obrazk贸w do 220 znak贸w. Powinni艣my to odzwierciedli膰 w naszym kodzie:

const MAX_SPEC_NAME_LENGTH = 220;
const fullTestName = nameParts
    .filter(Boolean)
    .join(" -- ")
    .slice(0, MAX_SPEC_NAME_LENGTH);

Niestety jest to tzw. szczeg贸艂 implementacyjny. Nie wiemy czy programi艣ci cypressa zmieni膮 kiedy艣 ten numer. Dlatego, lepsz膮 opcj膮 b臋dzie nie zagnie偶d偶anie test贸w na tak g艂臋bokich poziomach. Polecam lektur臋 posta od KentCDoddsa - Avoiding nesting when you are testing, w kt贸rym pokazuje, do jakich problem贸w mo偶e prowadzi膰 zagnie偶d偶anie w testach.

Podsumowanie

Mam nadziej臋, 偶e ten artyku艂 pom贸g艂 Wam w poprawnym skonfigurowaniu 艣rodowiska pod generowanie raport贸w HTML z cypressa. Tego typu raporty bardzo pomagaj膮 nam, w Egnyte, w szybkim sprawdzeniu gdzie le偶y 藕r贸d艂o problemu - wierz臋, 偶e pomog膮 i Wam :)

Podsumujmy nasze 3 proste kroki:

  1. Instalacja i konfiguracja reporter贸w
  2. Zebranie wynik贸w i wygenerowanie raportu HTML
  3. Dekoracja raportu kontekstem - zrzutem ekranu - za pomoc膮 metody addContext.

Wszystkie niezb臋dne zmiany, mo偶ecie zobaczy膰 w przyst臋pnej formie pull requesta: https://github.com/przemuh/cypress-example-kitchensink/pull/1/files. Oczywi艣cie po wygenerowaniu raportu musicie jeszcze go podpi膮膰 pod narz臋dzie CI. Ale to ju偶 historia na osobnego posta. :)

To jak? Jeste艣cie gotowi na stworzenie swojego raportu HTML z cypress.io?