Chaining, filtrowanie i parametry w lokatorach

Prezentacja

Różne sposoby szukania elementów i metody w lokatorach

Różne sposoby szukania elementów i metody w lokatorach

Różne sposoby szukania elementów i metody w lokatorach

Różne sposoby szukania elementów i metody w lokatorach

Różne sposoby szukania elementów i metody w lokatorach

Różne sposoby szukania elementów i metody w lokatorach

Różne sposoby szukania elementów i metody w lokatorach

Różne sposoby szukania elementów i metody w lokatorach

Różne sposoby szukania elementów i metody w lokatorach

TIP: Cały kod testów z poszczególnych lekcji znajdziesz w specjalnie przygotowanym repozytorium:
👉jaktestowac/playwright-elements-locators

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

Parametry w metodach getBy

Dodatkowe parametry w metodach getBy pozwalają lepiej sprecyzować i określić szukany element.

Przykładowo, poza rolą możemy jeszcze określić tekst, jaki powinien znajdować się na elemencie:

const elementRole = "button";
const elementText = "Click!";


const buttonLocator = page.getByRole(elementRole, { name: elementText });

Filtrowanie lokatorów

Filtrowanie lokatorów to sposób na wybieranie konkretnych elementów z kilku dostępnych na stronie.

Na przykład, możesz wybrać przyciski lub tekst, które zawierają określone słowa, lub które mają wewnątrz określone elementy (dzieci). Dzięki temu możesz łatwo znaleźć i np. kliknąć właściwy przycisk lub element na stronie.

Przykład filtrowania elementów button po tekście:

test("Single button click (using filter)", async ({ page }) => {
    const role = "button";


    // we can define the locator for the element
    const buttonLocator = page.getByRole(role).filter({ hasText: "Click me!" });


    // print the count of buttons on the page
    console.log("buttonLocator", await buttonLocator.count());


    // check if number of buttons is 1
    await expect(buttonLocator).toHaveCount(1);


    // click the button
    await buttonLocator.click();


    // check if the button is clicked
    const resultsLocator = page.getByTestId("dti-results");
    await expect(resultsLocator).toHaveText("You clicked the button!");
  });

Pełna dokumentacja z przykładami: Lokatory i filtry

Łączenie lokatorów (chaining)

Łączenie lokatorów (chaining) jest używane do bardziej precyzyjnego wyszukiwania elementów na stronie.

Łączenie lokatorów pozwala zawężać miejsce, w którym znajduje się szukany przez nas element.
Najpierw wyszukujemy wiersz, który zawiera dany tekst, a następnie w znalezionym wierszu – znajdujemy przycisk:

const rowLocator = page.getByRole("row", { name: "Row 2" })
const buttonLocator = rowLocator.getByRole("button", { name: "Click!" });

Co można też napisać w krótszy sposób:

      const buttonLocator = page
        .getByRole("row", { name: "Row 2" })
        .getByRole("button", { name: "Click!" });

Cały test może mieć następującą postać:

test("Single button click (chained getBy)", async ({ page }) => {
      const buttonLocator = page
        .getByRole("row", { name: "Row 2" })
        .getByRole("button", { name: "Click!" });


      // check if number of buttons is 1
      await expect(buttonLocator).toHaveCount(1);


      // click the button
      await buttonLocator.click();


      // check if the button is clicked
      const resultsLocator = page.getByTestId("dti-results");
      await expect(resultsLocator).toHaveText(
        "You clicked the button! (row 2)"
      );
    });


Pełna dokumentacja z przykładami: Lokatory i operatory.

Kod do lekcji

Początkowy kod (fragmenty)

Początkowa zawartość pliku locator-filters-operations.spec.ts:

import { test, expect } from "@playwright/test";


test.describe("Locator filters", () => {
  test.beforeEach(async ({ page }) => {
    await page.goto("/practice/simple-multiple-elements-no-ids.html");
  });


  test.describe("Finding element - different approaches", () => {
    test("Single button click using options", async ({ page }) => {
      // TODO:
      // Arrange:
      // Act:
      // Assert:
    });


    test("Single button click (using filter and hasText)", async ({ page }) => {
      // TODO:
      // Arrange:
      // Act:
      // Assert:
    });
  });
});

Kolejna sekcja z testami do locator-filters-operations.spec.ts:



  test.describe("Buttons in table - different approaches", () => {
    test("Single button click (chained getBy)", async ({ page }) => {
      // TODO:
      // Arrange:
      // Act:
      // Assert:
    });


    test("Single button click (using filter and has)", async ({ page }) => {
      // TODO:
      // Arrange:
      // Act:
      // Assert:
    });
  });

Cały początkowy kod

Początkowa zawartość pliku locator-filters-operations.spec.ts:

import { test, expect } from "@playwright/test";


test.describe("Locator filters", () => {
  test.beforeEach(async ({ page }) => {
    await page.goto("/practice/simple-multiple-elements-no-ids.html");
  });


  test.describe("Finding element - different approaches", () => {
    test("Single button click using options", async ({ page }) => {
      // TODO:
      // Arrange:
      // Act:
      // Assert:
    });


    test("Single button click (using filter and hasText)", async ({ page }) => {
      // TODO:
      // Arrange:
      // Act:
      // Assert:
    });
  });


  test.describe("Buttons in table - different approaches", () => {
    test("Single button click (chained getBy)", async ({ page }) => {
      // TODO:
      // Arrange:
      // Act:
      // Assert:
    });


    test("Single button click (using filter and has)", async ({ page }) => {
      // TODO:
      // Arrange:
      // Act:
      // Assert:
    });


  });
});


Finalny kod

Finalna zawartość pliku locator-filters-operations.spec.ts:

import { test, expect } from "@playwright/test";


test.describe("Locator filters", () => {
  // https://playwright.dev/docs/locators#locator-operators


  test.beforeEach(async ({ page }) => {
    await page.goto("/practice/simple-multiple-elements-no-ids.html");
  });


  test.describe("Finding element - different approaches", () => {
    test("Single button click using options", async ({ page }) => {
      // Arrange:
      const elementRole = "button";
      const elementText = "Click me!";
      const expectedMessage = "You clicked the button!";
      const resultsTestId = "dti-results";


      // we can define the locators for the element
      const buttonLocator = page.getByRole(elementRole, { name: elementText });
      const resultsLocator = page.getByTestId(resultsTestId);


      // print the count of buttons on the page
      console.log("buttonLocator", await buttonLocator.count());


      // Act:
      await buttonLocator.click();


      // Assert:
      await expect(resultsLocator).toHaveText(expectedMessage);
    });


    test("Single button click (using filter and hasText)", async ({ page }) => {
      // Arrange:
      const elementRole = "button";
      const elementText = "Click me!";
      const expectedMessage = "You clicked the button!";
      const resultsTestId = "dti-results";


      const buttonLocator = page
        .getByRole(elementRole)
        .filter({ hasText: elementText });


      const resultsLocator = page.getByTestId(resultsTestId);
      // print the count of buttons on the page
      console.log("buttonLocator", await buttonLocator.count());


      // Act:
      await buttonLocator.click();


      // Assert:
      await expect(resultsLocator).toHaveText(expectedMessage);
    });


  });


  test.describe("Buttons in table - different approaches", () => {
    test("Single button click (chained getBy)", async ({ page }) => {
      // Arrange:
      const elementRole = "button";
      const elementText = "Click!";
      const parentRole = "row";
      const parentText = "Row 2!";
      const expectedMessage = "You clicked the button! (row 2)";
      const resultsTestId = "dti-results";


      const resultsLocator = page.getByTestId(resultsTestId);
      const buttonLocator = page
        .getByRole(parentRole, { name: parentText })
        .getByRole(elementRole, { name: elementText });


      // Act:
      await buttonLocator.click();


      // Assert:
      await expect(resultsLocator).toHaveText(expectedMessage);
    });


    test("Single button click (using filter and hasText)", async ({ page }) => {
      // Arrange:
      const elementRole = "button";
      const elementText = "Click here!";
      const expectedMessage = "You clicked the button! (Third one!)";
      const resultsTestId = "dti-results";


      const resultsLocator = page.getByTestId(resultsTestId);
      const buttonLocator = page.getByRole(elementRole).filter({
        hasText: elementText,
      });


      // Act:
      await buttonLocator.click();


      // Assert:
      await expect(resultsLocator).toHaveText(expectedMessage);
    });
  });
});


Bonusowy test

Wykorzystanie metod getBy i filter:



    test("Single button click (using filter and has)", async ({ page }) => {
      // Arrange:
      const elementRole = "button";
      const parentRole = "row";
      const siblingText = "Row 2";
      const expectedMessage = "You clicked the button! (row 2)";
      const resultsTestId = "dti-results";


      const buttonLocator = page
        .getByRole(parentRole)
        .filter({ has: page.getByText(siblingText) })
        .getByRole(elementRole);


      const resultsLocator = page.getByTestId(resultsTestId);
      // print the count of buttons on the page
      console.log("buttonLocator", await buttonLocator.count());


      // Act:
      await buttonLocator.click();


      // Assert:
      await expect(resultsLocator).toHaveText(expectedMessage);
    });


    test("Single button click (using filter and hasText)", async ({ page }) => {
      // Arrange:
      const elementRole = "button";
      const elementText = "Click here!";
      const expectedMessage = "You clicked the button! (Third one!)";
      const resultsTestId = "dti-results";


      const resultsLocator = page.getByTestId(resultsTestId);
      const buttonLocator = page.getByRole(elementRole).filter({
        hasText: elementText,
      });


      // Act:
      await buttonLocator.click();


      // Assert:
      await expect(resultsLocator).toHaveText(expectedMessage);
    });


Zewnętrzne linki i zasoby

7 komentarzy

  1. Dokładnie taka praktykę mamy, nie robimy testID do wszystkich elementów, tylko do tych “sekcji”, chyba, ze gdzieś cos potrzebujemy dodatkowo to wiadomo 🙂 Tak, przerobiłem już ten materiał, fajnie porządkuje wiedze 🙂

    Avatar Damian Krotowski
    1. Hej,
      Bardzo dobre pytanie!
      Z doświadczenia, częściej bazuje na filtrach, niż na nth.
      Chociaż to zależy troche od kontekstu.
      Do nth potrzebujemy konkretnego indexu, który może się łatwo zmieniać (np. gdy chcemy nacisnąć piąty (5.) przycisk, a na stronie zajdą zmiany i zostanie dodany kolejny przycisk i nasz przycisk będzie pod indexem 6).

      Metoda nth bardzo się przydaje, gdy chcemy przejść przez wszystkie znalezione elementy i kolejno chcemy wykonać na nich takie same akcje.

      Przykład z życia:
      – tworzenie użytkownika, któremu przypisujemy role
      – role przypisujemy zaznaczając checkboxy
      – w naszym teście chcemy użytkownikowi zawsze przypisać wszystkie role.
      Wtedy możliwe ze najlepszym rozwiązaniem będzie właśnie nth

      Natomiast, gdy chcemy precyzyjnie wyszukać jeden element – to metoda filter może być odpowiedniejsza 🙂

      W skrajnych przypadkach może się okazać, że ciężko znaleźć jeden element za pomocą metody filter, i jedyną opcją jest metoda nth.
      Wtedy tymczasowo zastosowałbym takie rozwiązanie, ale dodając 2 kroki:
      – oznaczając to miejse jako dług techniczny
      – dodając zadanie na dodanie na font-endzie ID/test ID aby precyzyjnie wyszukać dany element 🙂

      Krzysiek Kijas Krzysiek Kijas
      1. Jasne rozumiem, ja natomiast często spotykam się z sytuacja gdzie nazwa np przycisku jest zmieniana i w moim przypadku częściej problematyczne byłoby szukanie po nazwie. W zespole mamy ustalone, ze strona jest podzielona na różne “sekcje” które mają swoje test ID i w takim wypadku szukanie po nth np button wychodzi stabilniej. Ale w sumie jest tak jak piszesz i chyba nie ma na to złotego środka 🙂

        Avatar Damian Krotowski
        1. Pytanie czy każda z tych sekcji ma jakieś swoje unikalne ID, atrybut lub nazwę?
          Bo jesli tak – to najpierw możemy złapać całą sekcję (za pomocą jakiegoś selektora/getBy), a później tylko w jej obrębie możemy szukać elementu, który nas interesuje 🙂

          Krzysiek Kijas Krzysiek Kijas

Dodaj komentarz

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