Powrót do: Praktyczne wprowadzenie do testów automatycznych z Playwright
DRY i hook beforeEach
Prezentacja
Dodatkowe materiały
Bazujemy na kodzie lekcji L05_rozwiazanie_aaa
Kod wynikowy tej lekcji znajduje się tu: L06_dry_hooks
Pamiętaj, aby po danej porcji pracy: uruchamiać test, commitować poprawnie działający kod 😉
Czym jest DRY
DRY (czyli Don’t Repeat Yourself) jest zasadą w programowaniu, która mówi, żeby nie powtarzać tego samego kodu w różnych częściach programu.
Zamiast powtarzać kod, można wydzielić powtarzające się części i umieścić je w jednym miejscu (pliku, funkcji, metodzie, etc.). Taka praktyka często ułatwia zrozumienie kodu, ułatwia wprowadzanie zmian oraz redukuje ryzyko popełnienia błędów.
Dzięki temu możemy zaoszczędzić czas i wysiłek, ponieważ nie musimy pisać tego samego kodu wielokrotnie 😉
Hook beforeEach
beforeEach()
to hook, czyli funkcja wywoływana przed każdym testem. Występuje ona w wielu bibliotekach do testów w języku JavaScript/TypeScript.
beforeEach()
służy do wykonania pewnych czynności przed testami, np. przygotowanie danych, logowanie, przygotowanie środowiska, etc. Dzięki temu możemy uniknąć powielania kodu w każdej funkcji testowej.
Wyciągnięcie url do hook beforeEach
beforeEach()
w pliku login.spec.ts wyciągamy powtarzający się kod wspólny dla wszystkich testów:
test.beforeEach(async ({ page }) => { const url = 'https://demo-bank.vercel.app/'; await page.goto(url); });
Struktura pliku login.spec.ts będzie wyglądać tak:
test.describe('User login to Demobank', () => { test.beforeEach(async ({ page }) => { const url = 'https://demo-bank.vercel.app/'; await page.goto(url); }); test('successful login with correct credentials', async ({ page }) => { // Arrange const userId = 'testerLO'; const userPassword = '10987654';
Pamiętamy o usunięciu kodu, który został wyciągnięty do beforeEach()
ze wszystkich testów w ramach całego describe()
Uruchomienie testów dla danego pliku
Konsola bash w VSC na Windows:
Kliknij strzałkę w dół w prawym górnym menu konsoli i wybierz Git Bash.
Wprowadź polecenie npx playwright test
Zacznij wpisywać nazwy folderów lub plików, a następnie uzupełniać podpowiedzi klawiszem TAB.
Dla login.spec.ts będzie to polecenie:
npx playwright test tests/login.spec.ts
Implementacja DRY w w testach pulpitu
Możemy wyciągnąć kod przejścia do strony i zalogowania się:
const url = 'https://demo-bank.vercel.app/'; const userId = 'testerLO'; const userPassword = '10987654';
await page.goto(url); await page.getByTestId('login-input').fill(userId); await page.getByTestId('password-input').fill(userPassword); await page.getByTestId('login-button').click();
z testów do beforeEach()
:
test.beforeEach(async ({ page }) => { const userId = 'testerLO'; const userPassword = '10987654'; const url = 'https://demo-bank.vercel.app/'; await page.goto(url); await page.getByTestId('login-input').fill(userId); await page.getByTestId('password-input').fill(userPassword); await page.getByTestId('login-button').click(); });
Przykład: zmienna na poziomie describe
jest dostępna we wszystkich testach a na poziomie beforeEach()
tylko dla tego bloku. Jeśli zmienna nie jest używana w testach a tylko w beforeEach
to powinna tylko tam się znaleźć.
Zawartość pliku pulpit.spec.ts:
import { test, expect } from '@playwright/test'; test.describe('Pulpit tests', () => { test.beforeEach(async ({ page }) => { const userId = 'testerLO'; const userPassword = '10987654'; const url = 'https://demo-bank.vercel.app/'; await page.goto(url); await page.getByTestId('login-input').fill(userId); await page.getByTestId('password-input').fill(userPassword); await page.getByTestId('login-button').click(); }); test('quick payment with correct data', async ({ page }) => { // Arrange const receiverId = '2'; const transferAmount = '150'; const transferTitle = 'pizza'; const expectedTransferReceiver = 'Chuck Demobankowy'; // Act await page.locator('#widget_1_transfer_receiver').selectOption(receiverId); await page.locator('#widget_1_transfer_amount').fill(transferAmount); await page.locator('#widget_1_transfer_title').fill(transferTitle); await page.getByRole('button', { name: 'wykonaj' }).click(); await page.getByTestId('close-button').click(); // Assert await expect(page.locator('#show_messages')).toHaveText( `Przelew wykonany! ${expectedTransferReceiver} - ${transferAmount},00PLN - ${transferTitle}` ); }); test('successful mobile top-up', async ({ page }) => { // Arrange const topUpReceiver = '500 xxx xxx'; const topUpAmount = '50'; const expectedMessage = `Doładowanie wykonane! ${topUpAmount},00PLN na numer ${topUpReceiver}`; // Act await page.locator('#widget_1_topup_receiver').selectOption(topUpReceiver); await page.locator('#widget_1_topup_amount').fill(topUpAmount); 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(); // Assert await expect(page.locator('#show_messages')).toHaveText(expectedMessage); }); });
Wyciągnięcie zmiennej url do konfiguracji
Z beforeEach()
w pliku login.spec.ts wyciągamy wartość zmiennej url
:
test.beforeEach(async ({ page }) => { const url = 'https://demo-bank.vercel.app/'; await page.goto(url) });
I umieszczamy ją w pliku playwright.config.ts pod zmienną baseURL
(wystarczy jej odkomentowanie):
use: { /* Maximum time each action such as `click()` can take. Defaults to 0 (no limit). */ actionTimeout: 0, /* Base URL to use in actions like `await page.goto('/')`. */ baseURL: 'https://demo-bank.vercel.app', /* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */ trace: 'retain-on-failure', video: 'retain-on-failure', },
Po takiej zmianie, w kodzie naszych testów logowania możemy zmienić:
const url = 'https://demo-bank.vercel.app/'; await page.goto(url);
na:
await page.goto('/');
Również w testach pulpitu aplikujemy tę zmianę.
Zmienna kod dla każdego testu, gdy jest on wydzielony do beforeEach()
przebiega dużo szybciej.
Posiadanie globalnie ustawionego adresu url
pozwala nam szybko zmienić go w obrębie wszystkich testów.
DRY – czy zawsze i wszędzie?
Nie zawsze warto stosować DRY. Dla przykładu w testach w pliku login.spec.ts mamy powtórzony kod userId
ale nie we wszystkich testach.
Wyciąganie nadmiarowych danych powoduje ich dostępność w obrębie wszystkich testów, nawet takich, które danego rozwiązania nie potrzebują.
Dlatego warto się zastanowić jak zastosowanie DRY wpłynie na nasze bloki i czy nie idziemy w drugą stronę zbyt nadmiarowo wyciągając i współdzieląc dane.
Cześć,
mam jakiś problem z dostępnością constów użytych w beforeEach:
const amount = ‘150’;
Test nie może ich znaleźć, trzeba je jakoś specjalnie przekazać tak jak page?
Cannot find name ‘amount’ gdy próbuję wywołać stała w teście 🙁
Hej!
Jak wygląda Twój cały kod
Zarówno z deklaracją tych zmiennych jak i miejscami gdzie chcesz się do nich odwołać 🙂
Chodzi o wykorzystanie amount (daem tą samą kwote w 2 testach dlatego tak)
Nie znajduje go wtedy w expecedSuccessTransfer i przy wypełnianiu pola
Można to naprawić przenosząc deklarację bespośrednio do test.describe, pytanie czy to dobra praktyka i jakie dane powinniśmy tam umieścić? (pomijając kwestię nadmiarowości danych w pozostałych testach)
Hej Jacek!
Wystarczy daną zmienną ustawić pod describe:
i można z niej korzystać w obrębie całego describe.
Jeśli chciałbyś modyfikowalny obiekt ustawiany np w beforeEach i dostępny ze zmodyfikowaną wartością w testach to wyglądało by to tak:
To standardowa praktyka.
Warto tu zwrócić uwagę, że te zmienne są dostępne w całym obszarze describe.
Powinny być one wykorzystywane przez wszystkie testy gdyż nadmiarowe stałe/zmienne niepotrzebnie udostępniane (wyciekające) stanowią narzut przy czytaniu i zrozumieniu testów.
Jeśli potrzebujesz zmiennych tylko do pewnej grupy testów należy wydzielić zagnieżdżone describe wyłącznie ze zmiennymi dotyczącymi danych testów.
Oczywiście jest to rekomendacja i czasem są od niej odstępstwa.
Mam nadzieję, że to w miarę rozjaśnia sytuację.
Pozdro!
Hej,
I jeszcze jedo pytanie – czy warto w hookach robić soft asercje dla danych działań czy raczej całkowicie unikać soft asercji w hookach ??
Powiedziałbym, zę to zależy od przypadku.
Gdy w hooku umieścimy tylko soft assert, który zakończy się niepowodzeniem, to test zostanie uruchomiony – i nawet jesli test zakończy sie powodzeniem, to przez hook zostanie oznaczony jako failed 🙂
Dlatego trzebaby się zastanowić co chcemy osiągnąc i co da nam tego typu sprawdzenie 🙂 I najlepiej przygotować jakiś POC, czyli prosty kawałek kodu, na którym zobaczymy wynik 😉
Hej hej,
Dzięki za odpowiedź super czyli nie jest to całkowicie bad practice ale lepiej uważać na asercjowanie innych niż główne elementy – dzięki za odpowiedź,
Pozdrawiam i spokojności życzę,
Jakub K
Hej hej,
Mam pytanie – jest projekt na kilku środowiskach – z tego co kojarzę użycie globalnie url’a do projektu jest spoko jeśli podstawa adresu się nie zmienia a co z puszczaniem testów na różnych środowiskach, gdzie adres url na dane środo jest inny na każdym ze środowisk – czy wtedy używać base url tylko dodawać ifa w hook’u, czy duplikować pliki z kodem pod każde środowisko yżywając innego baseURL w zależności od np wybranej zmiennej w teście – przygotowane w playwright.config.json np baseURL, baseURL2 itd czy jest jakieś inne prostrze i nie wymagające duplikowania testów rozwiązanie które oferuje playwright??
Pozdrawiam i dzięki z góry za odpowiedź,
Jakub K
Dobre pytanie! 🙂
Przy testach na różnych środowiskach można użyć kilka różnych mechanizmów (projects w Playwright i biblioteka dotenv). Temat ten opisujemy na naszym blogu: https://playwright.info/playwright-testy-na-roznych-srodowiskach
Również dedykowany materiał na ten temat właśnie przygotowuje w ramach: https://jaktestowac.pl/playwright/ 🙂
Hej super,
Dzięki za odpowiedź i za to że ten temat również macie zamiar umieścić.
Pozdrawiam i spokojności życzę,
Jakub K
Hej, mam pewien problem z uruchomieniem testów z poziomu konsoli:
‘npx playwright test tests/login.spec.ts’
Przy każdej próbie otrzymuję błąd:
“Error: page.goto: Protocol error (Page.navigate): Cannot navigate to invalid URL
Call log:
– navigating to “/”, waiting until “load””
Ustawiłem zmienną baseURL w pliku playwright.config.ts podmieniając w beforeEach zmienną url w metodzie goto na:
await page.goto('/');
Powyższy problem nie występuje gdy uruchamiam testy z poziomu IDE przy użyciu wtyczki.
Hmm wygląda na to, że już działa.
Nie wiem na ile to pomogło ale uruchomiłem testy poprzez skrypt:
npm run test i wszystko przeszło.
Od tamtej pory uruchamiam już testy: ‘npx playwright test tests/login.spec.ts’ i działa.
Trochę martwi ta stabilność…
Hej,
Może miałeś jakiś niezapisany plik?
Również pytanie jak dokładnie wyglądał Twój kod, czy nie było tam dodatkowego “/” w adresie?
Przy automatyzacji i konfiguracji trzeba kilka rzeczy przypilnować – jednak najważniejsze, że obecnie działa 🙂
Daj prosze znać jakbyś miał jeszcze jakies problemy 🙂
Hej
Mam pytanie jak zrobi, żeby na moim trace viewer wydoczne były te znaczki “>” które dają opcje rozwinięcia danego kroku?
Moment o którym mówię, występuje w tej lekcji w 22:41, gdy Przemek rozwija sobie hook beforeEach i ma podgląd na kod, ja nie mam takiej możliwości, i znak > mam tylko przy beforeEach ale jak go rozwijam to nie widze kodu 🙁
P.S. Przydałaby się możliwość wklejania niewielkich screenów w komentarzach 🙂
Fajne pytanie – obecnie już nie ma możliwości bezpośrednio w raporcie podglądnąć kodu z danych akcji.
Takie informacje są zawarte w podglądzie Trace Viewer (w obecnym kodzie pojawia się tylko w przypadku faila testu) lub z pomocą UI mode (otwieranego
npx playwright test --ui
)Tutaj przyznam, że twórcy wprowadzają wiele zmian, nie zawsze na plus🫡
Co do obrazków w komentarzach to niestety nasza obecna platforma nie ma wsparcia ale śmiało przyjmie linki z dowolnego serwisu gdzie takie obrazki można bezkarnie wklejać.
Hej, dzięki za odpowiedź. Tak się właśnie domyślałem, że przypuszczalnie brak podglądu to wynik zmian w Playwright, ale wolałem się upewnić 🙂
fajny pomysł, na przyszłość, będę uploadował i podrzucał link 🙂
Cześć, czy możemy w jakiś prosty sposób wyłączyć użycie beforeEach dla jednego testu, który jest w zestawie. Jednocześnie chcemy, żeby ten test był zawarty w danym zestawie testów. W skrócie chodzi o to, że dla jednego testu chcielibyśmy użyć innych danych do logowania 🙂
Hej,
Są przynajmniej 2 sposoby 🙂
Dużo zależy co dokładnie chcesz osiągnąć oraz jak wyglądają Twoje testy.
Sposób pierwszy: odpowiednia hierarchia describe oraz test:
W rezultacie otrzymamy całość w takiej kolejności:
Sposób drugi: warunki w beforeEach. Poniżej pomijamy beforeEach przed testem o nazwie “test 2”:
A na konsoli otrzymamy:
Super, bardzo dziękuję za odpowiedź i gotowe rozwiązania. Ten drugi sposób już przetestowałam w praktyce i zadziałało od razu 🙂 nie wiem jeszcze na ile tego typu rozwiązania sprawdzają się w komercyjnych, dużych projekcie (w firmie jesteśmy na dość wczesnym etapie wdrażania testów automatycznych), ale na teraz bardzo się przydało. Jeszcze raz dzięki!
Osobiście sugerowałbym pierwsze – pozwala dodatkowo na grupowanie testów w zagnieżdżone
– ma jeden poważny minus – opiera się na nazwie testów. Jednak można je wykorzystać, gdy w testach wprowadzimy tagi ( https://playwright.dev/docs/test-annotations#tag-tests ), czyli w nazwie testu dodamy np
"@high"
czy"@low"
(np. w kontekście priorytetów). Wtedy w takim Before możemy zastosować daną akcję jedynie w kontekście testów z danym tagiem:if (testInfo.title.includes("@high")) {
Jedno i drugie rozwiązanie ma swoje wady i zalety – wszystko zależy od kontekstu i potrzeb 😉
błąd odnaleziony 🙂 braki/za dużo ‘});’ w całym kodzie -składnia
testy już działają
Doskonale! 😉
1) [chromium] › desktop.spec.ts:36:5 › successful mobile top-up ──────────────────────────────────
Test timeout of 50000ms exceeded.
Error: locator.selectOption: Target closed
=========================== logs ===========================
waiting for locator(‘#uniform-widget_1_topup_receiver’)
prośba o wyjaśnienie 🙂 zminiłam timeout na 5000 ale pomimo tego jakoś długo szuka
Hej,
Jak wygląda Twój cały kod projektu wraz z konfiguracją? 🙂
Jak prezentuje się przebieg tego testu na video i screenshotach (powstałych po teście)?
Tu wynik z test viewera:
video jest białe – przez 29 s trwania
test ‘successful mobile top-up’ jest poza describe wiec beforeEach czyli nawigacja i logowanie sie nie wykonuje. Zastanawiajace jest jednak czemu sie nie sypie na linijce 43 tylko na linijce 44
Fakt, zastanawiające, chociaż u mnie na wersji Playwright 1.38.1 błąd leci właśnie na linii 43:
Więc obstawiałbym, że może coś w tamtej wersji Playwrighta mogło być zepsute 🤔