PrzedsprzedażAkademia AI w testach i jakości oprogramowania: 1 350 zł zamiast 1 750 zł netto do 30 listopada

Startuje przedsprzedaż Akademii AI w testach i jakości oprogramowania: 5 tygodni z Tomaszem Stelmachem, start 11 stycznia 2027. Wyjątkowa cena 1 350 zł netto zamiast 1 750 zł tylko do 30 listopada.

zostało 31 dniZobaczcie program i zapiszcie się
Wzorce projektowe w Selenium WebDriver: Page Object, Factory, Strategy z kodem Java
Czas czytania: 16 minut

Wzorce projektowe w Selenium WebDriver to sprawdzone sposoby układania kodu testów: gdzie trzymać selektory, kto tworzy przeglądarkę, jak budować dane testowe i jak dodać logi bez zaśmiecania testów. Automatyzacja testów Selenium bez wzorców działa świetnie przez pierwsze dwadzieścia testów, a potem każda zmiana w UI oznacza poprawki w trzydziestu plikach.

W tym artykule pokazujemy siedem wzorców na jednym małym frameworku w Javie, a nie tylko je opisujemy. Cały kod skompilowaliśmy i uruchomiliśmy 11.10.2026 na Selenium 4.51.0, JUnit 6.1.3 i Javie 25: oba testy przeszły w Chrome 154 w trybie headless. Jeśli Wasza przygoda z Selenium dopiero się zaczyna, najpierw przeczytajcie przewodnik Selenium: co to jest i od czego zacząć, bo tu zakładamy, że pierwszy test macie już za sobą.

Po co wzorce projektowe w Selenium i automatyzacji testów?

Test UI dotyka trzech rzeczy, które zmieniają się w różnym tempie: interfejsu, konfiguracji uruchomienia i danych. Gdy wszystkie trzy siedzą w jednej metodzie testowej, każda zmiana w którejkolwiek z nich wymaga edycji testu. Wzorce rozdzielają te odpowiedzialności, tak by zmiana selektora dotykała jednej klasy, a zmiana przeglądarki jednej linijki konfiguracji.

Termin „wzorzec projektowy” spopularyzowała książka „Design Patterns” tak zwanej Bandy Czworga z 1994 roku, która według jej opisu w Wikipedii katalogowała 23 klasyczne wzorce. W testach używamy kilku z nich (Factory, Strategy, Builder, Decorator) oraz wzorców typowych dla automatyzacji UI, takich jak Page Object, który Martin Fowler opisał na swoim blogu 10.09.2013. Poniżej mapa, która pokazuje, jaki problem rozwiązuje każdy z nich.

Wzorzec Jaki problem rozwiązuje Klasa w przykładzie
Page Object selektory i kliknięcia rozsiane po testach LoginPage, DashboardPage
Page Component Object ten sam nagłówek, modal albo tabela na wielu stronach HeaderComponent
Factory testy wiedzą, jaką przeglądarkę uruchamiają DriverFactory
ThreadLocal zamiast Singletona testy równoległe dzielą jedną przeglądarkę DriverManager
Builder dane testowe tworzone dziesięcioma parametrami konstruktora User.Builder
Strategy różne sposoby wykonania tego samego kroku, na przykład logowania LoginStrategy, UiLogin
Decorator logi i zrzuty ekranu wklejane ręcznie do każdego testu LoggingListener z EventFiringDecorator

Źródło: opracowanie Quality Island na podstawie dokumentacji Selenium i katalogu wzorców GoF

Struktura projektu, na którym pracujemy w całym artykule, wygląda tak:

src/test/java/pl/qi/wzorce/ Browser.java enum przeglądarek DriverFactory.java Factory: tworzy WebDrivera DriverManager.java ThreadLocal: jeden driver na wątek LoginPage.java Page Object DashboardPage.java Page Object złożony z komponentów HeaderComponent.java Page Component Object User.java Builder danych testowych (record) LoginStrategy.java Strategy: interfejs logowania UiLogin.java Strategy: logowanie przez UI LoggingListener.java Decorator: logi zdarzeń LogowanieTest.java test JUnit 6 src/test/resources/aplikacja.html

Jak wygląda Page Object Model w Selenium 4?

Page Object to klasa, która reprezentuje stronę albo jej fragment i udostępnia testom zachowania, a nie elementy. Dokumentacja Selenium podaje dwie główne korzyści: oddzielenie kodu testu od kodu specyficznego dla strony oraz jedno miejsce na usługi i operacje danej strony. Gdy zmieni się selektor przycisku logowania, poprawiacie jedną stałą w LoginPage, a nie dwadzieścia testów.

import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; import org.openqa.selenium.support.ui.ExpectedConditions; import org.openqa.selenium.support.ui.WebDriverWait; import java.time.Duration; public class LoginPage { private static final By EMAIL = By.cssSelector("[data-testid='login-email']"); private static final By PASSWORD = By.cssSelector("[data-testid='login-password']"); private static final By SUBMIT = By.cssSelector("[data-testid='login-submit']"); private static final By ERROR = By.cssSelector("[data-testid='login-error']"); private final WebDriver driver; private final WebDriverWait wait; public LoginPage(WebDriver driver) { this.driver = driver; this.wait = new WebDriverWait(driver, Duration.ofSeconds(10)); } public DashboardPage loginAs(User user) { submit(user); return new DashboardPage(driver); } public String loginExpectingError(User user) { submit(user); return wait.until(ExpectedConditions.visibilityOfElementLocated(ERROR)).getText(); } private void submit(User user) { driver.findElement(EMAIL).sendKeys(user.email()); driver.findElement(PASSWORD).sendKeys(user.password()); driver.findElement(SUBMIT).click(); } }

Zwróćcie uwagę na cztery decyzje w tej klasie:

  • Selektory są prywatnymi stałymi typu By. Test nie wie, że pole e-mail ma data-testid, i nie musi wiedzieć
  • Metody mówią językiem użytkownika: loginAs i loginExpectingError, a nie clickButton czy fillInput
  • loginAs zwraca następną stronę, DashboardPage. Test czyta się jak scenariusz, a kompilator pilnuje, na której stronie jesteście
  • W klasie nie ma asercji. Dokumentacja Selenium mówi wprost, że obiekty stron nie powinny same weryfikować wyników, bo to zadanie testu

Mało kto o tym wie

Wiele kursów nadal uczy Page Object z adnotacjami @FindBy i klasą PageFactory. Tymczasem dokumentacja Selenium trzyma opis PageFactory w dziale „Legacy”, w części o Selenium 2, a wszystkie aktualne przykłady Page Object budują lokatory ze zwykłych obiektów By, jak w kodzie wyżej. Leniwie inicjowane proxy z PageFactory potrafią przy tym zwracać nieaktualne elementy na stronach budowanych dynamicznie. PageFactory nie jest zakazane, po prostu jest trochę jak faks w biurze: działa, ale nikt nowy już go nie zamawia.

Konstruktor DashboardPage poniżej czeka na nagłówek przez WebDriverWait z Duration. To dobra praktyka z dokumentacji Selenium: obiekt strony w konstruktorze sprawdza, że strona jest gotowa, i rzuca wyjątek, gdy nie jest. Dlaczego czekamy jawnie, a nie przez implicit wait, wyjaśniamy w artykule Explicit Wait vs Implicit Wait, a jak dobierać stabilne selektory, w tekście selektory w Selenium.

Kiedy wydzielić Page Component Object?

Nagłówek z nazwą użytkownika, menu, modal, tabela z paginacją: te elementy powtarzają się na wielu stronach. Jeśli każdy Page Object obsługuje je po swojemu, wracacie do duplikacji, przed którą Page Object miał chronić. Dokumentacja Selenium nazywa rozwiązanie Page Component Object: obiekt reprezentuje komponent, a nie całą stronę, i szuka elementów tylko wewnątrz swojego korzenia.

public class HeaderComponent { private final WebElement root; public HeaderComponent(WebElement root) { this.root = root; } public String userName() { return root.findElement(By.cssSelector("[data-testid='user-name']")).getText(); } public boolean canLogout() { return root.findElement(By.cssSelector("[data-testid='logout']")).isDisplayed(); } }
public class DashboardPage { private final HeaderComponent header; public DashboardPage(WebDriver driver) { var root = new WebDriverWait(driver, Duration.ofSeconds(10)) .until(ExpectedConditions.visibilityOfElementLocated(By.cssSelector("[data-testid='header']"))); this.header = new HeaderComponent(root); } public HeaderComponent header() { return header; } }

HeaderComponent dostaje w konstruktorze element korzenia i wywołuje root.findElement, a nie driver.findElement. Dzięki temu selektor user-name nie trafi przypadkiem w identyczny element w stopce. DashboardPage składa się z komponentów, więc gdy panel dostanie drugi komponent, na przykład listę zamówień, dopisujecie jedno pole. Gdy chcecie wspólną klasę bazową dla stron, zobaczcie, jak budujemy BasePage w lekcji o dziedziczeniu w Javie.

Jak Factory tworzy WebDrivera dla każdej przeglądarki?

Factory oddziela tworzenie obiektu od miejsca, w którym się go używa. Test prosi o przeglądarkę, a fabryka decyduje, jakie opcje ustawić. W Javie 25 zapis jest krótki dzięki wyrażeniu switch z yield, które sprawdza kompletność: dodacie do enuma nową przeglądarkę, a kompilator wskaże miejsce, które trzeba uzupełnić.

public enum Browser { CHROME, FIREFOX, EDGE }
import org.openqa.selenium.WebDriver; import org.openqa.selenium.chrome.ChromeDriver; import org.openqa.selenium.chrome.ChromeOptions; import org.openqa.selenium.edge.EdgeDriver; import org.openqa.selenium.edge.EdgeOptions; import org.openqa.selenium.firefox.FirefoxDriver; import org.openqa.selenium.firefox.FirefoxOptions; public final class DriverFactory { private DriverFactory() { } public static WebDriver create(Browser browser, boolean headless) { return switch (browser) { case CHROME -> { ChromeOptions o = new ChromeOptions(); if (headless) o.addArguments("--headless=new"); yield new ChromeDriver(o); } case FIREFOX -> { FirefoxOptions o = new FirefoxOptions(); if (headless) o.addArguments("-headless"); yield new FirefoxDriver(o); } case EDGE -> { EdgeOptions o = new EdgeOptions(); if (headless) o.addArguments("--headless=new"); yield new EdgeDriver(o); } }; } }

W fabryce nie ma System.setProperty ze ścieżką do sterownika. Od Selenium 4.6 sterowniki pobiera Selenium Manager, więc ten krok z dawnych tutoriali można usunąć. Kolejny przypadek fabryki to zdalne uruchomienie: gdy zmienna środowiskowa wskazuje adres Selenium Grid, fabryka zwraca new RemoteWebDriver(url, opcje) zamiast lokalnego sterownika, a testy nie zmieniają się ani o linijkę. Uruchamianie testów w pipeline z takim przełącznikiem budujemy w ramach usługi budowa procesów CI/CD.

Dlaczego ThreadLocal zamiast Singletona dla WebDrivera?

Singleton, czyli jedna statyczna instancja WebDrivera dla całego pakietu, wygląda kusząco: nie trzeba przekazywać drivera między klasami. Problem pojawia się w dniu, w którym włączycie testy równoległe. Wszystkie wątki dostają tę samą przeglądarkę i zaczynają klikać jednocześnie w jednym oknie. Singleton dla WebDrivera to wzorzec, który nie skaluje się dokładnie w tym momencie, w którym framework zaczyna być potrzebny.

public final class DriverManager { private static final ThreadLocal<WebDriver> DRIVER = new ThreadLocal<>(); private DriverManager() { } public static void start(WebDriver driver) { DRIVER.set(driver); } public static WebDriver get() { return DRIVER.get(); } public static void stop() { WebDriver d = DRIVER.get(); if (d != null) { d.quit(); DRIVER.remove(); } } }

ThreadLocal daje każdemu wątkowi własną przeglądarkę przy tym samym wygodnym dostępie przez DriverManager.get(). Metoda stop() woła remove(), żeby pula wątków nie zostawiła referencji do zamkniętej przeglądarki dla kolejnego testu. Taki układ działa z równoległym uruchamianiem w JUnit 6 i z Selenium Grid, gdzie każdy wątek dostaje osobną sesję na innym węźle.

Jak Builder i Strategy porządkują dane i logowanie?

Dane testowe to druga po selektorach najczęstsza przyczyna zmian w testach. Konstruktor z pięcioma parametrami typu String zaprasza do pomyłki kolejności, a każdy nowy atrybut użytkownika wymaga poprawy wszystkich wywołań. Builder ustawia rozsądne wartości domyślne, a test nadpisuje tylko to, co jest dla niego istotne.

public record User(String email, String password, String role) { public static Builder builder() { return new Builder(); } public static final class Builder { private String email = "tester@example.com"; private String password = "Tajne123!"; private String role = "USER"; public Builder email(String email) { this.email = email; return this; } public Builder password(String password) { this.password = password; return this; } public Builder admin() { this.role = "ADMIN"; return this; } public User build() { return new User(email, password, role); } } }

W teście błędnego logowania wystarczy User.builder().password("zle").build() i od razu widać, czym ten przypadek różni się od szczęśliwej ścieżki. Rekord Javy daje niezmienność i gotowe equals, więc dane nie zmienią się w połowie testu. Więcej o budowaniu obiektów danych testowych piszemy w lekcji o konstruktorach w Javie.

Strategy pozwala wymienić sposób wykonania kroku bez zmiany testu. Klasyczny przykład to logowanie: test logowania musi przejść przez formularz, ale test koszyka nie musi. Wspólny interfejs i dwie implementacje dają wybór:

public interface LoginStrategy { DashboardPage login(WebDriver driver, String url, User user); }
public class UiLogin implements LoginStrategy { @Override public DashboardPage login(WebDriver driver, String url, User user) { driver.get(url); return new LoginPage(driver).loginAs(user); } }

Druga implementacja, ApiLogin, pobiera token z endpointu logowania, ustawia ciasteczko sesji przez driver.manage().addCookie() i otwiera od razu panel. Nie pokazujemy jej w kodzie, bo zależy od API Waszej aplikacji. Z naszego doświadczenia to jedna z najtańszych optymalizacji pakietu UI: logowanie przez API trwa ułamek czasu formularza, a test formularza zostaje jeden, zamiast powtarzać się na początku każdego scenariusza. Gdy macie już testy API, warto połączyć je z Waszymi testami UI, co robimy w ramach usługi automatyzacja testów API.

Jak dodać logi i zrzuty ekranu wzorcem Decorator?

Logi kroków, zrzuty ekranu przy błędzie, pomiar czasu akcji: to wszystko przydaje się w raporcie, ale wklejone do testów zasłania ich logikę. Wzorzec Decorator owija obiekt w dodatkowe zachowanie bez zmiany jego interfejsu. Selenium 4 ma to wbudowane: EventFiringDecorator zwraca udekorowany WebDriver, który wywołuje Wasz WebDriverListener przed i po każdej akcji.

public class LoggingListener implements WebDriverListener { final List<String> log = new ArrayList<>(); @Override public void beforeClick(WebElement element) { log.add("klik: " + element.getDomAttribute("data-testid")); } @Override public void afterGet(WebDriver driver, String url) { log.add("otwarto: " + driver.getTitle()); } }

Listener w przykładzie zapisuje każde kliknięcie z identyfikatorem elementu i tytuł każdej otwartej strony. W prawdziwym frameworku w tym samym miejscu robicie zrzut ekranu w metodzie onError i dołączacie go do raportu. Test o tym nie wie, a każdy nowy test dostaje logi za darmo.

Jak wygląda test, który składa wzorce w całość?

Test poniżej używa wszystkich wzorców naraz: fabryka tworzy przeglądarkę, dekorator dokłada logi, menedżer trzyma driver dla wątku, strategia loguje, builder tworzy użytkownika, a obiekty stron ukrywają selektory.

import org.junit.jupiter.api.*; import org.openqa.selenium.WebDriver; import org.openqa.selenium.support.events.EventFiringDecorator; import java.nio.file.Path; import static org.junit.jupiter.api.Assertions.*; class LogowanieTest { private static final String URL = Path.of("src/test/resources/aplikacja.html").toUri().toString(); private final LoggingListener listener = new LoggingListener(); private final LoginStrategy login = new UiLogin(); @BeforeEach void start() { WebDriver surowy = DriverFactory.create(Browser.CHROME, true); DriverManager.start(new EventFiringDecorator<WebDriver>(listener).decorate(surowy)); } @AfterEach void stop() { DriverManager.stop(); } @Test void poprawneLogowaniePokazujeUzytkownika() { User user = User.builder().email("anna@example.com").build(); DashboardPage panel = login.login(DriverManager.get(), URL, user); assertEquals("anna@example.com", panel.header().userName()); assertTrue(panel.header().canLogout()); assertTrue(listener.log.contains("klik: login-submit")); } @Test void bledneHasloPokazujeKomunikat() { DriverManager.get().get(URL); String blad = new LoginPage(DriverManager.get()) .loginExpectingError(User.builder().password("zle").build()); assertEquals("Błędny e-mail lub hasło", blad); } }

Metoda testowa ma trzy linijki przygotowania i trzy asercje. Nie ma w niej żadnego selektora, żadnego sleep ani konfiguracji przeglądarki. Gdy zmieni się formularz logowania w Waszej aplikacji, test się nie zmieni, zmieni się LoginPage. O tym, jak pisać asercje, które mówią coś o wyniku biznesowym, piszemy w tekście o asercjach w testach automatycznych.

Wynik uruchomienia przykładowego frameworka, 11.10.2026

15 z 15

testów z trzech artykułów o Selenium przeszło, w tym 2 z tego frameworka

4,7 s

trwała cała klasa LogowanieTest z dwiema przeglądarkami headless

11

plików w projekcie, żaden nie przekracza 60 linijek

0

selektorów i wywołań sleep w metodach testowych

Źródło: dane własne Quality Island, Maven 3.9.12, Selenium 4.51.0, JUnit 6.1.3, Java 25, Chrome 154

Kiedy wzorzec szkodzi zamiast pomagać?

Każdy wzorzec ma koszt: dodatkową klasę, warstwę abstrakcji, rzecz do wytłumaczenia nowej osobie. W Waszym małym projekcie z dziesięcioma testami Page Object w zupełności wystarczy, a Strategy z jedną implementacją to ozdobnik. Kilka sygnałów, że przesadziliście:

Przerost

Page Object na 800 linijek

Klasa obsługuje cały moduł z setką elementów. Rozbijcie ją na komponenty.

Przerost

Abstrakcja nad abstrakcją

Wrapper na WebElement, który tylko woła click. Usuńcie go, Selenium ma czytelne API.

Niedobór

Asercje w Page Object

Strona sama sprawdza wynik, więc test nie mówi, co weryfikuje. Przenieście asercje do testu.

Niedobór

Statyczny driver

Singleton blokuje równoległość. Zamieńcie go na ThreadLocal, zanim pakiet urośnie.

W bardzo dużych pakietach z wieloma rolami użytkowników niektóre zespoły przechodzą z Page Object na wzorzec Screenplay, w którym test opisuje aktorów, ich zdolności i zadania. Daje to świetną czytelność scenariuszy biznesowych, ale wymaga więcej kodu i dyscypliny. Wybierajcie go, gdy Wasze Page Objecty puchną od metod zależnych od roli, a nie dlatego, że brzmi nowocześnie. Szerszy plan wdrażania automatyzacji, od pierwszych testów po skalowanie, opisuje Strefa QA w tekście plan wdrożenia automatyzacji testów na 6 miesięcy.

Jak sprawdzić, że framework Selenium działa?

Dobry framework widać w liczbach, nie w diagramie klas. Według wpisu na Google Testing Blog z 2016 roku około 1,5% uruchomień testów w Google daje wynik niestabilny, a prawie 16% testów ma jakiś poziom niestabilności, więc nawet najlepsze zespoły mierzą ten wskaźnik stale. U naszych klientów patrzymy na pięć miar:

  • Odsetek niestabilnych uruchomień: testy, które przy tym samym kodzie raz przechodzą, raz nie
  • Czas pełnej regresji w pipeline i jego trend z miesiąca na miesiąc
  • Liczba plików zmienianych przy zmianie UI: przy dobrym Page Object to jeden, najwyżej dwa
  • Czas analizy czerwonego buildu: logi i zrzuty z dekoratora skracają go najbardziej
  • Błędy wykryte przed produkcją względem tych, które zgłosili użytkownicy

W projekcie dla Argos automatyzacja testów i uporządkowanie procesów QA skróciły regresję przed releasem z 5 dni do 10 godzin, a błędów krytycznych na produkcji było o 46% mniej, co opisujemy w case study Argos. Według danych Quality Island nasze zespoły napisały ponad 450 000 testów automatycznych i to doświadczenie stoi za wzorcami z tego artykułu.

Wzorce z tego artykułu, od Page Object po równoległe uruchomienia, ćwiczymy krok po kroku na szkoleniu Automatyzacja testów Selenium WebDriver, a podstawy języka, na których stoją, na szkoleniu Programowanie w języku Java dla testerów. Gdy Wasz framework urósł bez architektury, pomoże Wam doradztwo w automatyzacji testów: przegląd kodu, plan refaktoryzacji i wsparcie zespołu przy jej wdrożeniu.

Co zabrać z tego artykułu

01Page Object ukrywa selektory i udostępnia zachowania. Nie zawiera asercji, a metody zwracają kolejne strony.

02Powtarzalne fragmenty UI modelujcie jako Page Component Object, który szuka elementów tylko w swoim korzeniu.

03Factory tworzy przeglądarkę i przełącza na Selenium Grid bez zmiany testów. Selenium Manager zastępuje System.setProperty.

04Zamiast Singletona używajcie ThreadLocal, inaczej testy równoległe będą klikać w jednym oknie.

05Builder daje czytelne dane testowe, Strategy pozwala logować przez UI albo przez API.

06Logi i zrzuty ekranu dodajcie dekoratorem EventFiringDecorator, a nie w każdym teście.

Źródło: dokumentacja Selenium 2026, framework przykładowy Quality Island z 11.10.2026

Wasz framework Selenium urósł bez architektury i każda zmiana w UI boli? Zrobimy przegląd i plan refaktoryzacji.

Doradztwo w automatyzacji


Powiązane na blogu Quality Island

Pełna lista źródeł

Co o tym sądzisz?

Dodaj komentarz

Bądź na bieżąco
Strefa QA, portal z eksperckimi artykułami o jakości oprogramowania
Bądź na bieżąco
Cyberbezpieczeństwo KSC i NIS2 online: zarząd, pracownicy, zespoły IT i pakiety dla organizacji

od 199,00 zł netto

Kompetencje cyfrowe i AI dla pracowników 50+
Kompetencje cyfrowe i AI dla pracowników 50+: szkolenie praktyczne

1 000,00 zł

07.12.26, 18.01.27, 15.02.27, 15.03.27, 19.04.27
1 dzień
Europejski Akt o Dostępności w praktyce
Europejski Akt o Dostępności w praktyce: wymagania ustawy dla e-commerce, banków i usług cyfrowych

1 290,00 zł

01.12.26, 19.01.27, 16.02.27, 16.03.27, 20.04.27
1 dzień
Popularne artykuły
Język Gherkin: co to jest i jak go używać w testowaniu oprogramowania
Smoke test: co to jest, kiedy go uruchamiać i czym różni się od sanity testu
Jak zostać testerem oprogramowania: ścieżka krok po kroku bez dyplomu informatyka
Najnowsze artykuły
AI w testowaniu oprogramowania: szablon promptu, tabela decyzyjna i liczby z badań
Wdrożenie AI w zespole QA: plan 90 dni, koszt pilotażu i karta mierników
NIS2 a dostawcy oprogramowania: jakich testów zażąda od Was klient objęty KSC
Popularne kategorie

Grupa marek jakości oprogramowania

Jakość oprogramowania w Polsce ma swój adres

Quality Island