Zdjęcie artykułu

Selektory w testach e2e i ich efektywne utrzymywanie

4m
testing
improvements
quality

Intro

Sprawdzimy wszystkie popularne podejścia, które znacznie ułatwiają utrzymywanie oraz skalowanie selektorów dla testów e2e.

Trochę o selektorach dla testów e2e

Podczas pisania testów e2e musisz zawsze wykorzystać jakiś selektor. Im bardziej precyzyjny i unikalny, tym lepiej dla Ciebie i Twoich testów. Dlaczego? Załóżmy, że mamy selektor, który wygląda w ten sposób:

Ładowanie

Teraz w testach e2e, załóżmy, że korzystamy z Cypress, nasz kod mógłby wyglądać tak:

Ładowanie

Najlepiej pisać selektory w taki sposób, aby zawsze były unikalne i niezależne od struktury HTML (zmiana w niej nie powinna wywalić testów). Tylko takie podejście da Ci stabilność w testach. Oczywiście od tej reguły mogą być wyjątki, ale jeżeli jest taka możliwość, to powinniśmy starać się przestrzegać tej zasady.

Ładowanie

Szczerze? To chyba jeszcze gorszy pomysł. Dlaczego?

  • Jak zmieni się nazwa komponentu - musisz zmienić nazwę klasy
  • Selektor jest unikalny na skalę komponentu, lecz nie jest na skalę aplikacji :)

A może zmienić klasę na #id? Tutaj będą te same problemy. No to cholbyka, co w takim razie mamy zrobić? Problem nie jest w tym, jaki atrybut ustawiamy, ale w tym jaką wartość mu przypisujemy. Powinniśmy wiedzieć, jaki jest zbiór możliwych selektorów, skąd je brać i czy są unikalne na skalę aplikacji/biblioteki, bądź funkcjonalności.

Jak osiągnąć skończoną liczbę selektorów?

Możesz stworzyć zwykły obiekt JavaScript lub JSON i wykorzystywać jego wartości w testach e2e, jak i w kodzie aplikacji.

Ładowanie

Teraz już wiemy, jaką mamy liczbę selektorów, które powinny być wykorzystane w testach e2e - wiemy, że są skończone. Również utrzymanie ich będzie o wiele prostsze (zmiana w jednym miejscu).

Dalej widzę tu dwa problemy:

  • Ten obiekt może być koszmarnie duży - co będzie rzutowało na rozmiar aplikacji,
  • Każdy może użyć dowolnego atrybutu: klasa, id lub inny,

Jak pozbyć się dużego obiektu i odchudzić aplikację?

Najpierw zdefiniujmy string literals z TypeScript, które określą jakich selektorów możemy użyć.

Ładowanie

Teraz zarówno w kodzie testów e2e, jak i kodzie aplikacji będzie korzystać z następującej funkcji:

Ładowanie

Funkcja, która jedyne, co robi, to bierze parametr i go zwraca? LOL... No ok, to teraz patrz na to. Zamist dużego obiektu masz funkcję oraz type literals, które określają zawsze skończoną liczbę selektorów. Następującą funkcję należy teraz zaimportować i wywołać w aplikacji oraz w testach. Wygąda to tak:

Ładowanie

Zwróć uwagę na podpowiedzi, które otrzymujesz od TypeScript

Tworzymy fasadę na Cypress.get

No dobra, ale w dalszym ciągu każdy może użyć sobie dowolnego atrybutu. Jeden deweloper będzie wykorzystywać klasy, inny id, a jeszcze inny skorzysta z selektorów dostępności (accessibility), które dadzą możliwość używania strony osobom niepełnosprawnym.

Ładowanie

Powinniśmy używać tylko i wyłącznie konkretnych atrybutów (skończonej liczby) - na potrzeby przykładu będzie to data-attribute o nazwie data-i. Może być ich tyle ile chcemy, ale ilość musi być skończona i zdefiniowana w jednym miejscu.

Ładowanie

Zwróć uwagę na to, że przekazujemy instancję Cypress i de facto ją zwracamy, ale z przechwyconym typem.

A tak wygląda użycie w kodzie testów oraz aplikacji.

Ładowanie

Wybór selektorów dla testów e2e

No dobra, ale co z good practices? Część deweloperów uważa, że selektory dla testów e2e powinny być tam, gdzie możliwe oparte o atrybuty dostępności - accessibility. To zależy...

Jeżeli ktoś chce przetestować jednocześnie accessibility oraz ścieżki biznesowe aplikacji w testach e2e, musi się liczyć z tym, że takie testy mogą znacznie częściej psuć się i wymagać zmiany. Wykorzystanie data-attributes pozwala na oznaczenie elementu HTML unikalną wartością, która referuje do tego, czym ten element jest w kontekście biznesowym, a ten atrybut będzie tylko i wyłącznie używany przez testy e2e.

Dzięki temu zmiana styli, logiki, dodanie innych atrybutów, czy zmiana samej struktury HTML (zmiana kolejności, dodanie elementu wyżej lub niżej oraz zagnieżdżenie) - nie popsuje naszych testów.

Sama dokumentacja Cypress mówi o tym, że wykorzystanie data-cy to najlepsza możliwa praktyka. Nasze selektory będa odseparowane całkowicie od zmian w stylach, czy logice. Nie znięchęcam do używania selektorów accessibility, ale należy się liczyć z tym, że takie testy e2e będą znacznie cięższe do utrzymania, a zmiany w strukturze HTML będą psuły testy, a nie powinny.

Moim zdaniem to idealny przykład wyboru rozwiązania po preferencjach dewelopera, a nie zyskach/stratach. Ja wybieram opcję z data-attributes dlatego, że (i nie mówię Ci, że to best practice):

  1. Testy e2e są czasochłonne i kosztowne. Każdy false negative marnuje nam czas. Dlatego wolę ograniczyć ilość failujących testów w momencie zmiany struktury HTML'a.
  2. Wolę testować accessibility w izolacji - na poziomie testów jednostkowych komponentu, bądź nawet napisać testy e2e tylko dla komponentu, sprawdzające accessibility.
  3. Testy z data-attributes są bardzo łatwe w utrzymaniu i definiowaniu skończonej liczby selektorów e2e.

Link do dokumentacji Cypress z przykładem, o którym wspominałem: Cypress best practices.

Skalowanie selektorów i definicji typów

Jedna funkcja do obsługi wszystkich selektorów to trochę mało. Aby lepiej skalować podpowiedzi, które TypeScript będzie nam oferował, możemy stworzyć kilka funkcji (per feature):

Ładowanie

Podsumowanie i słów kilka o testach e2e

Testy e2e są piekielnie czasochłonne. Muszą być napisane w taki sposób, aby były odporne na zmianę implementacji - style, logika, struktura dokumentu HTML. Stosowanie selektorów w oparciu o data-attributes zapewnia odporność na wszystkie trzy wymienione.

Zmieniłeś nazwę klasy? Zmieniłeś strukturę HTML'a? Dodałeś nowe elementy? Testy będą dalej działać prawidłowo i nie będą wymagać żadnej zmiany, o tyle jeżeli funkcjonalność rzeczywiście działa i selektor z data-attribute nie uległ zmianie.

Proszę, potraktuj ten wpis jako ciekawostkę. Naprawdę nie lubię nazywać rzeczy jako good/bad practice, bo sądzę, że jeżeli ktoś umie uargumentować jakieś rozwiązanie, to jest to wystarczające do stosowania go. Oczywiście, jeżeli te argumenty do Ciebie trafiają. Sprawdź sam i wypracuj własną opinie!