Powrót do: Playwright Elements – Kluczowe koncepcje automatyzacji testów
Czym jest test id i data-testid? Jak szukać elementów po tych atrybutach?
Prezentacja
👉jaktestowac/playwright-elements-locators
Czym jest data-testid?
Atrybuty data-testid są niestandardowymi atrybutami, które można dodać do elementów HTML. Dzięki temu łatwiej jest identyfikować i wyszukiwać te elementy podczas pisania testów automatycznych.
Choć data-testid nie są częścią specyfikacji HTML, są powszechnie używane w ramach testowych i narzędziach do lokalizowania i interakcji z elementami.
Jakie są zalety stosowania atrybutów data-testid?
- Zwiększona niezawodność testów
Atrybutydata-testidpoprawiają niezawodność testów automatycznych. W testach korzystających z selektorów CSS lub wyrażeniach XPath łatwiej wyszukiwać i identyfikować elementy. - Łatwiejsza lokalizacja elementów
Atrybuty
data-testidoferują jasny i spójny sposób identyfikacji elementów na stronie. Ułatwia to deweloperom i testerom odnajdywanie elementów i interakcję z nimi. - Oddzielenie logiki od testów
Korzystanie zdata-testiddo identyfikacji elementów dla celów testowych pozwala na oddzielenie logiki strony od testów. Dzięki temu zmiany w HTML mają mniejszy wpływ na testy, co ułatwia utrzymanie i aktualizację kodu.
Jakie są wady stosowania atrybutów data-testid?
- Nadmiar atrybutów w kodzie HTML
Dodanie dużej liczby atrybutówdata-testiddo kodu HTML może sprawić, że stanie się on mniej przejrzysty. Może to utrudniać czytanie i zrozumienie struktury dokumentu. - Ryzyko zduplikowanych wartości
Nieumiejętne zarządzanie atrybutami
data-testidmoże prowadzić do duplikacji wartości, co może spowodować problemy z identyfikacją elementów w testach. Konieczne jest wprowadzenie i utrzymanie spójnych konwencji nazewniczych, aby uniknąć takich konfliktów. Ważne: ten problem nie dotyczy tylko atrybutówdata-testid, ale może dotyczyć każdego sposobu identyfikacji elementów na stronie.
Stosowanie atrybutów data-testid w procesie rozwoju może przynieść wiele korzyści, takich jak poprawa niezawodności testów, łatwiejsza lokalizacja elementów, oddzielenie logiki od testów oraz czytelniejsza dokumentacja. Dzięki spójnym i znaczącym konwencjom nazewniczym, deweloperzy mogą tworzyć bardziej niezawodny i łatwiejszy do utrzymania kod.
Można też wprowadzać inne własne atrybuty niestandardowe, takie jak dataid, aby identyfikować elementy HTML w testach. Wprowadzanie własnych atrybutów niestandardowych, zazwyczaj jest możliwe i może być korzystne w określonych przypadkach.
Kluczowe jest jednak, aby zachować spójność i przejrzystość kodu oraz być świadomym potencjalnych wad i ograniczeń. Własne atrybuty mogą być świetnym narzędziem do lepszego identyfikowania elementów w testach. Jednak powinny być używane z rozwagą i w zgodzie z dobrymi praktykami 😉
Kod i polecenia
Konfiguracja
Minimalna konfiguracja w pliku playwright.config.ts:
import { defineConfig, devices } from "@playwright/test";
export default defineConfig({
testDir: "./tests",
fullyParallel: true,
workers: undefined,
reporter: "html",
use: {
baseURL: "http://localhost:3000",
trace: "on",
testIdAttribute: 'pw-test' // define the testIdAttribute used in page.getByTestId()
},
projects: [
{
name: "chromium",
use: { ...devices["Desktop Chrome"] },
},
],
});
testIdAttribute to domyślna nazwa atrybutu, którego będzie szukał Playwright, to data-testid. W takim przypadku element powinien zawierać atrybut data-testid:
<button data-testid="simple-button" onclick="buttonOnClick()">Click me!</button>
Kod HTML szukanego elementu
Szukany element ma następujący kod HTML (możesz go podejrzeć w przeglądarce po wejściu na testowaną stronę za pomocą opcji inspect):
<button pw-test="simple-button" onclick="buttonOnClick()">Click me!</button>
Zauważ atrybut pw-test o wartości simple-button.
getByTestId() będzie szukał atrybutu pw-test, bo w pliku playwright.config.ts zdefiniowaliśmy:
testIdAttribute: 'pw-test'
Jeśli w konfiguracji nie zdefiniujemy pola testIdAttribute to Playwright w metodzie getByTestId() będzie szukał atrybutu data-testid. W takim przypadku przycisk powinien mieć następujący kod HTML:
<button data-testid="simple-button" onclick="buttonOnClick()">Click me!</button>
Kod
Początkowa zawartość pliku custom-testid-attributes.spec.ts:
import { test, expect } from "@playwright/test";
test.describe("Finding elements using getByTestId and locators", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/practice/simple-elements-custom-attribute.html");
});
test("click the button (using getByTestId)", async ({ page }) => {
// TODO:
});
test("click the button (using locator)", async ({ page }) => {
// TODO:
});
});
Finalna zawartość pliku custom-testid-attributes.spec.ts:
import { test, expect } from "@playwright/test";
test.describe("Finding elements using getByTestId and locators", () => {
test.beforeEach(async ({ page }) => {
await page.goto("/practice/simple-elements-custom-attribute.html");
});
test("click the button (using getByTestId)", async ({ page }) => {
// Arrange:
const buttonTestId = "simple-button";
const resultsTestId = "results";
const expectedMessage = "You clicked the button!";
const buttonLocator = page.getByTestId(buttonTestId);
const resultsLocator = page.getByTestId(resultsTestId);
// Act:
await buttonLocator.click();
// Assert:
await expect(resultsLocator).toHaveText(expectedMessage);
});
test("click the button (using locator)", async ({ page }) => {
// Arrange:
const buttonSelector = "[pw-test='simple-button']";
const resultsSelector = "[pw-test='results']";
const expectedMessage = "You clicked the button!";
const buttonLocator = page.locator(buttonSelector);
const resultsLocator = page.locator(resultsSelector);
// Act:
await buttonLocator.click();
// Assert:
await expect(resultsLocator).toHaveText(expectedMessage);
});
});
Wiele atrybutów data-testid w jednym projekcie
Możemy lokalnie nadpisać wartość ustawienia testIdAttribute:
test.use({ testIdAttribute : "pw-test"});
Czyli taki zapis w custom-testid-attributes.spec.ts:
import { test, expect } from "@playwright/test";
test.describe("Finding elements using getByTestId and locators", () => {
test.use({ testIdAttribute : "pw-test"});
test.beforeEach(async ({ page }) => {
await page.goto("/practice/simple-elements-custom-attribute.html");
});
test("click the button (using getByTestId)", async ({ page }) => {
// Arrange:
const buttonTestId = "simple-button";
const resultsTestId = "results";
const expectedMessage = "You clicked the button!";
const buttonLocator = page.getByTestId(buttonTestId);
const resultsLocator = page.getByTestId(resultsTestId);
// Act:
await buttonLocator.click();
// Assert:
await expect(resultsLocator).toHaveText(expectedMessage);
});
test("click the button (using locator)", async ({ page }) => {
// Arrange:
const buttonSelector = "[pw-test='simple-button']";
const resultsSelector = "[pw-test='results']";
const expectedMessage = "You clicked the button!";
const buttonLocator = page.locator(buttonSelector);
const resultsLocator = page.locator(resultsSelector);
// Act:
await buttonLocator.click();
// Assert:
await expect(resultsLocator).toHaveText(expectedMessage);
});
});
spowoduje, że w obrębie tego test.describe testy będą miały zmienioną konfigurację. Czyli metody page.getByTestId będą szukały atrybutów pw-test.
Dzięki temu możemy mieć w projekcie testy, które bazują na wielu niestandardowych atrybutach.
Jednak korzystanie z
test.use({ testIdAttribute : "pw-test"});
może być niezalecane z kilku powodów:
- Złożoność utrzymania
Jeśli w projekcie jest wiele miejsc, gdzie nadpisujemy wartość
testIdAttribute, może to prowadzić do trudności w śledzeniu, które testy korzystają z jakich atrybutów. W dłuższej perspektywie może to skomplikować utrzymanie testów, zwłaszcza gdy liczba testów rośnie lub gdy do projektu dołączają nowe osoby. - Spójność
Nadpisywanie
testIdAttributelokalnie może prowadzić do niespójności w kodzie testów. Gdy różne testy używają różnych atrybutów, może to wprowadzać zamieszanie. Stosowanie jednego standardowego atrybutu testowego jest bardziej przejrzyste i zrozumiałe dla całego zespołu. - Trudności w debugowaniu
W przypadku problemów z testami, konieczność uwzględniania różnych atrybutów podczas debugowania może być czasochłonna. Debugowanie staje się bardziej skomplikowane, gdy testy w różnych miejscach projektu używają różnych ustawień, co może prowadzić do nieoczekiwanych zachowań, trudnych do zdiagnozowania.
- Problemy z reużywalnością kodu
Jeżeli w projekcie używamy różnych
testIdAttribute, reużywalność kodu testowego może być ograniczona. Testy, które są specyficzne dla danego atrybutu, mogą nie działać poprawnie, jeśli próbujemy je użyć w innym kontekście, gdzie używany jest inny atrybut testowy.
Z tych powodów, zaleca się unikanie nadmiernego używania lokalnych nadpisań testIdAttribute, a zamiast tego stosowanie jednolitego standardu w całym projekcie. Jeśli jednak konieczne jest użycie niestandardowego atrybutu w specyficznych przypadkach, warto to robić świadomie i z pełnym zrozumieniem potencjalnych konsekwencji.
Zewnętrzne linki i zasoby
- Aplikacja do testów: lekcja o pobraniu, instalacji i uruchomieniu
- Strona do testów: [aplikacja GAD musi być uruchomiona lokalnie!] simple-elements-custom-attribute.html
- Oficjalna dokumentacja: Locate by test id
- Why Should You Use data-testid Attributes?
- Why Your Development Team Should Use data-testid Attributes






Cześć, czy testIdAttribute przyjmie tablicę wartości? Mam w projekcie różnie zdefiniowane automation id (chyba nawet 5 wersji znalazłam) i narazie je jako lokator css definiowałam. Ale skoro można je w configu dodać, to chętnie całą tablicę bym dodała, o ile to możliwe. I pytanie czy codgen będzie wyłapywać te niestandardowe selektory czy jednak trzeba je samemu dodawać?
Aktualnie testIdAttribute działa tylko na jednej wartości i nie możemy przekazać tutaj listy.
Jesli w projekcie masz różne automation Ids to masz rózne rozwiazania:
– albo wszystko ujednolicić (dużo pracy)
– albo przygotować metody pomocnicze
Np. jeśli masz atrybuty
my-auto-id="value1"iother-auto-id="value2", to takie metody mogłyby wyglądać:export function formatMyAutoIdSelector(id: string): string { return `[my-auto-id="${id}"]` }oraz
export function formatOtherAutoIdSelector(id: string): string { return `[other-auto-id="${id}"]` }a w testach możesz ich używać jako np:
page.locator(formatMyAutoIdSelector("value1")) page.locator(formatOtherAutoIdSelector("value2"))Plusem tego rozwiązania jest wykorzystanie aktualnych atrybutów elementów i funkcje pomocnicze które redukują duplikację.
Minusem – brak jednolitego podejścia w całej aplikacji.