Powrót do: Playwright Elements – Kluczowe koncepcje automatyzacji testów
Selektory i lokatory w Playwright – jak znajdować elementy na stronie
Prezentacja
👉jaktestowac/playwright-elements-locators
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:
- Selektory CSS: Najczęściej używane, np.
#element-id
,.class-name
,div > span
. - Selektory XPath: Używane do nawigacji po strukturze dokumentu XML/HTML, np.
//div[@id='element-id']
. - Selektory tekstowe: Pozwalają na wybór elementów na podstawie widocznego tekstu, np.
text=Submit
. - Selektory atrybutów: Pozwalają na wybór elementów na podstawie wartości atrybutów, np.
[placeholder="Search"]
. - 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
- Selektor to wzorzec lub wyrażenie używane do identyfikacji jednego lub więcej elementów w DOM.
- 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
.
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
- Używane do lokalizowania elementów na podstawie atrybutu data-testid (np.
-
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ócejpage.locator('button')
- Przykład:
page.locator('xpath=//button')
lub krócejpage.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.
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 atrybutid
lubdata-test-id
. - Korzystaj z atrybutów data-testid, testid lub id
Bardzo często w projektach stosuje się klasyczny atrybutid
, który służy do jednoznacznej identyfikacji danego elementu. Czasem wprowadzenie dodatkowych specjalnych atrybutów, takich jakdata-testid
czytestid
, 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 jakdata-testid
czyid
, 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
czygetByLabelText
. 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
lubid
. - 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 elementuform-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ówdata-testid
lubid
Zewnętrzne linki i zasoby
- Strona do testów: [aplikacja GAD musi być uruchomiona lokalnie!] practice/simple-elements.html
- Oficjalna dokumentacja: Lokatory
- Oficjalna dokumentacja: built-in locators
- Lekcja: Kod strony HTML
- Lekcja: Elementy strony w przeglądarce
- Jak pobrać projekt z repozytorium? Zobacz naszą lekcję: Instalacja projektu z pliku w Node.js