Rozwiązanie: Kolejny test pulpitu

TIP: Ta lekcja jest częścią rozwijanego Programu Testy Automatyczne z Playwright 🎭

Dodatkowe materiały

Link do testowanej strony
TIP: Cały kod z tej i innych lekcji znajdziesz w naszym repozytorium.

Twoim 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')
      });
});

28 komentarzy

  1. 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:  5000ms
    

    POMOCY!

    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',
        );
      });
    });
    
    Avatar Patrycja Spychalska
    1. 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!

      Przemek Przemek
  2. I drugie pytanie, z czystej ciekawości: czy jest jakiś konkretny powód, dla którego zostawiłeś w asercji page.locator zamiast page.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ść

    Avatar Marcin Nowak
    1. 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

      Przemek Przemek
  3. 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ć 🙂

    Avatar Marcin Nowak
    1. 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 😀

      Przemek Przemek
  4. 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:44
    

    Dla pewności pobrałam Wasz kod z repozytorium, niestety błąd dalej się pojawia.

    Avatar Justyna Kadela
    1. 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 🙂

      Przemek Przemek
  5. 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. 🙂

    Avatar Ewa
  6. 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?

    Avatar Franciszek Klocek
    1. 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 🙂

      Krzysiek Kijas Krzysiek Kijas
  7. 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:

    Error: locator.selectOption: Error: Element is not a  element
    

    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 🙂

    Avatar Slawomir Nowodworski
    1. Hej 😀
      Dzięki wielkie! 🙇‍♂️
      selectOption nie zadziała na page.locator('#widget_1_topup_amount'), bo jest to obiekt typu input (a nie select/drop down) 😉 Select byłby odpowiedni dla elementu page.locator('#widget_1_topup_receiver') 🙂

      Krzysiek Kijas Krzysiek Kijas
      1. 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

        Avatar Slawomir Nowodworski
        1. 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 😀

          Krzysiek Kijas Krzysiek Kijas
  8. 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.

    Avatar Adam
    1. 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 retries bez 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 🙂

      Krzysiek Kijas Krzysiek Kijas
    1. 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 😉

      Krzysiek Kijas Krzysiek Kijas

Dodaj komentarz

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *