Powrót do: Praktyczne wprowadzenie do testów automatycznych z Playwright
Rozwiązanie: Kolejny test pulpitu
Dodatkowe materiały
Link do testowanej stronyTwoim zadaniem było przygotowanie testu automatycznego dla operacji doładowania telefonu na stronie Pulpit. Poniżej znajdziesz porady jak podejść do tego zadania oraz jego rozwiązanie 😉
Nagrywanie testu
Aby nagrać test za pomocą codegen użyj polecenia:
npx playwright codegen [adres]
czyli np:
npx playwright codegen https://demo-bank.vercel.app/
Uruchomienie jednego testu
Podczas pisania nowego testu warto często go uruchamiać, aby sprawdzić, czy podążamy w dobrą stronę 😉 Aby skupić się tylko na tym, co piszemy, warto uruchamiać tylko jeden testy
Aby to zrobić, skorzystaj z konstrukcji only() np.:
test.only('successful mobile top-up', async ({ page }) => {
Przerywanie działającego procesu w konsoli
Kliknij w terminalu, w którym proces node.js nadaj jest uruchomiony, a następnie użyj skrótu CTRL + C, aby przerwać działający proces.
Komentowanie wiele linii
Zaznacz wiele linii i użyj skrótu CTRL + /
Duplikacja danej linii
Ustaw kursor na linii, którą chcesz zduplikować, a następnie naciśnij ALT + SHIFT + ↓
Selektory i wyszukiwanie elementów na stronie
Aby wyszukać elementy w przeglądarce za pomocą selektorów CSS otwórz narzędzia developerskie (DevTools). Następnie otwórz konsolę i użyj następującej konstrukcji:
$$(‘selektor CSS’)
Wyszukiwaniu elementów w przeglądarce omawiamy w naszych lekcjach bonusowych:
Cały kod
Zawartość pliku pulpit.spec.ts z rozwiązaniem zadania w postaci testu successful mobile top-up:
import { test, expect } from '@playwright/test';
test.describe('Pulpit tests', () => {
test('quick payment with correct data', async ({ page }) => {
await page.goto('https://demo-bank.vercel.app/');
await page.getByTestId('login-input').fill('testerLO');
await page.getByTestId('password-input').fill('password');
await page.getByTestId('login-button').click();
await page.locator('#widget_1_transfer_receiver').selectOption('2');
await page.locator('#widget_1_transfer_amount').fill('150');
await page.locator('#widget_1_transfer_title').fill('pizza');
await page.getByRole('button', { name: 'wykonaj' }).click();
await page.getByTestId('close-button').click();
await expect(page.locator('#show_messages')).toHaveText('Przelew wykonany! Chuck Demobankowy - 150,00PLN - pizza')
});
test('successful mobile top-up', async ({ page }) => {
await page.goto('https://demo-bank.vercel.app/');
await page.getByTestId('login-input').fill('testerLO');
await page.getByTestId('password-input').fill('password');
await page.getByTestId('login-button').click();
await page.locator('#widget_1_topup_receiver').selectOption('503 xxx xxx');
await page.locator('#widget_1_topup_amount').fill('50');
await page.locator('#uniform-widget_1_topup_agreement span').click();
await page.getByRole('button', { name: 'doładuj telefon' }).click();
await page.getByTestId('close-button').click();
await expect(page.locator('#show_messages')).toHaveText('Doładowanie wykonane! 50,00PLN na numer 503 xxx xxx')
});
});

Czesc,
Niestety moje testy nie przechodza i nie wiem dlaczego. Dodalam czekanie na zaladowanie strony, ale to nie pomoglo. Ostatecznie skopiowalam kod z githuba od was wiec wyglada jak ponizej i on tez nie działa.
Przy wywolaniu testu 1 raz, nie przechodzi. Przy 10 razach, nie przechodzi rowniez 😀
Moj raport wyglada rozpaczliwie: na 10 testow; 4 falky, 6 failed.
bledy mam albo tutaj:
Error: locator.click: Test timeout of 30000ms exceeded. Call log: - waiting for getByTestId('close-button') 15 | 16 | await page.getByRole('button', { name: 'wykonaj' }).click(); > 17 | await page.getByTestId('close-button').click(); | ^albo tutaj:
Error: expect(locator).toHaveText(expected) failed Locator: locator('#show_messages') Expected: "Przelew wykonany! Chuck Demobankowy - 150,00PLN - pizza" Received: "Brak wiadomości" Timeout: 5000msPOMOCY!
import { test, expect } from '@playwright/test'; test.describe('Pulpi tests', () => { test.describe.configure({ retries: 2 }); test.only('quick payment with correct data', async ({ page }) => { await page.goto('https://demo-bank.vercel.app/'); await page.getByTestId('login-input').fill('testerLO'); await page.getByTestId('password-input').fill('password'); await page.getByTestId('login-button').click(); await page.locator('#widget_1_transfer_receiver').selectOption('2'); await page.locator('#widget_1_transfer_amount').fill('150'); await page.locator('#widget_1_transfer_title').fill('pizza'); await page.getByRole('button', { name: 'wykonaj' }).click(); await page.getByTestId('close-button').click(); await expect(page.locator('#show_messages')).toHaveText( 'Przelew wykonany! Chuck Demobankowy - 150,00PLN - pizza', ); }); test('successful mobile top-up', async ({ page }) => { await page.goto('https://demo-bank.vercel.app/'); await page.getByTestId('login-input').fill('testerLO'); await page.getByTestId('password-input').fill('password'); await page.getByTestId('login-button').click(); await page.locator('#widget_1_topup_receiver').selectOption('503 xxx xxx'); await page.locator('#widget_1_topup_amount').fill('50'); await page.locator('#uniform-widget_1_topup_agreement span').click(); await page.getByRole('button', { name: 'doładuj telefon' }).click(); await page.getByTestId('close-button').click(); await expect(page.locator('#show_messages')).toHaveText( 'Doładowanie wykonane! 50,00PLN na numer 503 xxx xxx', ); }); });Cześć Patrycja!
Dzięki za podzielenie się kodem i logami – to bardzo ułatwia diagnozę! 🙂
Trafiasz tutaj na klasyczny przypadek “zbyt szybkich testów” w połączeniu ze specyficznym zachowaniem demobank
Błąd Received: “Brak wiadomości” sugeruje, że w momencie, gdy Playwright próbuje sprawdzić tekst, aplikacja zdążyła już zresetować kontener wiadomości (dzieje się to często przy zamykaniu modala).
Widzę, że masz już dodane
test.describe.configure({ retries: 2 });, co jest super praktyką, ale przy tak dużym “flakiness” (4/10) warto zastosować pewien trik logiczny.Spróbuj zmienić kolejność działań w teście. Najpierw sprawdź, czy wiadomość się pojawiła (asercja), a dopiero potem zamknij okno potwierdzenia (click). Dzięki temu sprawdzisz tekst, zanim aplikacja zdąży go wyczyścić.
Twój kod w sekcji test.only mógłby wyglądać tak:
await page.getByRole('button', { name: 'wykonaj' }).click(); // Zmiana kolejności: najpierw asercja await expect(page.locator('#show_messages')).toHaveText( 'Przelew wykonany! Chuck Demobankowy - 150,00PLN - pizza', ); // Dopiero potem zamknięcie modala await page.getByTestId('close-button').click();Co prawda zmienia to nieco “ścieżkę użytkownika” (bo user zwykle najpierw klika OK), ale w przypadku tak zachowującej się aplikacji pozwala to ustabilizować testy.
Daj znać, czy wynik się poprawił! 💪
Pozdrawiam!
Dziekuje za odpowiedz 🙂
Niestety to nie pomoglo 🙁 ALE za to pomogla zmiana na szybsza siec.
Musze pamietac, zeby sie podlaczac do dobrego internetu i powinnam przeżyć 😀
WOW!
Tego się nie spodziewałem – ale to ciekawy przypadek do zapamiętania❤️
Pozdrawiam!
I drugie pytanie, z czystej ciekawości: czy jest jakiś konkretny powód, dla którego zostawiłeś w asercji
page.locatorzamiastpage.getByTestId?Z wcześniejszej lekcji zanowowałem sobie, że gradacja (hierarchia) jest jak poniżej:
– getByTestId
– getByRole
– locator
co mi podpowiada, że lepiej użyć “ważniejszego” jeśli mamy taką możliwość
W tym przypadku to pozostawiłem to bardziej do celów naukowych – refaktoryzacja do użycia test id zdecydowanie to byłby dobry kierunek i spójne podejscie do kodu testów 🙂
Więc hierarchia jak najbardziej obowiązuje 🙂
Czyli dobrze rozumujesz!
Pozdrawiam
akcja
await page.getByTestId('close-button').click()jest zbyteczna w tym teście i można ją śmiało usunąć bo nie ma wpływu na tę konkretną asercjęKomunikat z asercji pojawia się w chwili “doładowania”
Niby drobiazg ale skoro można skrócić test to czemu nie skorzystać 🙂
Hej Marcin!
Co do asercji pełna zgoda – jednak…
A co by było gdyby to był nasz jedyny biznesowy test i przycisku close zostałby usunięty przez przypadek – wspomniana linia to wychwyci 🙂
Oczywiście to taki experyment logiczny ale możemy zakładać, że istnieje zbiór gdzie taka linia kodu ma sens 😀
Hej 🙂 Chyba jednak potrzebuję waszej pomocy. Po wykonaniu testu: quick payment with correct data pojawia mi się taki błąd:
Error: locator.click: Test timeout of 30000ms exceeded. Call log: - waiting for getByTestId('close-button') 24 | 25 | await page.getByRole('button', { name: 'wykonaj' }).click(); > 26 | await page.getByTestId('close-button').click(); | ^ 27 | 28 | // Assert 29 | await expect(page.locator('#show_messages')).toHaveText( at /Users/henrykkadela/Project/L02_wzorzec_aaa/tests/pulpit.spec.ts:26:44Dla pewności pobrałam Wasz kod z repozytorium, niestety błąd dalej się pojawia.
Hej Justyna!
Ciekawy problem.
Sprawdziłem z kodem załączonym z lekcji jak i z tym z repozytorium
S01_wprowadzenie/L05_rozwiazanie_zadania/tests/pulpit.spec.ts
https://github.com/jaktestowac/playwright_automatyzacja_wprowadzenie/blob/main/S01_wprowadzenie/L05_rozwiazanie_zadania/tests/pulpit.spec.ts
I u mnie testy przechodzą.
Będę bardzo wdzięczny za podesłania na maila kontakt[małpa]jaktestowac.pl spakowanego projektu (jako paczka zip).
Przeanalizujemy w czym może być problem.
Jeszcze poprosiłbym o wersję Node (w terminalu node -v) i typ systemu.
Rozwiążemy ten błąd!
Pozdrawiam 🙂
Bardzo dziękuje! Mail już został wysłany. 🙂
Pozdrawiam, Justyna 🙂
Dzięki, działamy💪
Szczególnie cenna jest ta analiza krok po kroku. Sprawdzanie lokatorów na różne sposoby, gdzie dzieje się faktycznie akcja na stronie. A potem rozważenie optymalnego dla danego testu wyboru.
Myślę, że dla osób z doświadczeniem w testach manualnych może to jest intuicyjne ale dla nowicjuszy to zaprezentowanie “jak myśleć pisząc testy” jest bezcenne. 🙂
Dzięki wielkie!
Ciesze się, że Ci się podoba nasz sposób 🙂 Zatem korzystaj i stosuj 🙂
Czy konstrukcja only() nadal jest wspierana?
Tak, cały czas możesz używać
test.only(), aby uruchamiać tylko jeden test 🙂https://playwright.dev/docs/test-annotations#focus-a-test
Hej mam pytanie o tego checkboxa, przyznam, że zamiast używać codegen do nagrania testu to spróbowałem po prostu z devtoolsami i szybko poszło, ale kod na końcu się różnił od tego co podaliście, ale wszystko działa. Sięgnąłem też do dokumentacji playwrighta i zauważyłem, że są tam sugerowane lokatory elementów i raczej odradza się używanie CSS i Xpath jeśli nie jest to konieczne, ze względu na trudność w utrzymaniu testów (cssy i xpathy łatwo ulegają zmianom). Jednak ja złapałem element #widget_1_topup_agreement zamiast uniform-widget_1_topup_agreement span, kierowałem się przy tym myśleniem, że mimo wszystko to Id, odwołuje się bezpośrednio do checkboxa (który jest inputem), a uniform-widget_1_topup_agreement span odwołuje się do jakiegoś elementu opakowującego/trzymającego w sobie ten input. Założyłem, że prędzej ten div może ulec zmianie niż sam input, który może co najwyżej zostać przeniesiony. Czy tutaj istnieje jakieś jednoznaczne stwierdzenie, które z tych rozwiązań jest lepsze czy “to zależy”?
U mnie:
await page.locator('#widget_1_topup_agreement').click();W podanym rozwiązaniu przez jaktestowac.pl:
await page.locator('#uniform-widget_1_topup_agreement span').click();Druga rzecz, kiedy po zrobieniu zadania, spróbowałem zrobić je jeszcze raz z codegen zauważyłem, że na sam koniec kiedy zamykam ekran informujący o tym, że doładowanie zakończyło się powodzeniem, playwright inspektor czasem zapisuje kliknięcie buttona OK jako getByRole a czasem jako page.getByTestId, czy oba te rozwiązania są równie dobre?
Ogólnie Twoje rozwiązanie również wygląda poprawnie 🙂
Również myślę, że Twoje przemyślenia nt zmienności elementów są poprawne – czyli, że lepiej odnieść się do konkretnego elementu przez jego ID, niż po typie elementu 🙂 I też sugerowałbym odnosić się do ID, które jest najbliżej interesującego nas elementu.
To zależy odnosiłoby się do projektów, w których elementy nie mają ID i musimy wybierać “mniejsze zło” 😉
Działanie codegen może się zmieniać między wersjami Playwrighta… i jak widać również w zależności od uruchomienia😅 Obecnie nie wiem dokładnie na jakiej podstawie są generowane i wybierane lokatory w codegen, jednak obie opcje mogą być poprawne 🙂
Tutaj zachęcałbym również do przyjrzenia się czy dałoby radę zrobić to lepiej/inaczej – np. po ID. Bo codegen nie zawsze działa optymalnie 🙂
Super lekcja, dzięki! 🙂 Jednak zauważyłem coś dziwnego: po wybraniu nr tel 504 xxx xxx zamiast pola input mamy listę rozwijaną z kwotami do wyboru.
Mimo to, jeśli wpiszę w teście
await page.locator('#widget_1_topup_amount').selectOption('100');i puszczę test, to Playwright wskazuje mi w tym miejscu błąd:
No i jeszcze dziwniejsze jest to, że test dla numeru 504 xxx xxx przechodzi, jeśli zamiast metody selectOption dam metodę fill:
await page.locator('#widget_1_topup_amount').fill('50');Trzeba będzie sprawdzić, co się dzieje w API 🙂
Hej 😀
Dzięki wielkie! 🙇♂️
selectOptionnie zadziała napage.locator('#widget_1_topup_amount'), bo jest to obiekt typu input (a nie select/drop down) 😉 Select byłby odpowiedni dla elementupage.locator('#widget_1_topup_receiver')🙂Krzysiek, cały wic polega na tym, że dla numeru 504 xxx xxx (tylko dla tego numeru) zamiast pola input mamy listę rozwijaną z kwotami doładowania do wyboru. Przynajmniej u mnie tak to działa. Zaręczam, że nie gmerałem w kodzie w devtoolsach 🙂 Tym dziwniejsze jest to, że ten numer nie przechodzi testu, jeśli wybierzemu lokator selectOption. Prawda testu i prawda ekranu. 😀
504 xxx xxx
wybierz telefon do doładowania
500 xxx xxx
502 xxx xxx
503 xxx xxx
504 xxx xxx
Faktycznie 😀 Sprawdziłem dla innych numerów – i akurat nie sprawdziłem
504 xxx xxx.W sumie w realnym projekcie -> idealne na zgłoszenie buga 😀
W naszym przypadku – pozostaje nam pominąć ten przypadek 😉
Widać w tej apcje mamy więcej bugów niż zakładaliśmy i niż jesteśmy świadomi 😀
Oprócz “braku wiadomości” test szybkiego przelewu czasami nie przechodzi, gdy kwota w wiadomości jest podawana bez wartości dziesiętnych.
Expected string: “Przelew wykonany! Chuck Demobankowy – 150,00PLN – pizza”
Received string: “Przelew wykonany! Chuck Demobankowy – 150PLN – pizza”
Pewnie treść wiadomości powinna być doprecyzowana w specyfikacji, a sytuacja stworzona w celach dydaktycznych.
W pewnych przypadkach testy mogą kończyć się niepowodzeniem, gdzie powody mogą być różne – począwszy od połączenia internetowego, prędkości testu, czy z winy samej aplikacji. W tym przypadku możemy ten błąd zignorować, a w projekcie warto przedyskutować go z innymi QA lub developerami.
Również takie niestabilne testy w testowej aplikacji pozwalają też pobawić się z ustawieniami w Playwrighcie – np w pliku config jest parametr
retries, który pozwala ponawiać testy, które zakończyły się niepowodzeniem.Domyślnie wartość ustawiona jest na 0, czyli każdy test ma tylko jedną szanse, ale przy niestabilnych testach możemy poeksperymentować z wartością
retries.Przy czym – warto tu się zastanowić czy takie ponawianie testów jest dobrym rozwiązaniem w naszym kontekście (czyli projekcie) i jaki jest faktyczny powód tej niestabilności testów. Bo ustawiając
retriesbez analizy problemy możemy bardzo łatwo przeoczyć faktyczny błąd w aplikacji 😉Więcej o tym powiemy w przyszłych materiałach – również o wadach, zaleta oraz zagrożeniach 🙂
mnie jedynie drażni ten “Brak wiadomości”, od czapy się lubi pokazać 😛
Idealnie! 😀
A tak na serio – była w tym głębsza myśl 😉
Aplikacja została specjalnie tak zaprojektowana, aby oddać różne (czasem specyficzne) wymagania klienta oraz kilka innych niedogodności… Dzięki temu symulujemy prosty projekt, w którym musimy się dostosować, ale momentami nie jesteśmy pewni czy znaleziona rzeczy to bug czy feature 😉
Jak zwykle wszystko świetnie wytłumaczone od podstaw!
Dzięki wielki! 🙇♂️