Playwright od zera — konfiguracja i pierwszy test

Dowiedz się, dlaczego Playwright to nowoczesny wybór dla testów E2E. Skonfiguruj pierwszy projekt, napisz niezawodny test używając auto-waiting i dostępnych selektorów, oraz zrozum model.

Ten wpis jest częścią serii Podstawy Playwright. W tej pięcioczęściowej serii nauczysz się budować niezawodne testy end-to-end z Playwright — od instalacji po zaawansowane wzorce. Zacznij tutaj, jeśli jesteś nowy w Playwright lub chcesz wzmocnić swoje fundamenty.


Dlaczego Playwright?

Jeśli wybierasz framework do testów E2E w 2025 roku, Playwright powinien być na Twojej krótkiej liście. Oto dlaczego.

Auto-waiting domyślnie. Playwright czeka, aż elementy będą aktywne, przed interakcją z nimi. Koniec z waitForTimeout(3000) rozrzuconymi po testach w nadziei, że strona się załadowała. Ta pojedyncza funkcja eliminuje 80% niestabilności, która nękała suity Selenium i wczesnego Cypressa.

Wsparcie cross-browser, które naprawdę działa. Playwright jest dostarczany z Chromium, Firefox i WebKit (silnik Safari). Jeden zestaw testów, trzy przeglądarki. To nie jest teoria — działa out of the box.

Developer experience. Trace viewer to game-changer. Gdy test nie przejdzie, dostajesz pełną oś czasu: screenshoty, snapshoty DOM, ruch sieciowy, logi konsoli. To jak posiadanie debuggera, który podróżuje w czasie.

Testowanie API w tym samym frameworku. APIRequestContext Playwright pozwala przygotowywać dane, sprawdzać stan backendu lub uruchamiać czyste testy API bez zmiany narzędzi. To ma znaczenie, gdy testujesz pełną podróż użytkownika obejmującą zarówno UI, jak i API.

Szybkość. Playwright uruchamia testy równolegle domyślnie i startuje przeglądarki w milisekundach. Dobrze zbudowana suita Playwright działa szybciej niż większość suit Selenium pomimo pokrywania tych samych scenariuszy.

:::tip[Kiedy NIE używać Playwright] Playwright to przesada dla czystego testowania API (użyj curl, httpie lub natywnych klientów HTTP dla danego języka). To także nie jest właściwy wybór dla testowania regresji wizualnej na dużą skalę (użyj Chromatic, Percy lub Applitools). Playwright wyróżnia się w testowaniu interakcji użytkownika przez przeglądarkę. :::


Instalacja — właściwy sposób

Skonfigurujmy nowy projekt Playwright od zera.

Krok 1: Zainicjuj projekt Node.js

mkdir my-playwright-tests
cd my-playwright-tests
npm init -y

To tworzy package.json w pustym katalogu.

Krok 2: Zainstaluj Playwright

npm install --save-dev @playwright/test
npx playwright install

Pierwsza komenda instaluje test runner Playwright. Druga pobiera binaria przeglądarek (Chromium, Firefox, WebKit). Te binaria są wersjonowane z Playwright, więc dostajesz znaną-dobrą przeglądarkę, która się nie zmieni, gdy Chrome auto-aktualizuje się na Twojej maszynie.

Krok 3: Stwórz podstawową konfigurację

Utwórz playwright.config.ts w głównym katalogu projektu:

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

export default defineConfig({
  testDir: './tests',
  fullyParallel: true,
  forbidOnly: !!process.env.CI,
  retries: process.env.CI ? 2 : 0,
  workers: process.env.CI ? 1 : undefined,
  reporter: 'html',
  use: {
    baseURL: 'http://localhost:3000',
    trace: 'on-first-retry',
    screenshot: 'only-on-failure',
  },
  projects: [
    {
      name: 'chromium',
      use: { ...devices['Desktop Chrome'] },
    },
  ],
});

Rozbijmy, co robi ta konfiguracja:

  • testDir: Gdzie Playwright szuka plików testowych (domyślnie: pliki kończące się na .spec.ts lub .test.ts)
  • fullyParallel: Uruchom wszystkie testy równolegle (szybszy feedback, ale wymaga izolacji testów)
  • forbidOnly: Zawiedź CI, jeśli ktoś przypadkowo zostawił test.only w kodzie
  • retries: Powtarzaj nieudane testy w CI (siatka bezpieczeństwa dla niestabilności infrastruktury), ale nie lokalnie (więc od razu widzisz prawdziwe błędy)
  • baseURL: Główny URL Twojej aplikacji — wszystkie względne URL-e w testach rozwiązują się względem tego
  • trace: 'on-first-retry': Przechwytuj pełny trace (screenshoty, sieć, DOM) tylko jeśli test nie przejdzie i jest ponawiany
  • projects: Definiuje, które przeglądarki testować. Zacznij od jednej (Chromium), dodaj Firefox i WebKit później

:::info[Dlaczego zaczynać od jednej przeglądarki?] Testowanie w trzech przeglądarkach od pierwszego dnia potraja czas wykonania i mnoży powierzchnię debugowania. Zacznij od Chromium, ustabilizuj swoje testy, potem dodaj Firefox i WebKit. Różnice między przeglądarkami są realne, ale rzadkie — nie płać za to, dopóki nie potrzebujesz pokrycia. :::


Twój pierwszy test — flow logowania

Stwórz tests/login.spec.ts:

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

test.describe('Login', () => {
  test('powinien zalogować z prawidłowymi danymi', async ({ page }) => {
    await page.goto('/login');

    await page.getByLabel('Email').fill('user@example.com');
    await page.getByLabel('Hasło').fill('password123');
    await page.getByRole('button', { name: 'Zaloguj' }).click();

    await expect(page.getByText('Witaj ponownie')).toBeVisible();
    await expect(page).toHaveURL(/\/dashboard/);
  });

  test('powinien pokazać błąd z nieprawidłowymi danymi', async ({ page }) => {
    await page.goto('/login');

    await page.getByLabel('Email').fill('wrong@example.com');
    await page.getByLabel('Hasło').fill('wrongpassword');
    await page.getByRole('button', { name: 'Zaloguj' }).click();

    await expect(page.getByText('Nieprawidłowe dane')).toBeVisible();
    await expect(page).toHaveURL('/login');
  });
});

Co sprawia, że to jest dobry test?

Selektory odzwierciedlają, jak użytkownicy widzą stronę. getByLabel('Email') znajduje input po tekście etykiety — w ten sam sposób, w jaki prawdziwy użytkownik go znajduje. Jeśli id lub class inputa zmieni się podczas refaktoru, test nadal działa. Omówimy strategię lokatorów dogłębnie w Części 2 — Lokatory, które się nie rozpadają.

Brak hardkodowanych oczekiwań. Zauważ, że nie ma waitForTimeout(2000). expect(locator).toBeVisible() w Playwright automatycznie czeka, aż element będzie widoczny lub upłynie timeout (domyślnie: 5 sekund). To auto-waiting w akcji.

Asercje sprawdzają wyniki, nie kroki. Test nie weryfikuje, że żądanie HTTP się zakończyło lub że pojawił się spinner ładowania. Sprawdza obserwowalny rezultat: tekst “Witaj ponownie” i zmianę URL. Jeśli implementacja się zmieni, ale wynik pozostanie taki sam, test nadal przechodzi.


Uruchamianie testu

Uruchom wszystkie testy

npx playwright test

To uruchamia testy w trybie headless we wszystkich skonfigurowanych projektach (przeglądarkach). Output pokazuje, które testy przeszły, nie przeszły lub zostały pominięte.

Uruchom w trybie headed (zobacz przeglądarkę)

npx playwright test --headed

Przydatne podczas pisania testów lub debugowania błędów. Widzisz dokładnie to, co widzi Playwright.

Uruchom konkretny plik testowy

npx playwright test tests/login.spec.ts

Uruchom testy pasujące do wzorca

npx playwright test --grep "powinien zalogować"

Otwórz raport HTML

npx playwright show-report

Po uruchomieniu testów to otwiera interaktywny raport HTML pokazujący status pass/fail, trace’y dla błędów i screenshoty.

:::tip[Debuguj z UI Mode] npx playwright test --ui otwiera UI mode Playwright — interaktywny test runner z podglądem na żywo, wykonaniem krok po kroku i snapshotami DOM. To najszybszy sposób, żeby zrozumieć, dlaczego test nie przechodzi. :::


Model mentalny — Browser, Context, Page

Zrozumienie architektury Playwright ułatwia debugowanie i odblokowuje zaawansowane wzorce.

Browser

browser reprezentuje działającą instancję przeglądarki. Playwright uruchamia ją raz i używa ponownie w testach dla szybkości.

const browser = await chromium.launch();

W większości przypadków nigdy nie wchodzisz w interakcję z przeglądarką bezpośrednio — test runner nią zarządza.

BrowserContext

context to izolowana sesja przeglądarki z własnymi cookies, localStorage i stanem sesji. Każdy test dostaje świeży kontekst, zapewniając, że testy nie wyciekają stanu między sobą.

const context = await browser.newContext();

Dlatego testy Playwright są izolowane domyślnie. Test A się loguje, test B zaczyna od nowa — nie ma potrzeby ręcznego czyszczenia.

Page

page to pojedyncza karta w kontekście. Większość testów pracuje z pojedynczą stroną, ale możesz otworzyć wiele stron, aby przetestować scenariusze jak “użytkownik otwiera link w nowej karcie.”

const page = await context.newPage();

W Playwright Test (runner @playwright/test) zazwyczaj używasz fixture’a page:

test('przykład', async ({ page }) => {
  // `page` jest dostarczana automatycznie — świeży kontekst + strona na test
  await page.goto('https://example.com');
});

Test runner tworzy nowy kontekst i stronę dla każdego testu, potem je odrzuca, gdy test się kończy. Dlatego nie potrzebujesz czyszczenia afterEach w większości przypadków.

:::warning[Współdzielony stan to bug] Jeśli test B zawodzi tylko, gdy test A uruchomi się pierwszy, masz problem ze współdzielonym stanem. Sprawdź: cookies, localStorage, stan sesji po stronie serwera, rekordy bazy danych. Izolacja kontekstu Playwright zapobiega wyciekowi po stronie klienta, ale nie może naprawić zanieczyszczenia stanu po stronie serwera. Projektuj testy tak, aby tworzyły własne dane przez API przed interakcją UI (omówione w Części 5 — Testowanie API z Playwright). :::


Częste pułapki dla początkujących

1. Zapominanie o await

To najczęstszy błąd:

// ŹLE — nie czeka na nawigację
page.goto('/login');
page.getByLabel('Email').fill('user@example.com');

Każda akcja Playwright zwraca Promise. Musisz na nią await:

// POPRAWNIE
await page.goto('/login');
await page.getByLabel('Email').fill('user@example.com');

Włącz @typescript-eslint/no-floating-promises w swoim linterze, aby to automatycznie wychwytywać.

2. Używanie page.locator() jako synchronicznego zapytania

// ŹLE — to niczego nie znajduje
const button = page.getByRole('button', { name: 'Wyślij' });
if (button) {
  await button.click();
}

getByRole() zwraca Locator — leniwą referencję, która rozwiązuje się tylko, gdy wykonasz akcję. Nie odpytuje DOM natychmiast. Aby sprawdzić, czy element istnieje, użyj asercji:

// POPRAWNIE — sprawdza widoczność asynchronicznie
const button = page.getByRole('button', { name: 'Wyślij' });
await expect(button).toBeVisible();
await button.click();

3. Hardkodowany baseURL w każdym teście

// UNIKAJ — kruche, jeśli URL się zmieni
await page.goto('http://localhost:3000/login');
// LEPIEJ — względem baseURL w configu
await page.goto('/login');

To czyni testy przenośnymi między środowiskami (dev, staging, prod).


Co omówiliśmy

Masz teraz:

  • Działającą instalację Playwright
  • Konfigurację testów zoptymalizowaną pod szybkość i debugowalność
  • Niezawodny test logowania używający dostępnych selektorów i auto-waiting
  • Zrozumienie architektury browser/context/page

Ta podstawa wystarczy, żeby zacząć pisać swoją pierwszą suitę testów. W następnym poście zagłębimy się w strategię lokatorów — najważniejszą umiejętność do pisania testów, które nie psują się przy każdej zmianie UI.

Zadanie na ten tydzień: Zainstaluj Playwright w projekcie, napisz jeden test dla prawdziwego flow użytkownika (logowanie, wyszukiwanie, checkout — coś, co Twoi użytkownicy faktycznie robią) i uruchom go zarówno w trybie --headed, jak i --ui. Zapoznaj się z trace viewerem, zanim będziesz go potrzebować w produkcji.


Dalej w serii: Część 2 — Lokatory, które się nie rozpadają