Powrót do: Praktyczne wprowadzenie do testów automatycznych z Playwright
POM i komponenty, czyli wspólne elementy
TIP: Ta lekcja jest częścią rozwijanego Programu Testy Automatyczne z Playwright 🎭
Prezentacja
Dodatkowe materiały
Bazujemy na kodzie lekcji L06_pom_refactor
Kod wynikowy tej lekcji znajduje się tu: L07_pom_component
Pamiętaj, aby po danej porcji pracy: uruchamiać test, commitować poprawnie działający kod 😉
TIP: Zauważ, że do wspólnego komponentu możemy też przenieść element wyświetlający nazwę użytkownika, która również jest częścią panelu bocznego 😉
Zawartość side-menu.component.ts:
export class SideMenuComponent { constructor(private page) { } paymentLink = this.page.getByRole('link', { name: 'płatności' }) }
Zawartość pulpit.page.ts oraz wykorzystanie SideMenuComponent
:
import { Page } from '@playwright/test'; import { SideMenuComponent } from '../component/side-menu.component'; export class PulpitPage { constructor(private page: Page) {} sideMenuComponent = new SideMenuComponent(this.page) transferReceiverInput = this.page.locator('#widget_1_transfer_receiver'); transferAmountInput = this.page.locator('#widget_1_transfer_amount'); transferTitleInput = this.page.locator('#widget_1_transfer_title'); transferButton = this.page.getByRole('button', { name: 'wykonaj' }); actionCloseButton = this.page.getByTestId('close-button'); messageText = this.page.locator('#show_messages'); topUpReceiverInput = this.page.locator('#widget_1_topup_receiver'); topUpAmountInput = this.page.locator('#widget_1_topup_amount'); topUpAgreementCheckbox = this.page.locator( '#uniform-widget_1_topup_agreement span' ); topUpExecuteButton = this.page.getByRole('button', { name: 'doładuj telefon', }); moneyValueText = this.page.locator('#money_value'); userNameText = this.page.getByTestId('user-name') }
Zawartość payment.spec.ts wraz z wykorzystaniem PulpitPage
, w którym znajduje się SideMenuComponent
:
import { test, expect } from '@playwright/test'; import { loginData } from '../test-data/login.data'; import { LoginPage } from '../pages/login.page'; import { PaymentPage } from '../pages/payment.page'; import { PulpitPage } from '../pages/pulpit.page'; test.describe('Payment tests', () => { test.beforeEach(async ({ page }) => { const userId = loginData.userId; const userPassword = loginData.userPassword; await page.goto('/'); const loginPage = new LoginPage(page) await loginPage.loginInput.fill(userId) await loginPage.passwordInput.fill(userPassword) await loginPage.loginButton.click() const pulpitPage = new PulpitPage(page) await pulpitPage.sideMenuComponent.paymentLink.click(); }); test('simple payment', async ({ page }) => { // Arrange const transferReceiver = 'Jan Nowak'; const transferAccount = '12 3456 7890 1234 5678 9012 34568'; const transferAmount = '222'; const expectedMessage = `Przelew wykonany! ${transferAmount},00PLN dla Jan Nowak`; // Act const paymentPage = new PaymentPage(page) await paymentPage.transferReceiverInput.fill(transferReceiver) await paymentPage.transferToInput.fill(transferAccount) await paymentPage.transferAmountInput.fill(transferAmount) await paymentPage.transferButton.click() await paymentPage.actionCloseButton.click() // Assert await expect(paymentPage.messageText).toHaveText(expectedMessage); }); });
Dodaliśmy w pliku payment.page.ts i pulpit.page.ts element
, a w pliku payment.spec.ts z testami stosujemy :
z racji ze zdecydowaliśmy sie utworzyć w obu plikach zmienną sideMenu poprawnie nie powinno być
?
Słuszna uwaga!
Z jednej strony – moglibyśmy w
beforeEach
od razu stworzyćpaymentPage
, bo każda z tych stron zawiera w sobieSideMenuComponent
.Z drugiej strony – zaraz po zalogowaniu jesteśmy na stronie pulpitu użytkownika (który reprezentuj
PulpitPage
). Zatem jeśli wykorzystamy opcję:obrazujemy w testach jak wygląda droga użytkownika (czyli jakie kolejno otwierane są strony).
Oba podejścia są poprawne i każde z nich zawiera plusy i minusy. W pierwszym podejściu zmniejszamy odrobinę liczbę linii kodu, a w drugim – mamy więcej informacji o kolejnych krokach/odwiedzonych stronach 🙂
u siebie w testach takie elementy (widoczne w każdym panelu, tj. np. pasek nawigacji, górny pasek, jakieś boczne ikony) wydzieliłem do osobnych page objectów czyli, przykładowo, taki pasek w naszym banku to czysta nawigacja:
– navigation.page.ts
i potem w testach
const goTo = new navigationPage(page)
await goTo.platnosci();
macie jakieś przeciwwskazania dla takiego podejścia? Osobiście wydaje mi się to bardziej przejrzyste niż “ukrywanie” takiego menu w jakimś innym page’u 🙂
To troche zalezy od Twojego przypadku 🙂
Ogólnie jeśli chodzi o komponenty występujące na stronach (menu, paski nawigacji, wyszukiwarka etc) zazwyczaj lepiej wydzielić jako osoby moduł, a następnie umiescic je w danej stronie (tak jak w przykładzie, stosując kompozycję).
Przemawia za tym, że dane komponenty nie występują same w naturze, a są powiązane z daną stroną/stronami 🙂 Dzięki temu zabezpieczamy się przed wykorzystaniem elementów w momencie, gdy nie jesteśmy na stronie, na której te elementy występują.
Jednak możemy zastosować też inne wzorce i praktyki, które modyfikują to podejście.
Jesli dobrze rozumiem, to w Twoim przypadku taki komponent nawigacyjny pełni rolę nawigatora/transportera, czyli jest w stanie przekierować nas w dane miejsce (jak rozumiem – za pomoca menu).
Rozważył bym też zmianę nazwy
navigation.page.ts
, bo page sugeruje że to strona 🙂 Może np.navigation.component.ts
(jednak to moze sugerować komponent strony).Więc tu do rozważenia jak zaprojektować strony/komponenty/nawigacje w Twoim przypadku 🙂
hmmm
jeśli chodzi o:
pulpitPage.sideMenuComponent.paymentLink.click();
to czy nie lepiej by było zainicjować sideMenuComponent w teście bezpośrednio i po prostu się odnosić do niego:
sideMenuComponent.paymentLink.click()?
Pozwolę sobie porozmyślać, bo jest filozoficzne pytanie.
`sideMenu` samo w sobie bez strony nie istnieje, dlatego w teście pulpitPage.sideMenu jest mniej dwuznaczne niż samo sideMenu bo komponent ten też będzie widoczny na innych stronach naszego serwisu, a są strony, na których on nie będzie widoczny jak loginPage. Nie używałbym raczej suffixu `Component` bo to już dodawanie za dużej ilości szczegółów do nazwy zmiennej, ale to następny temat rzeka 😀
Zgodzę się –
sideMenu
jako takie nie istnieje bez rodzica w postaci danej strony 😉 Przez to, że łączymy ten komponent ze stroną, to wiemy też na jakich stronach możemy go wywołać, a na jakich go nie ma (np. LoginPage) 🙂 Jest to mega cenna informacja i potrafi znacznie ułatwić pisanie testów.Nie jest to jedyne możliwe podejście – i jesli w projekcie z jakiegoś powodu podjęłaby została decyzja, że możemy używać elementów niezależenie – to też jest możliwe 🙂
Minus – zaciemniamy sobie w testach co i gdzie jest możliwe do wykonania, jakie akcje gdzie mozna podejmować.
Nazwy zmiennych to inna historia 😀
Przy nauce na początku dobrze obrazują co jest czym, później można optymalizować i wpisywać ustalenia do Coding Standards 😉
PS. Wcześniej nie zauważyłem powiadomienia o Twoim komentarzu :/ Rzuć proszę okiem na komentarz od Adama i mój w tamtym wątku) 🙇♂️