Powrót do: Praktyczne wprowadzenie do testów automatycznych z Playwright
Rozbudowa testów i lokatory
- brakiem wczytania wiadomości potwierdzającej przelew
- brakiem wypełnienia pola z kwotą i tytułem przelewu
Wynikały one z próby wykonania akcji na jeszcze nie do końca załadowanej stronie.
Powodem tego mogą być:
- problemy z hostingiem
- problemy z połączeniem internetowym
- nie optymalne rozwiązania zastosowane na testowanej stronie
- zbyt dużą szybkością akcji, jakie wykonuje Playwright
Rozwiązaniem jest dodanie inteligentnego czekania w postaci:
await page.waitForLoadState("domcontentloaded")
Konstrukcja waitForLoadState
czeka, aż pojawi się w przeglądarce zdarzenie domcontentloaded, które oznacza, że zawartość strony została w pełni załadowana.
Na nagraniu jej nie używamy, jednak jeśli Twoje testy kończyłyby się niepowodzeniem, to rzuć okiem pod nagranie do sekcji Stabilizacja Testów. Tam umieściliśmy dodatkowe instrukcje, polecenia i kod 😉
Prezentacja
Dodatkowe materiały
Link do testowanej stronyUzupełnienie wiedzy o HTML i lokatorach
Jeśli nie miałeś styczności z kodem stron internetowych HTML to skorzystaj z lekcji Kod strony HTML oraz Elementy strony w przeglądarce.
Drobne poprawki kodu
Poprawiamy literówkę w pliku login.spec.ts. Zamiast:
unsuccessful login with to short username
powinno być:
unsuccessful login with too short username
Synchronizujemy się z zawartością pliku README.md z repozytorium poprzedniej Lekcji 02:
https://github.com/jaktestowac/playwright_automatyzacja_wprowadzenie/tree/main/S01_wprowadzenie/L02_testy_logowania
Wielokrotne użycie only
Zastosowanie ograniczenia wykonywania testów, w postaci test.only()
w kilku testach spowoduje wykonanie wszystkich testów oznaczonych funkcją only
.
Dokładnie sprawdzaj wyniki w konsoli przy uruchamianiu testów, czy przypadkiem nie pozostawiłeś gdzieś testu z funkcją only
.
Kasujemy nadmiarowe akcje
Pozbywamy się akcji w testach, które nie wpływają na wykonanie testów.
W naszych testach takie dwa kroki:
await page.getByTestId('login-input').click(); await page.getByTestId('login-input').fill('testerLO');
Mogą być zredukowane do:
await page.getByTestId('login-input').fill('testerLO');
Metoda fill()
realizuje zarówno focus na elemencie jak i wypełnienie go danymi.
Czyścimy z testu wszystkie nadmiarowe kroki, dzięki czemu tak teraz wyglądają nasze testy:
test('successful login with correct credentials', async ({ page }) => { await page.goto('https://demo-bank.vercel.app/'); await page.getByTestId('login-input').fill('testerLO'); await page.getByTestId('password-input').fill('10987654'); await page.getByTestId('login-button').click(); await expect(page.getByTestId('user-name')).toHaveText('Jan Demobankowy'); }); test('unsuccessful login with too short username', async ({ page }) => { await page.goto('https://demo-bank.vercel.app/'); await page.getByTestId('login-input').fill('tester'); await page.getByTestId('password-input').click(); await expect(page.getByTestId('error-login-id')).toHaveText('identyfikator ma min. 8 znaków'); }); test('unsuccessful login with too short password', async ({ page }) => { await page.goto('https://demo-bank.vercel.app/'); await page.getByTestId('login-input').fill('testerLO'); await page.getByTestId('password-input').fill('1234'); await page.getByTestId('password-input').blur(); await expect(page.getByTestId('error-login-password')).toHaveText('hasło ma min. 8 znaków'); });
Testy pulpitu
Tworzymy nowy plik z testami pulpit.spec.ts.
Dodajemy podstawowe struktury do pliku:
import { test, expect } from '@playwright/test'; test.describe('Pulpit tests', () => { });
Nagrywamy nowy scenariusz według kroków:
- Zalogowanie się
- Skorzystanie z opcji Szybki przelew
- do Chuck Demobankowy
- kwota 150
- tytułem Przelw środków
- Akceptacja okna potwierdzenia
- Weryfikacja wiadomości o przelewie
Wklejamy nagrany test do przygotowanego pliku, oraz usuwamy nadmiarowe akcje (czyli np. nadmiarowe kliknięcia w element).
Lokatory
Lokator: jest to sposób w jaki znajdujemy dany element ze strony internetowej.
Selektor: jest to adres danego elementu
Lokator getByTestId()
Zastosowanie:
await page.getByTestId('login-input').fill('testerLO');
Lokator getByTestId()
Zastosowanie:
await page.getByTestId('login-input').fill('testerLO');
Lokator getByTestId()
działa w oparciu o wartość atrybutu zlokalizowanego w kodzie strony data-testid
. Przykład elementu z data-testid
do wpisywania loginu:
Taki test id musi zostać dodany przez twórców aplikacji do kodu danej strony. Najczęściej ten atrybut jest usuwany przed wprowadzeniem aplikacji na produkcję.
Usunięcie tego atrybutu to jedynie kosmetyczny zabieg w większości przypadków. Jedynie w przypadku bardzo rozbudowanych stron taki atrybut może mieć cząstkowy wpływ na ich wydajność.
Ostatecznie dodawanie i usuwanie takiego atrybutu to konwencja techniczna zależna od realiów danego projektu.
Więcej o usuwaniu takiego atrybutu z punkty widzenia deweloperskiego poczytasz tutaj:
https://dpericich.medium.com/removing-data-test-attributes-from-react-production-dom-5ea4ea018acc
Dodanie takiego atrybutu do kodu strony zazwyczaj jest poprzedzone zespołowym uzgodnieniem konwencji: jak będzie się on nazywał, jak będą wyglądać jego wartości.
W Playwright domyślnie rozpoznawany jest format nazwy data-testId
ale można go dowolnie nazwać w kodzie aplikacji i ustawić w playwright.config.ts (klucz testIdAttribute
):
// Set custom test id attribute from @playwright/test config: import { defineConfig } from '@playwright/test'; export default defineConfig({ use: { testIdAttribute: 'data-pw' }, });
Więcej o tym czym jest test id poczytasz we wpisach (język angielski):
Oficjalna dokumentacja: https://playwright.dev/docs/locators#locate-by-test-id
Dlaczego warto używać test id: https://medium.com/@automationTest/why-your-development-team-should-use-data-testid-attributes-a83f1ca27ebb
Implementacja automatycznego test id w React: https://www.educative.io/answers/what-is-the-data-testid-attribute-in-testing
Lokator getByRole()
Zastosowanie:
await page.getByRole('button', { name: 'wykonaj' }).click();
getByRole()
działa w oparciu o typy elementów i ich cechy.
Przykład elementu związanego z przyciskiem wykonaj:
Selektorem dla tego elementu działającym w oparciu o typ element button
i wartość jego tekstu będzie:
getByRole('button', { name: 'wykonaj' })
Lokator locator()
Zastosowanie:
await page.locator('#widget_1_transfer_receiver').selectOption('2');
locator()
wykorzystuje między innymi adresy zapisane w formacie CSS.
Przykład elementu z css
związanego z wybieraniem odbiorcy przelewu:
Selektorem dla tego elementu działającym w oparciu o wartość atrybutu id
będzie:
#widget_1_transfer_receiver
Asercja
Posiadając nagrany kod:
await page.getByRole('link', { name: 'Przelew wykonany! Chuck Demobankowy - 150,00PLN - pizza' }).click();
Można zidentyfikować element:
Przelew wykonany! Chuck Demobankowy - 34,00PLN - pizza
Używając atrybutu id elementu z tekstem, który został nagrany można zbudować taką asercję:
await expect(page.locator('#show_messages')).toHaveText('Przelew wykonany! Chuck Demobankowy - 150,00PLN - pizza');
Aby upewnić się, że nasz test działa poprawnie, a dokładnie jego asercja warto ją uszkodzić celowo, aby wywołać niepowodzenie testu.
Nazwa testu
quick payment with correct data
Stabilizacja testów
- brakiem wczytania wiadomości potwierdzającej przelew
- brakiem wypełnienia pola z kwotą i tytułem przelewu
Wynikały one z próby wykonania akcji na jeszcze nie do końca załadowanej stronie.
Powodem tego mogą być:
- problemy z hostingiem
- problemy z połączeniem internetowym
- nie optymalne rozwiązania zastosowane na testowanej stronie
- zbyt dużą szybkością akcji, jakie wykonuje Playwright
Rozwiązaniem jest dodanie inteligentnego czekania w postaci:
await page.waitForLoadState("domcontentloaded")
Konstrukcja waitForLoadState
czeka, aż pojawi się w przeglądarce zdarzenie domcontentloaded, które oznacza, że zawartość strony została w pełni załadowana.
Jak skorzystać z tej konstrukcji?
W naszym teście pulpitu problem ten może występować, po operacji logowania. Wtedy ładowana jest nowa strona pulpitu.
Dlatego po kliknięciu w przycisk login musimy dodać await page.waitForLoadState("domcontentloaded")
:
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(); // wait for page to fully load: await page.waitForLoadState("domcontentloaded") 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' ); }); });
Tego typu konstrukcja może zapewnić nas, że strona w następnym kroku (przy wyborze danych z elementy select) jest w pełni załadowana.
Cały kod
Aktualny kod z lekcji znajdziesz w dedykowanym repozytorium: https://github.com/jaktestowac/playwright_automatyzacja_wprowadzenie.
Hej,
Mam drobny problem z asercją w trakcie testu – próbowałem twardej i miękkiej – await w tej asercji nie jest potrzebny ale próbowałem i z awaitem i mam problem ponieważ mimo iż w consoli za pomocą lokatora i odpowiednich indexów tabeli oraz wartości obiektu innerText lub innerHTML nie mogę wyjąć odpowiedniej opcji do porównania
Mój test:
Wydzielony kawałek posiada asercję zostwiłem na miękkiej jednak błąd przy teście w raporcie html’a jest przy zanestowanym indexie w odpowiedzzi – czy macie jakiś pomysł jak to obejść
Czy macie również z tym kłopot??
Z góry dziękuję za odpowiedź.
Pozdrawiam,
Jakub K
Hej,
Dobre pytanie!
W dużym uproszczeniu –
$$
i mechanizm Playwright do znajdowania elementów działa odrobinę inaczej.Poniżej rozpisałem 4 sposoby na wykonanie tej asercji. 3 dobre (zaleznie od kontekstu) i sposób 4 – nie do końca zalecany. Pomija on mechanizm auto-wait, który jest wbudowany w Playwright gdy asercję bazujemy na obiekcie lokatora.
Jeśli mamy kilka elementów i musimy sprawdzić jeden z nich, to musimy wykorzystać konstrukcję
element.nth(3)
, a jeśli chcemy z niego wybrać tekst – toawait options.nth(3).innerText()
(zawait
, bo to jest metoda asynchroniczna!)Rzuć okiem na poniższy kod – z różnymi opcjami, opisami i asercjami – na konsoli też możesz prześledzić jakie wyniki otrzymasz na poszczególnych etapach tego przykładu 🙂
Hej,
Pięknie dziękuję za Tipy:
Rezultaty:
Jak pierwszy raz puściłem z solucją nr 1 – strony nie miałem otwartej za żadnym razem – to otrzymałem błąd: ale z czasem na kolejnej asercji z prekroczeniej czasu:
ale jak puściłem solucję 2 i potem znów 1nkę to wsio zaczęło przechodzić bez problemu włącznie z pozostałymi solucjami – dziękuję bardzo za różne sposoby rozwiązania problemu super info i wyczerpujące.
Pozdrawiam i spokojności życzę,
Jakub K
Hej,
Ciesze się, że wszystko śmiga 🙂
Błąd związany z weryfikacją wiadomosci:
to znany błąd – czasem występuje w tej aplikacji. Na razie postanowiliśmy go zostawić bo nie występuje zbyt czesto, a kroki do jego reprodukcji nie są jasne.
Zatem jeśli test wysypał Ci się tylko na weryfikacji wiadomości, to znaczy, że poprzednie kroki działają poprawnie 🙂
Hej,
Okidoki to wszystko jasne zatem,
Dzięuję za analizę i odpowiedź
Poadrawiam i spokojności życze,
Jakub K
Hej, wspominacie krótko o dobrej praktyce usuwania test-dataid z aplikacji produkcyjnej. Macie może jakiś ciekawy materiał na ten temat, który możecie podlinkować?
Hej Arek!
Rozszerzyłem opis tutaj https://jaktestowac.pl/lesson/pw1s01l03/#Lokator_getByTestId() i podlinkowałem jeden artykuł:
https://dpericich.medium.com/removing-data-test-attributes-from-react-production-dom-5ea4ea018acc
Warto zauważyć, że sprzątanie po test id jest zależne od technologi użytej w projekcie i często używane są specjalne paczki, które czyszczą kod budowany na produkcję:
https://www.npmjs.com/package/babel-plugin-jsx-remove-data-test-id
Czyszczenie więc to bardziej zagadnienie deweloperskie, uzależnione od technologii i związane ze skryptami Ciągłej Integracji.
Hej,
Ja miałem taki problem, że test przechodził albo w wersji headed albo z opcją trace włączoną.
Po spędzonej godzinie i degugingu m.in. logów z API PW, okazało się, że w trybie headless event ‘domcontentloaded’ jest później (różnica kilkadziesiąt milisekund) niż w wersji headed, trace, etc. i powoduje to, że w momencie zamknięcia tego okna z potwierdzeniem zrobienia przelewu, wiadomość nie była wczytywana.
Pomogło dodanie `page.waitForLoadState(“domcontentloaded”)` zaraz po kliknięciu knefla logowania, a przed krokiem wyboru odbiorcy przelewu.
Hej,
Dzięki wielkie za zgłoszenie! Przeanalizowałem potencjalne powody, jednak nie udało mi się odkryć głównego. Główną trudnością była niewielka powtarzalność tego błędu. Mogła wystąpić kombinacja poniższych powodów:
problemy z hostingiem
problemy z połączeniem internetowym
nie optymalne rozwiązania zastosowane na testowanej stronie
zbyt dużą szybkością akcji, jakie wykonuje Playwright
Zaktualizowałem lekcje i dodałem dodatkowy kod z konstrukcją
page.waitForLoadState(“domcontentloaded”)
, który powinien zaopiekować ten problem 🙂Być może jest to kwestia TypeScript, którego właściwie nie znam, ale dlaczego po page.waitForLoadState(“domcontentloaded”) (linijka 12) nie ma średnika?
Słuszne spostrzeżenie!
Zarówno w JavaScript, jak i TypeScript, średniki są opcjonalne – mozna, ale nie trzeba ich stosować 😉
Oba podejścia mają swoje wady i zalety (oraz zwolenników).
W zależności od projektu mozna wypracować reguły, że np. zawsze je stosujemy, albo nigdy ich nie stosujemy. Najważniejsze jest ujednolicone podejście 🙂
I w tym przypadku – z racji, żę wszystkie linie maja średnik, a linia z
await page.waitForLoadState("domcontentloaded")
nie ma średnika – to jest drobny błąd do poprawy 😉Mam ten sam problem:
This page isn’t working
If the problem continues, contact the site owner.
HTTP ERROR 405
Dodatkowo w adresie url doklejany jest pytajnik, który widać w trybie –headed:
https://demo-bank.vercel.app/pulpit.html?
Zwiększenie timeoutów w configu u mnie nie pomaga
Pomogło ostatecznie dodanie w configu
Ale uważam, że to kiepskie rozwiązanie. Może ktoś ma jakieś inne sugestie? Dodawanie wszelkich dodatkowych waitów uważam za złą praktykę 🙁
Hej,
To prawda, że waity na sztywno to zła praktyka. W PW jest kilka sposobów na inteligentne czekanie (np. na zadany stan strony
await page.waitForLoadState('domcontentloaded');
albo na odpowiedz z API).Jednak HTTP ERROR 405 nie powinien mieć miejsca🤔
Również przed chwilą puszczałem testy i wszystko działało🤔
Podeślij mi proszę cały spakowany projekt na mail kontaktowy (znajdziesz go po prawej stronie w sekcji Znajdź nas lub na stronie /kontakt).
Dodaj też proszę dokładne logi jak wygląda błąd i co jest w konsoli.
Rzucę na niego okiem i spróbuje odnaleźć przyczynę tej 405 🙂
Czy jest możliwe ze Playwright próbje działać zbyt szybko? Próbowałem uruchamiać u siebie kod dokładnie taki jak na githubie i za każdym razem test failuje. Kiedy uruchamiam to w trybie headed to wygląda to tak, jakby wybór z dropdownu był za wolny i pozostałe dwa pola z kwotą i tutułem przelewu pozostają nie wypełnione.
Kiedy dodałem sztywny timeout po wyborze dropdownu
test przechodzi bez problemów
Hej,
Jaki dokładnie błąd dostajesz?
Rozumiem, że kod masz dokładnie taki sam jak w lekcji? 🤔
Daj znać to zbadam temat dokładniej 😉
Dostaję błąd dokłądnie taki jak dostał Przemek w 33 min nagrania.
Error: Timed out 5000ms waiting for expect(received).toHaveText(expected)
Expected string: “Przelew wykonany! Chuck Demobankowy – 120,00PLN – pizza”
Received string: “Brak wiadomości”
Call log:
– expect.toHaveText with timeout 5000ms
– waiting for locator(‘#show_messages’)
– locator resolved to Brak wiadomości
– unexpected value “Brak wiadomości”
– locator resolved to Brak wiadomości
– unexpected value “Brak wiadomości”
– locator resolved to Brak wiadomości
– unexpected value “Brak wiadomości”
– locator resolved to Brak wiadomości
– unexpected value “Brak wiadomości”
– locator resolved to Brak wiadomości
– unexpected value “Brak wiadomości”
– locator resolved to Brak wiadomości
– unexpected value “Brak wiadomości”
– locator resolved to Brak wiadomości
– unexpected value “Brak wiadomości”
– locator resolved to Brak wiadomości
– unexpected value “Brak wiadomości”
– locator resolved to Brak wiadomości
– unexpected value “Brak wiadomości”
Tylko, że u mnie ten błąd bez dodatkowego timeoutu występuje cały czas.
Próbowałem uruchomić test w trybie UI i tutaj nawet bez dodatkowego timeoutu błąd się nie pojawia i test przechodzi za każdym razem. Natomiast w trybie headed i headless bez timeoutu błąd jest za każdym razem.
Hm podejrzane 🤔
Puszczałem przed chwilą testy z najnowszymi paczkami (Playwright 1.38.1) na 2 różnych konfiguracjach (Win10 i Win11) i w różnych lokalizacjach – we wszystkich przypadkach problem nie występował 🤔 Dodatkowo puszczałem testy w pętli, ale również problem nie wystąpił:
Zamiast sztywnego czekania, możesz spróbować jeszcze dodać:
Jest to inteligentne czekanie, aż dokument się załaduje 🙂
Jeśli to nie pomoże – podeślij mi proszę cały spakowany projekt na maila (znajdziesz go po prawej stronie w sekcji Znajdź nas lub na stronie /kontakt) – to rzuce na niego okiem i spróbuje przeanalizować przyczynę tego błędu.
Hej,
można znaleźć w playwright.config.ts w sekcji expect – rzuć okiem do naszego repo: https://github.com/jaktestowac/playwright_automatyzacja_wprowadzenie/blob/main/S01_wprowadzenie/L02_testy_logowania/playwright.config.ts
Później podczas testów ten timeout jest brany pod uwagę przy asercjach expect jako czas czekania na elementy 🙂
Za kazdym razem wystepuje ten problem u mnie
Hej,
Wygląda na to, że dany napis z jakiegoś powodu jest ucięty na testowanej stronie.
Jak wygląda Twój kod testów?
Czy masz poprawnie skonstruowaną asercję lub wyszukiwanie elementu?
Daj mi proszę znać, jak to u Ciebie wygląda, a znajdziemy rozwiązanie 🙂
Błąd wskazuje, że dane, które są używane w teście (selektory i wartości) się nie zgadzają z oczekiwanymi wynikami.
Sam test wygląda poprawnie i gdy puściłem go u siebie, to zakończył się powodzeniem. Aczkolwiek nie był to kod, z którego wysłałeś poprzedni błąd, który dotyczył przelewu o tytule
pizza
– bez aktualnie używanego przez Ciebie kodu, ciężko będzie wskazać powód tego błędu 🙂Również jak wygląda cała informacja w konsoli?
Powyżej wiadomości, którą wysłałeś w pierwszej wiadomości, powinny być też takie wpisy:
Error: expect(received).toHaveText(expected)
Expected string: “Błędny oczekiwany napis”
Received string: “Przelew wykonany! Chuck Demobankowy – 100,25PLN – Test pizza”
Jak wygląda testowana strona w trakcie testu? Tutaj możesz użyć polecenia
npx playwright test --headed
Zrobilem maly refaktor swojego kodu i test teraz przechodzi pozytywnie, czy moge cos w nim usprawnic?
Doskonale, że już wszystko działa 😉 Czy udało Ci sie zlokalizować i okreslić wcześniejszy problem?
Jesli chodzi o dalsze usprawnienia – to pokażemy je w kolejnych lekcjach, które udostępnimy w najbliższych dniach – tam pokażemy zmniejszenie duplikacji kodu, wydzielenie wartości do zmiennych (które tu wykonałeś) oraz praktykę AAA czyli Arrange Act Assert. W dalszych lekcjach pokażemy jak zastosować wzorzec POM w tym projekcie 🙂
Spotykam kolejny problem, gdy chce wyswietlic kontent z selektora #show_messages, dostaje error z strony
jezeli odkomentuje kod I usune te dwie linie
to test przechodzi pozytywnie co tu moze byc nie take czy blad po waszej stronie?
Sprawdziłem po naszej stronie – Twój test działa poprawnie (musiałem tylko zakomentować logToApp):
Jaki wynik otrzymujesz, gdy wykonasz go manualnie?
Jak wygląda wynik gdy uruchamiasz testy z opcją –headed?
Hej tak jak powyżej przesłałem jak użyje testy z headed ,
otrzymuje kod z przeglądarki
This page isn’t workingIf the problem continues, contact the site owner.
HTTP ERROR 405
Jeśli możesz spakuj pliki z kodem (bez folderu node_modules) i podeślij do nas na maila kontakt (małpa) jaktestowac.pl Widząc cały kod i ustawienia będziemy mogli pomóc. Pozdro
Miałam te same błędy. Musiałam zwiększyć czas w playwright.config.ts . Dokładnie w tym miejscu:
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 6000
Wcześniej miałam na 5000 i testy nie przechodziły, być może internet nie był wystarczająco szybki.
Może się tak zdarzyć, gdy aplikacja działa odrobinę wolniej w danym momencie.
Dodaliśmy stosowne informacje w pierwszych lekcjach 🙂
gdzie w playwright.config.ts można znaleźć? trzeba wejść gdzieś do powiązanych plików?
* Maximum time expect() should wait for the condition to be met.
* For example in `await expect(locator).toHaveText();`
*/
timeout: 500