Gdy formularz przyjmuje błędny adres e-mail, test UI nie rozpoznaje zmiennego komunikatu albo log zawiera setki podobnych identyfikatorów, zwykłe wyszukiwanie tekstu szybko przestaje wystarczać. Wyrażenie regularne pozwala opisać taki tekst za pomocą wzorca, a potem wykorzystać go w walidacji danych, skryptach i automatyzacji testów. Poniżej pokazuję składnię, praktyczne przykłady, sposób użycia w testach oraz ograniczenia, o których łatwo zapomnieć.
Wzorce tekstowe pomagają pisać stabilniejsze i bardziej precyzyjne testy
- Składnia opiera się między innymi na klasach znaków, kwantyfikatorach, grupach i kotwicach.
- Automatyzacja UI wykorzystuje regex do sprawdzania dynamicznych komunikatów, adresów URL i treści elementów.
- Kotwice ^ i $ odróżniają dopasowanie całej wartości od znalezienia fragmentu tekstu.
- Testy negatywne są równie ważne jak poprawne przykłady, bo ujawniają zbyt szerokie wzorce.
- Wydajność i przenośność zależą od silnika regex oraz konstrukcji wzorca.
Jak działa wzorzec i z czego się składa
Regex nie opisuje jednego konkretnego tekstu, lecz zbiór tekstów spełniających określone reguły. Wzorzec `INV-\d{4}` pasuje do `INV-2026`, ale nie do `INV-26` ani `INV-ABCD`. Dzięki temu mogę sprawdzać strukturę danych bez wpisywania każdej możliwej wartości osobno.
Podstawowe znaki mają zwykle podobne znaczenie w JavaScript, Pythonie, Javie czy narzędziach opartych na PCRE. Różnice pojawiają się przy bardziej zaawansowanych funkcjach, dlatego przed przeniesieniem wzorca do innego języka sprawdzam dokumentację konkretnego silnika.
| Element | Znaczenie | Przykład |
|---|---|---|
\d |
Jedna cyfra |
\d{4} dopasuje cztery cyfry |
\w |
Znak słowa, zwykle litera, cyfra lub podkreślenie |
\w+ znajdzie ciąg znaków |
\s |
Biały znak, na przykład spacja lub tabulator |
\s+ dopasuje jedną lub więcej spacji |
[A-Z] |
Jedna wielka litera z podanego zakresu |
[A-Z]{2} pasuje do kodu PL
|
[^0-9] |
Znak inny niż cyfra | Przydatne przy wykrywaniu separatorów |
. |
Dowolny znak, zwykle poza znakiem nowej linii |
ab. pasuje do abc i ab1
|
| |
Alternatywa, czyli logiczne „lub” |
GET|POST dopasuje jedną z metod |
Kwantyfikatory określają liczbę powtórzeń
Kwantyfikator mówi, ile razy może wystąpić poprzedni znak, grupa albo klasa znaków. Najczęściej używam +, gdy element musi pojawić się przynajmniej raz, oraz ?, gdy jest opcjonalny.
| Symbol | Znaczenie | Przykład |
|---|---|---|
* |
Zero lub więcej powtórzeń | a* |
+ |
Jedno lub więcej powtórzeń | \d+ |
? |
Zero lub jedno wystąpienie |
https? pasuje do HTTP i HTTPS |
{n} |
Dokładnie n powtórzeń | \d{4} |
{n,m} |
Od n do m powtórzeń | \d{2,4} |
Domyślnie kwantyfikatory są zachłanne, czyli próbują dopasować możliwie długi fragment. Dodanie znaku zapytania zmienia zachowanie na zachłanne minimalnie, na przykład .*?. W testach używam tej konstrukcji ostrożnie, bo często lepszy efekt daje precyzyjna klasa znaków niż „dowolny tekst”.
Jak budować wzorce, które nie łapią zbyt dużo
Najczęstszy błąd polega na napisaniu wzorca, który przechodzi dla poprawnego przykładu, ale akceptuje też dane błędne. Samo \d+ znajdzie cyfry w środku zdania. Jeśli test ma sprawdzić, czy cała wartość jest numerem zamówienia, potrzebuję kotwic początku i końca.
^ORD-\d{6}$
W tym przypadku ciąg musi zaczynać się od `ORD-`, zawierać dokładnie sześć cyfr i kończyć się zaraz po nich. W zależności od silnika ^ i $ mogą działać także względem poszczególnych linii, gdy włączony jest tryb wieloliniowy. Dlatego przy walidacji całego pola sprawdzam, czy używany tryb nie zmienia znaczenia kotwic.
Grupy poprawiają czytelność i pozwalają odzyskać fragment
Nawiasy grupują elementy i pozwalają zastosować kwantyfikator do całego wyrażenia. Wzorzec (PL|DE)-\d{5} opisuje kod kraju oraz pięć cyfr. Jeśli nie potrzebuję później odczytywać dopasowanego fragmentu, stosuję grupę nieprzechwytującą (?:PL|DE), ponieważ jasno komunikuje jej rolę.
^(?:PL|DE)-\d{5}$
Grupy przechwytujące są przydatne wtedy, gdy po dopasowaniu chcę pobrać konkretną część, na przykład numer wersji z tekstu `release-3.14`. Nie używam ich jednak automatycznie w każdym miejscu. W rozbudowanych wzorcach niepotrzebne przechwytywanie utrudnia odczyt wyników i może zwiększać koszt dopasowania.
Przykłady praktycznych wzorców
Poniższe przykłady traktuję jako punkty wyjścia, a nie gotowe reguły dla każdej aplikacji. Walidacja numeru telefonu, adresu e-mail czy daty zależy od przyjętego formatu, dlatego test powinien dokumentować, co dokładnie uznajemy za poprawną wartość.
| Cel | Wzorzec | Co sprawdza |
|---|---|---|
| Adres e-mail w prostym formacie | ^[^\s@]+@[^\s@]+\.[^\s@]+$ |
Tekst przed i po znaku @ oraz kropkę w domenie |
| Data RRRR-MM-DD | ^\d{4}-\d{2}-\d{2}$ |
Układ cyfr i separatorów, ale nie poprawność kalendarzową |
| Wersja semantyczna | ^v\d+\.\d+\.\d+$ |
Format taki jak v2.10.1
|
| Identyfikator użytkownika | ^[a-z][a-z0-9_]{2,15}$ |
Małą literę na początku i długość od 3 do 16 znaków |
Przy dacie regex sprawdza wyłącznie kształt tekstu. Wartość `2026-99-99` przejdzie taki test, choć nie istnieje w kalendarzu. To dobry przykład granicy tej techniki: wzorzec nie zastępuje logiki domenowej.
Jak wykorzystać regex w automatyzacji testów
W automatyzacji testów wzorce najlepiej sprawdzają się tam, gdzie tekst jest częściowo zmienny, ale jego struktura pozostaje stała. Może to być komunikat zawierający numer zgłoszenia, adres URL po przekierowaniu, nazwa pliku raportu albo status z identyfikatorem sesji.
Testy interfejsu z dynamicznym tekstem
W Playwright mogę przekazać obiekt RegExp do lokatora tekstowego lub asercji. Dzięki temu test nie zależy od konkretnego numeru zamówienia, ale nadal wymaga właściwej treści i formatu.
await expect(
page.getByText(/Zamówienie nr ORD-\d{6} zostało utworzone/)
).toBeVisible();
Jeśli element jest interaktywny, preferuję lokator oparty na roli, etykiecie albo stabilnym atrybucie testowym. Regex powinien doprecyzować tekst, a nie zastępować dobry wybór elementu. Zbyt szeroki lokator typu .* może dopasować kilka elementów i sprawić, że test przestanie sygnalizować realny błąd.
Sprawdzanie adresu URL i komunikatów
Po zalogowaniu aplikacja może przekierować użytkownika do adresu z dynamicznym identyfikatorem. Zamiast porównywać cały URL jako stały tekst, sprawdzam jego niezmienną część oraz oczekiwany format parametru.
await expect(page).toHaveURL(
/\/konto\/uzytkownik\/\d+\/ustawienia$/
);
Podobnie można zweryfikować komunikat z czasem wykonania operacji. W takim teście warto ograniczyć dopasowanie do konkretnego elementu, aby przypadkowy tekst w stopce lub ukrytym kontenerze nie dał fałszywie pozytywnego wyniku.
await expect(page.locator('[data-testid="status"]'))
.toHaveText(/Raport wygenerowany w \d+ sekund/);
Testy API i danych wyjściowych
W testach API regex przydaje się do lekkiej kontroli identyfikatorów, tokenów technicznych i komunikatów logowanych przez usługę. Nie próbuję nim jednak sprawdzać całego JSON-a. Do struktury danych lepiej użyć asercji pól, schematu JSON albo walidatora typów, a wzorzec zostawić dla pojedynczych wartości tekstowych.
expect(response.body.orderId).toMatch(/^ORD-\d{6}$/);
expect(response.body.status).toMatch(/^(new|paid|cancelled)$/);
W Pythonie analogiczny test może korzystać z modułu re. Gdy wzorzec jest używany wielokrotnie, kompiluję go raz i przekazuję do kolejnych sprawdzeń. To poprawia czytelność, a przy większej liczbie operacji pozwala uniknąć powtarzania przygotowania tego samego wzorca.
import re
order_id = re.compile(r"^ORD-\d{6}$")
assert order_id.fullmatch("ORD-202601")
assert not order_id.fullmatch("ORD-26")
W Pythonie surowy łańcuch oznaczony prefiksem r ogranicza problem podwójnego zapisu ukośników. W JavaScript zapis wygląda inaczej, bo wzorzec często umieszcza się między ukośnikami, na przykład /^ORD-\d{6}$/. To drobny szczegół, który regularnie powoduje błędy przy kopiowaniu wzorców między językami.
Jak testować sam wzorzec i unikać fałszywych wyników
Regex powinien mieć własny zestaw przypadków testowych. W praktyce przygotowuję co najmniej trzy grupy danych: przykłady poprawne, przykłady błędne oraz przypadki graniczne. Sama informacja, że jeden poprawny tekst przeszedł, nie mówi jeszcze, czy wzorzec jest wystarczająco precyzyjny.
| Grupa | Przykład dla identyfikatora | Cel |
|---|---|---|
| Poprawne | usr_anna7 |
Potwierdzenie podstawowego scenariusza |
| Niepoprawne | usr-anna7 |
Wykrycie niedozwolonego separatora |
| Granica długości | 2, 3, 16 i 17 znaków | Sprawdzenie limitów kwantyfikatora |
| Znaki narodowe | żółw_1 |
Ustalenie zachowania dla Unicode |
| Puste dane | Pusty ciąg lub sama spacja | Ochrona przed przypadkowym dopasowaniem |
W testach negatywnych często zaczynam od wartości, która różni się od poprawnej tylko jednym znakiem. Takie przypadki szybko pokazują, czy reguła wymaga kotwic, czy klasa znaków nie jest zbyt szeroka i czy opcjonalny fragment nie pozwala na pustą wartość.
Najczęstsze pułapki
- Brak kotwic sprawia, że wzorzec może znaleźć poprawny fragment w niepoprawnym tekście.
-
Kropka bez ucieczki oznacza dowolny znak, a nie dosłowną kropkę. Do adresu lub wersji użyj
\.. -
Mylenie
\dz pełnym formatem liczby prowadzi do akceptowania cyfr w przypadkowych miejscach. -
Założenie, że
\wzawsze obejmuje polskie znaki może dać inny wynik w zależności od języka i trybu Unicode. -
Zachłanne
.*potrafi pochłonąć większy fragment niż zakładano i utrudnia diagnozę błędu. - Bezpośrednie wstawianie danych użytkownika do wzorca może zmienić jego znaczenie. Takie wartości trzeba odpowiednio escapować.
Osobno sprawdzam też wielkość liter, znaki nowej linii i kodowanie. Flaga i włącza dopasowanie bez rozróżniania wielkości liter, a m zmienia działanie kotwic dla wielu linii. Nie dodaję flag „na wszelki wypadek”, bo każda zmienia kontrakt testu.
Przeczytaj również: XPath text - Stabilne selektory w testach automatycznych
Wydajność i ryzyko złożonych wzorców
Wzorce zawierające wiele zagnieżdżonych, zachłannych fragmentów, na przykład (a+)+ lub kilka sąsiadujących konstrukcji .*, mogą działać bardzo wolno dla odpowiednio przygotowanych danych. W aplikacji dostępnej publicznie jest to także potencjalne ryzyko typu ReDoS, czyli blokowania procesu przez kosztowne dopasowanie.
W testach zaczynam od prostszych klas znaków i ograniczonych zakresów, na przykład [A-Z0-9]{1,32}, zamiast używać nieograniczonego .*. Jeżeli wzorzec ma analizować duże logi albo dane od użytkownika, sprawdzam jego zachowanie dla długiego wejścia i nietypowych sekwencji, nie tylko dla krótkich przykładów.
Kiedy regex nie jest najlepszym narzędziem
Ta technika jest świetna do rozpoznawania lokalnych reguł tekstu. Gorzej radzi sobie z danymi, które mają zagnieżdżoną strukturę, zależności między polami albo skomplikowaną logikę biznesową. Próba opisania całego HTML-a, JSON-a czy warunków faktury jednym wzorcem zwykle kończy się kodem trudnym do utrzymania.
| Problem | Lepsze rozwiązanie |
|---|---|
| Sprawdzenie pól w JSON-ie | Asercje właściwości lub walidacja schematu |
| Obliczenie poprawności daty | Parser daty i reguły kalendarzowe |
| Wybór elementu UI | Rola, etykieta, stabilny identyfikator testowy |
| Analiza HTML-a | Parser DOM zamiast wielkiego wzorca |
| Sprawdzenie zależności biznesowych | Jawna logika w kodzie testu |
Moja praktyczna zasada jest prosta: regex powinien sprawdzać kształt pojedynczej wartości, a nie udawać parsera całego dokumentu. Jeśli wzorzec wymaga długiego komentarza, kilku wyjątków i wielu negatywnych lookaheadów, najczęściej warto rozbić walidację na mniejsze, nazwane reguły.
Trzeba też pamiętać o przenośności. W JavaScript, Pythonie i innych środowiskach podstawowe konstrukcje są podobne, ale obsługa lookbehind, flag, grup nazwanych czy właściwości Unicode może się różnić. Przed użyciem zaawansowanej składni uruchamiam ten sam zestaw przypadków w docelowym silniku, a nie w przypadkowym testerze online.
Jak wdrożyć wzorce w projekcie bez utraty czytelności
Najpierw zapisuję regułę słownie, na przykład „ma zaczynać się od prefiksu, zawierać sześć cyfr i nie mieć dodatkowych znaków”. Dopiero potem tworzę wzorzec. Taka kolejność ogranicza pokusę kopiowania przypadkowej konstrukcji znalezionej w dokumentacji bez zrozumienia jej zakresu.
- Zdefiniuj kontrakt danych i zapisz, co jest dozwolone, a co odrzucane.
- Zbuduj najmniejszy wzorzec, który opisuje wymagany format.
- Dodaj kotwice, jeśli testujesz całą wartość, a nie fragment.
- Przygotuj przypadki pozytywne, negatywne i graniczne.
- Uruchom testy w docelowym języku i sprawdź różnice silnika.
- Opisz nietypowe fragmenty komentarzem lub nazwą stałej.
W kodzie testowym wolę przechowywać skomplikowany wzorzec w nazwanej stałej niż wpisywać go bezpośrednio w kilku asercjach. Nazwa ORDER_ID_PATTERN mówi więcej niż anonimowy ciąg znaków, a zmiana formatu odbywa się w jednym miejscu.
const ORDER_ID_PATTERN = /^ORD-\d{6}$/;
await expect(page.locator('[data-testid="order-id"]'))
.toHaveText(ORDER_ID_PATTERN);
Warto też unikać nadmiernego uzależniania testów od tekstu widocznego dla użytkownika. Regex dobrze toleruje zmienny numer czy datę, ale nie rozwiąże problemu, gdy aplikacja zmieni język interfejsu. Stabilna semantyka elementu i osobny wzorzec dla zmiennej wartości dają zwykle trwalszy test niż dopasowywanie całego komunikatu.
Od prostego dopasowania do wiarygodnego testu
Największą wartość daje nie sam sprytny wzorzec, lecz jasny kontrakt i dobrze dobrane przypadki testowe. Krótka reguła z kotwicami, ograniczonymi kwantyfikatorami i kilkoma testami granicznymi będzie bardziej użyteczna niż efektowna konstrukcja, której nikt nie chce później zmieniać.
W automatyzacji używam regex przede wszystkim do danych częściowo zmiennych: identyfikatorów, wersji, URL-i i komunikatów. Gdy problem dotyczy struktury dokumentu, logiki biznesowej albo poprawności kalendarza, sięgam po parser lub zwykłe asercje. Taki podział odpowiedzialności sprawia, że testy są szybsze, czytelniejsze i znacznie łatwiejsze do diagnozowania.