Powrót do: Praktyczne wprowadzenie do testów automatycznych z Playwright
Najprostsza implementacja wzorca POM
Więcej o nowym sposobie przeczytasz pod nagraniem, w sekcji Uwaga 😉
Prezentacja
Dodatkowe materiały
Bazujemy na kodzie lekcji L08_dane_i_wtyczka_pw
Kod wynikowy tej lekcji znajduje się tu: L03_pom_login
Pamiętaj, aby po danej porcji pracy: uruchamiać test, commitować poprawnie działający kod 😉
Jeśli w poprzednich lekcjach go nie skonfigurowałeś tych narzędzi, to możesz to zrobić bazując na lekcjach bonusowych: Wersjonowanie projektu z Git oraz Prettier, czyli formatter kodu.
Page Object Model
Page Object Model może zostać zaimplementowany na różne sposoby i z różnym poziomem złożoności. Wszystko zależy od języka programowania, zastosowanych dodatkowych wzorców, wymagań i potrzeb. W tej lekcji zaczniemy od jednej z najprostszych implementacji 🙂
Page Object Model w najprostszej postaci
Page Object Model w jednej z najprostszych postaci to klasa, która reprezentuje daną stronę. Natomiast w tej klasie znajdują się lokatory, które wskazują na elementy (przyciski, pola tekstowe, etc.), z którymi wchodzimy w interakcję podczas testów.
W dużym uproszczeniu jego implementacja polega na następujących krokach:
- Tworzymy nowy moduł (plik), który zawiera klasę, która reprezentuje testowaną stronę.
- Następnie w tej klasie deklarujemy lokatory, które odwołują się do elementów na stronie.
- W testach wykorzystujemy obiekt strony wraz z lokatorami, aby wchodzić w interakcje z elementami strony.
Zawartość pliku login.page.ts:
import { Page } from '@playwright/test' export class LoginPage { constructor(private page: Page) {} loginInput = this.page.getByTestId('login-input') passwordInput = this.page.getByTestId('password-input') loginButton = this.page.getByTestId('login-button') }
Co ważne – VS Code domyślnie korzysta z nowszych wersji reguł (dla zainteresowanych – ustawienie “JS/TS › Implicit Project Config: Target” – aktualnie ES2022).
O samym ES i konfiguracjach na razie nie musisz więcej wiedzieć, jednak…
Co to dla nas zmienia? 🤔
Aktualnie wskazane jest inicjalizowanie zmiennych w konstruktorze.
W naszym przypadku oznacza to, że inicjalizację lokatorów musimy przenieść do konstruktora. Zawartość pliku login.page.ts przyjmie postać:
import { Locator, Page } from '@playwright/test'; export class LoginPage { loginInput: Locator; passwordInput: Locator; loginButton: Locator; constructor(private page: Page) { this.loginInput = this.page.getByTestId('login-input'); this.passwordInput = this.page.getByTestId('password-input'); this.loginButton = this.page.getByTestId('login-button'); } }
Zmiana ta dotyczy w tym momencie tylko plików stron 🙂
Kod w repozytorium zaktualizowaliśmy do najnowszych wymagań.
Poprzedni kod (gdzie konstruktor mamy pusty) nadal będzie działał, ale w VS Code otrzymasz podkreślone na czerwowo this.page
oraz informacje o błędzie:
Property 'page' is used before its initialization.ts(2729) login.page.ts(4, 15): 'page' is declared here. (property) LoginPage.page: Page
Jeśli jesteś zainteresowany czym jest ECMAScript, to skorzystaj z poniższych materiałów:
Test successful login with correct credentials z pliku login.spec.ts:
test('successful login with correct credentials', async ({ page }) => { // Arrange const userId = loginData.userId; const userPassword = loginData.userPassword; const expectedUserName = 'Jan Demobankowy'; // Act const loginPage = new LoginPage(page) await loginPage.loginInput.fill(userId) await loginPage.passwordInput.fill(userPassword) await loginPage.loginButton.click() // Assert await expect(page.getByTestId('user-name')).toHaveText(expectedUserName); });
Zalety:
- zmniejszenie duplikacji kodu w testach – selektory i lokatory są w jednym miejscu (w klasie strony)
- dobra utrzymywalność – gdy nastąpi zmiana na stronie (np. zmiana ID elementu), to musimy zaktualizować selektory w klasie strony, a testy pozostają bez zmian,
- agregacja danych strony – zebranie w jednym miejscu wszystkich elementów strony, z którymi wchodzimy w interakcję,
- bardzo szybka implementacja,
- dobry dla mniejszych i prostszych testów i projektów.
Wady:
- odpowiedzialność za kolejność działań jest po stronie piszącego testu – osoba pisząca nowe testy musi pamiętać, jakie elementy w jakiej kolejności powinno się używać na danej stronie, np. najpierw wpisanie loginu, później hasła, a na końcu przyciśnięcie przycisku zaloguj,
- słabsza utrzymywalność przy większych projektach lub bardzo dużej liczbie testów – jeśli zmieni się wymagana kolejność działań (np. najpierw hasło, a później login), to będzie wymagana aktualizacja testów.
Hej, w klasie LoginPage podkreśla mi się page z komentarzem: Property ‘page’ is used before its initialization. Co jest dziwne to to, że nawet z tym podkreśleniem test wykonuje mi się poprawnie. Tak wygląda mój plik login.page.ts :
Hej,
Dobra obserwacja!
Jest to znany przez nas “błąd” i mówimy o nim dokładniej w jednej z kolejnych lekcji 🙂
https://jaktestowac.pl/lesson/pw2s05l04/
W dużym uproszczeniu mamy tu rozjazd kilku standardów:
– nasz kod,
– kod modułów importowanych,
– analiza przez ESLint.
(masz w projekcie skonfigurowany ESLint? Lub czy masz paczkę tsconfig?)
Błąd ten
Property ‘page’ is used before its initialization
jest związany ze sprawdzaniem naszego kodu pod względem danej wersji standardów.Tutaj dostajemy błąd, ze próbujemy korzystać z page, który jeszcze nie został zainicjalizowany, bo to następuje w konstruktorze. I TypeScript, próbuje nas bronić przed takim działaniem.
Jednak z racji, że jesteśmy w klasie, to również przy inicjalizacji obiektu (wywołaniu konstruktora), inicjalizowane są wszystkie pola. Więc taki zapis jest poprawny w kontekście klas! 😀
Dlatego w tym momencie mozesz ten błąd zignorować, a my później pokażemy Ci jak skonfigurować projekt, aby pozbyć się tego błędu 🙂
Hej, przy testach z użyciem Selenium korzystałam z biblioteki Page Factory, która znacznie upraszcza Page Object Model. Czy jest coś podobnego w Playwrighcie?
Hej,
Oficjalnych bibliotek nie widzieliśmy, natomiast widziałem na GitHuba różne mało popularne próby opakowania lokatorów.
Osobiście z nich nie korzystamy a sami twórcy aktualnie tez oficjalnie nie rekomendują tego 🙂
Cześć, mam problem z loginButton dostaje: ‘await’ has no effect on the type of this expression.ts(80007)
Hej,
Rozumiem, że błąd występuje w linii:
Błąd jest spowodowany brakiem
()
na końcu linii 🙂Zmień proszę linię na:
i daj znać czy to pomogło 😉
Cześć, mam problem z loginButton dostaje: ‘await’ has no effect on the type of this expression.ts(80007)
Nie było pytania, można usunąć komentarz, znalazłem przyczynę
Hej hej mam 2 pytanka,
Klasę pod loginPage skonstruowałem trochę inaczej
Pytanie pierwsze brzmi czy używając w klasie metod z wykorzystaniem odpowiednich akcji lepiej jest użyć jednej metody która wywołuje kroki po kolei – czy lepiej rozbić to wszystko na osobne metody:
– minusy – większa ilość kodu
– plusy – po każdym kroku można używać asercje w kodzie co pozwoli na dokładniejsze przetestowanie danego działania??
A pytanie drugie to
Użyłem i w testach loginu i w testach desktopa i paymentów takiej konstrukcji w before Each’u
Jak zauważyliście mimo deklaracji w klasie awaita przy metodzie wpisywania id usera musiałem dodać i w beforeEachu awaita dla tej metody ponieważ playwright nie wychwytywał tej metody – wpisywania tekstu w pole na czas i pole loginu zostawało puste – moje pytanie brzmi:
Czy to jest błąd mój czy raczej Playwrighta że nie wychwytuje wpisania z przywołanej z klasy metody asychronicznej – i to tylko na pole loginu bo submit button i wpisanie hasła nie poczebuje już awaita ??
Dzięki z góry za odpowiedź,
Pozdrawiam,
Jakub K
znawczy się w testach loginu nie robiłem before eacha dla logowania tylko do wejścia na stronę i w żadnej z metod nie musiałem potem robić awaita
Więc może to z hookiem Playwright ma problemy wiecie coś o tym ?? dzięki z góry za odpowiedź
Pozdrawiam i spokojności życzę,
Jakub K
Hej,
Sugeruje jednak agregacje akcji (czyli jedną metodę odpowiedzialną za akcje użytkownika, jaką jest logowanie), niż rozbijanie interakcji z elementami na pojedyncze metody.
Więcej o tym opowiadam w kolejnych lekcjach: https://jaktestowac.pl/lesson/pw1s03l08/ 😉 Tam też opowiadam o plusach i minusach tego podejścia 😉
W beforeEach brakuje Ci
await
przed metodami asynchronicznymi:
Bez
await
wywoływanie tych metod może powodować wyścig – pole może nie byc wypełnione przed próbą naciśnięcia przycisku. Bez await akcje i testy będą niestabilne – raz zadziała, a innym razem (i przeważnie) – nie 🙂Hej super – dzięki za wskazówkę i wytłumaczenie.
Pozdrawiam i spokojności życzę,
Jakub K
A ja mam pewną zagwozdkę 🙂 co zrobić z wait-ami i asercjami w stosunku do Page Object?
Załóżmy, że w teście chcę mieć dwie metody ‘login’ i ‘logout’.
Metoda ‘login’ zawiera:
– wprowadzenie loginu,
– hasła,
– kliknięcie ‘Zaloguj’,
– poczekanie (wait) na załadowanie się strony
– sprawdzenie (expect), czy selektor ‘.name’ ma tekst ‘Witaj, ABC’.
Metoda ‘logout’ zawiera:
– kliknięcie przycisku ‘Wyloguj’,
– poczekanie (wait) na wylogowanie
– sprawdzenie (expect), czy ponownie widoczne jest okno logowania.
Czy w Playwright wait-y i asercje nie mogą znajdować się w POM? Zostają w teście?
Czyli wygladało by to tak:
Bardzo dobre pytanie! 🙂
Tak jak piszesz – są dwa podejścia.
Jedno pozwala na umieszczanie asercji zarówno w obiektach stron oraz w testach.
Natomiast drugie – tylko na poziomie testów.
Oba mają swoje plusy i minusy.
Asercje w obiektach stron:
❌Minusem z praktycznego punktu widzenia – jeśli dodamy asercję do metody logowania, to nie będziemy mogli użyć tej metody w teście niepoprawnego logowania.
❌Mniejsza czytelność testów – na poziomie plików
.spec
nie widzimy asercji -> nie wiemy co dokładnie sprawdzamy i co jest kluczowe w danym teście.❌Niezgodne z Single Responsibility Principle
✅Możemy wykorzystywać mechanizm auto-wait – czekanie na wartość elementu jeśli wykorzystamy lokator w expect –
await expect(page.locator('h2')).toHaveText('Logowanie');
✅Implementacja metod jest bardziej zgodna z praktyką Tell Dont Ask
Asercje w testach:
❌Minusem z praktycznego punktu widzenia – jeśli dodamy asercję do metody logowania, to nie będziemy mogli użyć tej metody w teście niepoprawnego logowania.
❌Nie zawsze będzie możliwe czyste i proste wykorzystanie wcześniej wspomnianego mechanizmu auto-wait (chociaż w kolejnych lekcjach pokazuje jak podejść do testów negatywnych).
❌Implementacja metod jest mniej zgodna z praktyką Tell Dont Ask
✅Większa czytelność testów – asercje na poziomie plików
.spec
✅Zgodne z Single Responsibility Principle
Osobiście skłaniałbym się do umieszczania głównych asercji na poziomie testów 🙂 Czyli jesli mam test na logowanie – to asercje umieszczam na poziomie testu na jego końcu. W innych przypadkach, jeśli wykorzystuje logowanie, to mogę w inny sposób zweryfikować czy logowanie się powiodło (np. jesli aplikacja udostępnia API – to za pomocą szybkiej weryfikacji odpowiedzi – za pomocą metody
page.waitForResponse()
:))Jednocześnie nie wykluczałbym czasem potrzeby użycia sprawdzenia czegoś w metodach w obiektach stron. Duzo tu zależy od poziomu skomplikowania testów i samej aplikacji 🙂
PS. Bardziej szczegółowo ten temat będę omawiał w zaawansowanym kursie Przegląd Architektury Testów z pełnego Programu https://jaktestowac.pl/playwright/
Hejka,
mam pytanie związane z przekazywaniem zmiennych.
Czy powinniśmy przypisać wartości z pól obiektu “loginData.username” do zmiennej np. “loginID” czy możemy to sobie odpuścić?
Chodzi mi o to czy wszystkie wartości powinny być wyciągane do zmiennych i wrzucane do sekcji Arrange? Czy jednak można to zostawić w taki sposób jak wkleiłem wyżej?
Skoro zdefiniowałem te pola obiektu loginData, to czy jest sens dodatkowo je przypisywać do kolejnych zmiennych, szczególnie, że format przekazanego argumentu to obiekt.pole i jest on dość jednoznaczny
Hej,
Bardzo dobre pytanie 😉
Oba podejścia mają swoje plusy i minusy:
Wydzielenie do zmiennych:
✅ lepsza widoczność zmiennych i danych które używamy w testach
✅ łatwiejsza zmiana danych, jeśli w teście wykorzystujemy kilka razy dane/obiekt
loginData.username
❌ odrobina duplikacji – dodatkowa zmienna
Używanie danych z loginData bezpośrednio:
✅ mniejsza duplikacja, mniej dodatkowych zmiennych
❌ możemy mieć trudność od razu stwierdzić jakie dane są wykorzystywane w testach – musimy przeglądać cały kod testów
Jeśli mamy lekkie i krótkie testy – powiedziałbym, że można rozważyć podejście nr. 2.
Natomiast gdy mamy bardziej rozbudowane scenariusze – wtedy sugerowałbym podejście 1. 🙂
Dodatkowo pozostaje kwestia spójności kodu w różnych testach – tutaj też sugerowałbym trzymanie sie jednego podejścia. Dzięki temu my się nigdy nie zgubimy w naszym kodzie a wdrażanie nowych osób będzie łatwiejsze 🙂
świetnie, że poruszyliście ten temat i podsumowaliście 🙂
Dzięki! 🙇♂️
hej, mam pytanie o samą koncepcję POM i jej konkretne zastosowanie tutaj.
przejrzałem stronę PW odnośnie POM:
https://playwright.dev/docs/pom
i z tego co widzę jest kilka różnic w stosunku do tego, jak Wy tutaj podchodzicie do zapisu rożnych rzeczy.
Przykładowo, oni pokazują trochę inny styl deklarowania elementów w klasie:
readonly getStartedLink: Locator;
oraz później przypisanie im wartości:
this.getStartedLink = page.locator(‘a’, { hasText: ‘Get started’ });
Wy macie w klasie od razu:
loginInput = this.page.getByTestId(‘login-input’)
Możecie powiedzieć coś więcej na ten temat? Dlaczego nie robicie tak jak twórcy PW? 😀
Czy warto wybrać Wasz sposób zapisu z jakichś konkretnych powodów?
Różnice:
– readonly – dzięki temu mamy pewność, że nie nadpiszemy danej wartości
– deklaracja lokatorów – tutaj nie ma większych różnic (pomijając szczegóły jak to TS/JS później pod spodem ogarniają) – w naszym sposobie sam konstruktor jest wizualnie lżejszy
Najważniejsze, zeby po wyborze danego podejścia konsekwentnie się go trzymać w całym frameworku 🙂
ok dzieki!
Też miałem o to zapytać ponieważ zaciekawiło mnie, że deklarowane zmienne w POM są bez readonly, gdzie u siebie w pracy komercyjnej każdy POM jest tak skonstruowany. Tak jak Krzysiek napisał, najważniejsze to konsekwentne podejście, więc ja zostanę przy readonly 🙂
Część “Przemka” skomentowałem już wcześniej.
Kurs jest świetny więc chciałem się jeszcze odnieść do części Krzyska.
W skrócie – kontynuacja świetnej roboty 🙂
Z POM już pracowałem dlatego w zasadzie starczyły mi materiały udostępnione miesiąc temu czyli do tej lekcji (+ był jeszcze git hub bodajże z 7 lekcji).
Bardzo prosto przekazane są podstawy – mogłem szybko wystartować z POM w typescript. W zasadzie samego Playwrighta potem od razu porzuciłem..
Ale dosyć szybko połapałem się dzięki przerobionemu kursowi w Cypress, który pojawił się z nowym projektem.
Szczerze mówiąc jakbym miał wybierać to osobiście wolałbym Playwright – ale nie zawsze jest dana możliwość wyboru technologii. Zazwyczaj trzeba się dostosować do tego co jest 🙂
Pozdrawiam. Dziękuję za kurs.
Zapewne co jakiś czas będą do Was zaglądać bo sporo fajnych rzeczy tutaj macie.
Dzięki wielkie za pracę i zaangażowanie!
Bardzo się ciesze, że z tego czego się nauczyłeś, to mogłeś wykorzystać w swojej pracy 🙂
Wspominasz też o Cypressie. Osobiście gdybym miał do wyboru narzędzia w nowym proj – to zdecydowanie wybrałbym Playwrighta. Nawet w obecnym projekcie jestem odpowiedzialny za zmianę frameworka – z Cypressa na Playwrighta 🙂
Sam Cypress też jest dobry w konkretnych przypadkach. Z każdego narzędzia jesteśmy w stanie się czegoś nauczyć, a później wykorzystać to doświadczenie w przyszłości 🙂
Do zobaczenia w kolejnych materiałach!