Selektory i lokatory w Playwright – jak znajdować elementy na stronie

Prezentacja

Selektory i lokatory w Playwright - jak znajdować elementy na stronie

Selektory i lokatory w Playwright - jak znajdować elementy na stronie

Selektory i lokatory w Playwright - jak znajdować elementy na stronie

Selektory i lokatory w Playwright - jak znajdować elementy na stronie

Selektory i lokatory w Playwright - jak znajdować elementy na stronie

Selektory i lokatory w Playwright - jak znajdować elementy na stronie

Selektory i lokatory w Playwright - jak znajdować elementy na stronie

Selektory i lokatory w Playwright - jak znajdować elementy na stronie

Selektory i lokatory w Playwright - jak znajdować elementy na stronie

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 🎭
TIP: Instalację VS Code, Node.js oraz przygotowanie pierwszego projektu omawiamy dokładnie w lekcji Playwright – Twój pierwszy test automatyczny.

Selektory

Selektory są to wzorzec lub wyrażenie używane do identyfikacji jednego lub więcej elementów na stronie. Mogą one bazować na różnych atrybutach i właściwościach elementów HTML, jak typ, id, nazwa, tekst zawarty w danym elemencie itp.

Playwright obsługuje różne typy selektorów, takie jak:

  1. Selektory CSS: Najczęściej używane, np. #element-id, .class-name, div > span.
  2. Selektory XPath: Używane do nawigacji po strukturze dokumentu XML/HTML, np. //div[@id='element-id'].
  3. Selektory tekstowe: Pozwalają na wybór elementów na podstawie widocznego tekstu, np. text=Submit.
  4. Selektory atrybutów: Pozwalają na wybór elementów na podstawie wartości atrybutów, np. [placeholder="Search"].
  5. Selektory zagnieżdżone: Można zagnieżdżać selektory, np. div#parent >> span.child.

Lokatory

Lokatory w Playwright są używane do wykonanie akcji na elementach znalezionych na stronie. Lokatory korzystają z selektorów do identyfikacji elementów. W Playwright lokator jest obiektem, który pozwala na wykonywanie operacji takich jak kliknięcie, wpisanie tekstu, czy oczekiwanie na element.

Przykład użycia lokatora:

const buttonSelector = 'button#submit'

const buttonLocator = page.locator(buttonSelector)
locator.click()

Samo stworzenie lokatora:

const buttonSelector = 'button#submit'

const buttonLocator = page.locator(buttonSelector)

nie powoduje jego powiązania z obiektem na stronie!
Dopiero przy próbie wykonywania akcji locator.click() Playwright zaczyna szukać danego elementu na stronie😉

Różnica między selektorami, a lokatorami

  1. Selektor to wzorzec lub wyrażenie używane do identyfikacji jednego lub więcej elementów w DOM.
  2. Lokator to obiekt, który przechowuje selektor i umożliwia wykonywanie akcji na znalezionym elemencie.

Podsumowując, selektory definiują sposób znajdowania elementów, a lokatory używają tych selektorów, aby precyzyjnie wskazywać elementy i wykonywać na nich różne operacje.

Lokatory, a elementy na stronie

Lokatory wskazują na element na stronie.

Jednak sama inicjalizacja i utworzenie lokatora nie wiąże go z daną stroną.

Dopiero przy wykonywaniu metod na lokatorze (jak click()) powoduje, że lokator zaczyna szukać elementów na stronie i wykonywać na nich akcje.

Wbudowane lokatory (built-in locators)

Playwright oferuje wiele wbudowanych lokatorów. Twórcy zalecają z korzystania z metod, takich jak page.getByRole() czy page.getByLabel() i innych page.getBy.

TIP: Playwright oferuje metody, które pomagają w znajdowaniu elementów, np. getByRole, getByText czy getByLabelText. Używanie tych metod zamiast czystych lokatorów z selektorami CSS czy Xpath może prowadzić do bardziej czytelnych testów. Jednak pamiętaj, że takie selektory mogą w niektórych aplikacjach być mało precyzyjne lub często zmienne (jak szukanie po tekście, labelach czy tytułach). A to może prowadzić do testów mało odpornych na zmiany w aplikacji.

Dlatego sugerujemy wyszukiwanie elementów po data-testid lub id.

Oto lista wbudowanych lokatorów w Playwright:

  • Lokalizatory ról (getByRole):

    • Używane do lokalizowania elementów na podstawie ich roli (np. przycisk, nagłówek).
    • Przykład: page.getByRole('button', { name: 'Sign in' })
  • Lokalizatory etykiet (getByLabel):

    • Używane do lokalizowania form kontrolnych na podstawie przypisanych etykiet.
    • Przykład: page.getByLabel('Password')
  • Lokalizatory placeholderów (getByPlaceholder):

    • Używane do lokalizowania pól wejściowych na podstawie tekstu placeholdera.
    • Przykład: page.getByPlaceholder('name@example.com')
  • Lokalizatory tekstu (getByText):

    • Używane do lokalizowania elementów na podstawie zawartego w nich tekstu.
    • Przykład: page.getByText('Welcome, John')
  • Lokalizatory tekstu alternatywnego (alt text) (getByAltText):

    • Używane do lokalizowania obrazów na podstawie atrybutu alt.
    • Przykład: page.getByAltText('playwright logo')
  • Lokalizatory tytułów (getByTitle):

    • Używane do lokalizowania elementów na podstawie atrybutu title.
    • Przykład: page.getByTitle('Issues count')
  • Lokalizatory test id (getByTestId):

    • Używane do lokalizowania elementów na podstawie atrybutu data-testid (np. data-testid).
    • Przykład: page.getByTestId('directions')
    • Atrybut ten można zmienić w konfiguracji testów w playwright.config.ts
  • Lokalizatory CSS lub XPath (locator):

    • Używane do lokalizowania elementów na podstawie selektorów CSS lub XPath.
    • Przykład: page.locator('css=button') lub krócej page.locator('button')
    • Przykład: page.locator('xpath=//button') lub krócej page.locator('//button')

Kod i polecenia

Przygotowanie projektu

W nowym pustym katalogu pw-locators wykonaj polecenie:

npm init playwright@latest

Otworzenie edytora VS Code w danej lokalizacji:

code .

Spowoduje to przygotowanie projektu, pobranie Playwrighta oraz instalacje przeglądarek.

TIP: Konfigurację Prettiera w projekcie omawiamy dokładnie w lekcji Prettier, czyli formatter kodu.

Minimalna konfiguracja

Minimalna konfiguracja w pliku playwright.config.ts:

import { defineConfig, devices } from "@playwright/test";


export default defineConfig({
  testDir: "./tests",
  fullyParallel: true,
  workers: 1,
  reporter: "html",
  use: {
    baseURL: "http://localhost:3000",
    trace: "on",
  },


  projects: [
    {
      name: "chromium",
      use: { ...devices["Desktop Chrome"] },
    },
  ],
});

Kod – lokatory getBy

Początkowa zawartość pliku using-get-by.spec.ts:

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


test.describe("Finding different elements with getBy methods", () => {
  test.beforeEach(async ({ page }) => {
    await page.goto("/practice/simple-elements.html");
  });


  test("Find button element by getByRole methods", async ({ page }) => {
    // TODO:
  });
});

Finalna zawartość pliku using-get-by.spec.ts:

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


test.describe("Finding different elements with getBy methods", () => {
  test.beforeEach(async ({ page }) => {
    await page.goto("/practice/simple-elements.html");
  });


  test("Find button element by getByRole methods", async ({ page }) => {
    const elementLocator = page.getByRole("button", { name: "Click me" });


    await expect(elementLocator).toBeVisible();
  });


  test("Find button element by getByText and getByTestId methods", async ({
    page,
  }) => {
    // Arrange:
    const resultId = "dti-results";
    const expectedMessage = "You clicked the button!";
    const elementLocator = page.getByText("Click me");


    // Act:
    await expect(elementLocator).toBeVisible();
    await elementLocator.click();


    // Assert:
    const resultElementLocator = page.getByTestId(resultId);
    await expect(resultElementLocator).toHaveText(expectedMessage);
  });
});


Kod – lokatory CSS i XPath

Początkowa zawartość pliku using-locators.spec.ts:

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


test.describe("Finding different elements using raw locators", () => {
  test.beforeEach(async ({ page }) => {
    await page.goto("/practice/simple-elements.html");
  });


  test("Find label element by ID (CSS)", async ({ page }) => {
    // TODO:
  });


  test("Find label element by ID (XPath)", async ({ page }) => {
    // TODO:
  });
});

Końcowa zawartość pliku using-locators.spec.ts:

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


test.describe("Finding different elements using raw locators", () => {
  test.beforeEach(async ({ page }) => {
    await page.goto("/practice/simple-elements.html");
  });


  test("Find label element by ID (CSS)", async ({ page }) => {
    // Arrange:
    const selector = "#id-label-element";
    const elementLocator = page.locator(selector);


    // Assert:
    await expect(elementLocator).toBeVisible();
    await expect(elementLocator).toHaveText("Some text for label");
  });


  test("Find label element by ID (XPath)", async ({ page }) => {
    // Arrange:
    const selector = "//*[@id='id-label-element']";
    const elementLocator = page.locator(selector);


    // Assert:
    await expect(elementLocator).toBeVisible();
    await expect(elementLocator).toHaveText("Some text for label");
  });
});




Wady i zalety selektorów XPATH i CSS

Zalety selektorów CSS w porównaniu do XPath

  • CSS Selectors są odrobinę szybsze w porównaniu z XPath.
  • Selektory CSS są bardziej czytelne i łatwiejsze do nauki.
  • Selektory CSS są kompatybilne ze wszystkimi nowoczesnymi przeglądarkami.

Wady selektorów CSS w porównaniu do XPath

  • CSS jest jednokierunkowy – pozwala na przeglądanie węzłów tylko od rodzica do dziecka. W skomplikowanych scenariuszach trudno jest skonstruować poprawny selektor.
  • Selektory CSS nie zapewniają metod obsługi złożonych lokalizatorów elementów, jak to ma miejsce w przypadku np. metod Axes w XPath.
  • Jeśli aplikacja nie zawiera atrybutów dla elementu w drzewie DOM, pisanie selektora CSS będzie trudne i może stać się zawodnym.

Poniżej zamieściliśmy ogólne porównanie różnych selektorów.

Co chcemy znaleźć? Selektor CSS Selektor XPath
Wszystkie elementy
*
//*
Wszystkie elementy <a>
a
//a
Wszystkie elementy podrzędne <a>
a *
//a//*
Wszystkie bezpośrednie elementy podrzędne <a>
a > *
//a/*
Element po nazwie tagu i atrybucie
input[type='text']
//input[@type='text']
Element z określoną wartością atrybutu href
a[href='example.com']
//a[@href='example.com']
Element po ID
#elementID
//*[@id='elementID']
Element div z określoną klasą
div.className
//div[contains(@class,'className')]
Element po klasie
.className
//*[contains(@class,'className')]
Element po wartości atrybutu
[attribute='value']
//*[@attribute='value']
Elementy z określonym atrybutem, niezależnie od jego wartości
[attribute]
//*[@attribute]
Wszystkie elementy <a> z podrzędnym <span> Niemożliwe
//a[span]
Pierwszy element podrzędny wszystkich elementów <a>
a > *:first-child
//a/*[1]
Pobranie węzłów tekstowych Niemożliwe
//a/text()
Pobieranie wartości atrybutu href z elementów <a> Niemożliwe
//a/@href
Wszystkie elementy <a> zawierające “Click”
a:contains('Click')
//a[contains(text(),'Click')]
Poprzedni element dla elementów <a>
a:has(+ *)

ale nie wspierane przez wszystkie przeglądarki

//a/preceding-sibling::*
Następny element dla elementów <a>
a + sibling
//a/following-sibling::*
Elementy zawierające określony tekst Niemożliwe
//*[contains(text(),'text')]
Elementy z określonym atrybutem w zagnieżdżonym węźle Niemożliwe
//div[span/@class='className']
Elementy na określonej pozycji (np. trzeci element) Niemożliwe
(//div)[3]
Elementy poprzedzające dany element Niemożliwe
//div/preceding::*
Elementy następujące po danym elemencie Niemożliwe
//div/following::*
Elementy, które zawierają atrybut, który zawiera ciąg znaków www
[href*="www"]
//*[contains(@href,  "www")]
Elementy o atrybucie zaczynającym się na www
[href*="www"]
//*[starts-with(@href, "www")]
Elementy o atrybucie kończącym się na dany ciąg znaków
[href$="com"]
//*[substring(@href, string-length(@href) - string-length("com") + 1) = "com"]

O czym warto pamiętać?

  • Nie zaleca się używania selektorów opartych na Xpath
    Chociaż Xpath może być potężnym narzędziem, jego stosowanie w testach automatycznych może prowadzić do mniej czytelnych testów. Zamiast tego sugerujemy używać selektory CSS oparte o atrybut id lub data-test-id.
  • Korzystaj z atrybutów data-testid, testid lub id
    Bardzo często w projektach stosuje się klasyczny atrybut id, który służy do jednoznacznej identyfikacji danego elementu. Czasem wprowadzenie dodatkowych specjalnych atrybutów, takich jak data-testid czy testid, ułatwia wyszukiwanie elementów, a tym samym - tworzenie stabilnych i łatwych do utrzymania selektorów. Dzięki takim atrybutom selektory są mniej podatne na zmiany w stylach CSS i struktury DOM.
  • Zadbaj o unikalność atrybutów w testowanej aplikacji
    Atrybuty używane do identyfikacji elementów w testach, takie jak data-testid czy id, powinny być unikalne w całej aplikacji, albo przynajmniej na danej stronie. Unikalność atrybutów zapobiega niejednoznaczności i potencjalnym konfliktom, które mogą prowadzić do nieprawidłowego działania testów. Tę praktykę warto przedyskutować z całym zespołem, a następnie warto wprowadzić ją w projekcie.
  • Stosuj z rozwagą wysokopoziomowe metody Playwright
    Playwright oferuje metody, które pomagają w znajdowaniu elementów, np. getByRole, getByText czy getByLabelText. Używanie tych metod zamiast czystych lokatorów z selektorami CSS czy Xpath może prowadzić do bardziej czytelnych testów. Jednak pamiętaj, że takie selektory mogą w niektórych aplikacjach być mało precyzyjne lub często zmienne (jak szukanie po tekście, labelach czy tytułach). A to może prowadzić do testów mało odpornych na zmiany w aplikacji.

    Dlatego sugerujemy wyszukiwanie elementów po data-testid lub id.

  • Używaj ostrożnie selektorów z odniesieniem do rodzica
    Czasami istnieje potrzeba odwołania się do elementów w określonym kontekście, np. wewnątrz określonego kontenera np. .form-container [data-test-id="submit-button"]. Może to być konieczne, gdy nie mamy unikalnych atrybutów na danej stronie.. W takich przypadkach używaj selektorów z odwołaniem do rodzica (w tym przypadku do elementu form-container). Jednak staraj się, aby nie były one zbyt rozbudowane, bo wtedy zmiany w drzewie DOM mogą zepsuć testy.
    Dlatego sugerujemy przedyskutowanie i z zespołem, a następnie próbę wprowadzenia unikalnych atrybutów data-testid lub id

Zewnętrzne linki i zasoby

Dodaj komentarz

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