POM i komponenty, czyli wspólne elementy

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

Prezentacja

POM i komponenty, czyli wspólne elementy

POM i komponenty, czyli wspólne elementy

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

8 komentarzy

  1. Dodaliśmy w pliku payment.page.ts i pulpit.page.ts element

    sideMenu = new SideMenuComponent(this.page);
    

    , a w pliku payment.spec.ts z testami stosujemy :

    const pulpitPage = new PulpitPage(page);
    await pulpitPage.sideMenu.paymentButton.click();
    

    z racji ze zdecydowaliśmy sie utworzyć w obu plikach zmienną sideMenu poprawnie nie powinno być

    const paymentPage = new PaymentPage(page);
    await paymentPage.sideMenu.paymentButton.click();
    

    ?

    Avatar Grzegorz Gajownik
    1. Słuszna uwaga!
      Z jednej strony – moglibyśmy w beforeEach od razu stworzyć paymentPage, bo każda z tych stron zawiera w sobie SideMenuComponent.

      Z drugiej strony – zaraz po zalogowaniu jesteśmy na stronie pulpitu użytkownika (który reprezentuj PulpitPage). Zatem jeśli wykorzystamy opcję:

      const pulpitPage = new PulpitPage(page);
      await pulpitPage.sideMenu.paymentButton.click();
      

      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 🙂

      Krzysiek Kijas Krzysiek Kijas
  2. 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 🙂

    Avatar Tomek Graul
    1. 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 🙂

      Krzysiek Kijas Krzysiek Kijas
  3. 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()?

    Avatar Michał Dudziak
    1. 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 😀

      Avatar Adam Pajda
      1. 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 😉

        Krzysiek Kijas Krzysiek Kijas

Dodaj komentarz

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