Playwright - Automatyczne testy E2E aplikacji webowych
Playwright Automatyczne testy
poradnikiPlaywright - Testy E2E aplikacji webowych
Testowanie end-to-end (E2E) jest kluczowym elementem zapewniania jakości aplikacji webowych. Playwright, stworzony przez Microsoft, szybko stał się jednym z najpopularniejszych frameworków do automatyzacji testów przeglądarek. Oferuje wsparcie dla wielu przeglądarek, inteligentne oczekiwanie na elementy, potężne lokatory i wiele narzędzi ułatwiających codzienną pracę testera. W tym przewodniku pokażemy, jak w pełni wykorzystać możliwości Playwright w projektach opartych o TypeScript.
Czym jest Playwright?#
Playwright to nowoczesny framework do automatyzacji testów E2E, który pozwala testować aplikacje webowe w przeglądarkach Chromium, Firefox i WebKit za pomocą jednego API. W przeciwieństwie do wielu starszych narzędzi, Playwright został zaprojektowany od podstaw z myślą o współczesnych aplikacjach SPA, obsługując natywnie asynchroniczność, Shadow DOM, iframes i wiele kart.
import { test, expect } from '@playwright/test';
test('strona główna wyświetla tytuł', async ({ page }) => {
await page.goto('https://example.com');
await expect(page).toHaveTitle(/Example/);
});
Instalacja jest niezwykle prosta:
npm init playwright@latest
Komenda ta tworzy kompletną strukturę projektu z plikiem konfiguracyjnym playwright.config.ts, katalogiem na testy i przykładowym testem.
Playwright vs Cypress vs Selenium#
Wybór narzędzia do testów E2E bywa trudny. Porównajmy trzy najpopularniejsze rozwiązania.
Selenium to weteran automatyzacji przeglądarek. Obsługuje wiele języków programowania i ma ogromną społeczność. Jednak jego architektura oparta na WebDriver sprawia, że testy bywają wolne i niestabilne. Konfiguracja jest skomplikowana, a API nieintuicyjne.
Cypress zrewolucjonizował testowanie E2E dzięki świetnemu Developer Experience. Działa bezpośrednio w przeglądarce, co daje szybki feedback. Jego ograniczeniem jest jednak obsługa wyłącznie przeglądarek opartych na Chromium (i eksperymentalnie Firefox), brak wsparcia dla wielu kart oraz synchroniczna architektura, która utrudnia testowanie złożonych scenariuszy.
Playwright łączy najlepsze cechy obu rozwiązań. Oferuje wsparcie dla wszystkich głównych przeglądarek, natywną asynchroniczność, wielokartowość, izolację kontekstów i wbudowane narzędzia deweloperskie. Jest szybszy od Selenium i bardziej elastyczny od Cypressa.
// Playwright - testowanie wielu przeglądarek w jednym pliku konfiguracyjnym
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: { ...devices['Desktop Firefox'] } },
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'mobile-chrome', use: { ...devices['Pixel 5'] } },
{ name: 'mobile-safari', use: { ...devices['iPhone 13'] } },
],
});
Multi-browser testing#
Jedną z największych zalet Playwright jest natywne wsparcie dla wielu przeglądarek. Każda przeglądarka jest pobierana automatycznie podczas instalacji i można je aktualizować jedną komendą:
npx playwright install
Playwright obsługuje trzy silniki renderowania: Chromium (Chrome, Edge), Firefox (Gecko) i WebKit (Safari). To oznacza, że możesz testować swoją aplikację na silnikach, które odpowiadają za ponad 95% rynku przeglądarek.
Testy uruchamiane na wielu przeglądarkach konfiguruje się w pliku playwright.config.ts poprzez definiowanie projektów. Każdy projekt może mieć własne ustawienia, takie jak rozmiar okna, emulacja urządzeń mobilnych czy geolokalizacja.
export default defineConfig({
projects: [
{
name: 'Desktop Chrome',
use: {
browserName: 'chromium',
viewport: { width: 1920, height: 1080 },
},
},
{
name: 'Mobile Safari',
use: {
browserName: 'webkit',
viewport: { width: 390, height: 844 },
isMobile: true,
hasTouch: true,
},
},
],
});
Auto-waiting - inteligentne oczekiwanie#
Jednym z najczęstszych problemów w testach E2E jest niestabilność wynikająca z ręcznego zarządzania oczekiwaniem na elementy. Playwright rozwiązuje ten problem dzięki mechanizmowi auto-waiting, który automatycznie czeka, aż element spełni określone warunki przed wykonaniem akcji.
Gdy wywołujesz page.click('button'), Playwright automatycznie czeka, aż przycisk będzie widoczny, włączony, stabilny (nie animowany), wolny od zasłaniających elementów i gotowy do odbioru zdarzeń. To drastycznie redukuje liczbę niestabilnych testów.
test('formularz logowania', async ({ page }) => {
await page.goto('/login');
// Playwright automatycznie czeka, aż pola będą gotowe
await page.fill('#email', 'user@example.com');
await page.fill('#password', 'securePassword123');
await page.click('button[type="submit"]');
// Auto-waiting działa również z asercjami
await expect(page.locator('.dashboard')).toBeVisible();
await expect(page).toHaveURL('/dashboard');
});
Nie musisz dodawać sztucznych opóźnień ani jawnych wywołań waitFor w większości przypadków. Playwright sam zadba o synchronizację.
Lokatory - precyzyjne wyszukiwanie elementów#
Lokatory to serce interakcji z elementami w Playwright. W odróżnieniu od prostych selektorów CSS czy XPath, lokatory Playwright są "leniwe" - nie wyszukują elementu od razu, lecz w momencie wykonania akcji. Dzięki temu są odporne na zmiany w DOM.
test('wyszukiwanie produktów', async ({ page }) => {
await page.goto('/products');
// Lokator po roli ARIA - najlepsza praktyka
const searchInput = page.getByRole('searchbox', { name: 'Szukaj produktów' });
await searchInput.fill('laptop');
// Lokator po tekście
const submitButton = page.getByRole('button', { name: 'Szukaj' });
await submitButton.click();
// Lokator po test ID - gdy inne metody nie pasują
const resultsList = page.getByTestId('search-results');
await expect(resultsList).toBeVisible();
// Łączenie lokatorów - filtrowanie
const firstProduct = page.getByRole('listitem')
.filter({ hasText: 'Laptop Pro' })
.first();
await expect(firstProduct).toContainText('Laptop Pro');
// Lokator po placeholderze
const filterInput = page.getByPlaceholder('Filtruj wyniki...');
await filterInput.fill('16GB RAM');
});
Playwright rekomenduje używanie lokatorów opartych o role ARIA (getByRole), etykiety (getByLabel), tekst (getByText) i atrybuty testowe (getByTestId). Takie podejście sprawia, że testy są bardziej odporne na zmiany w strukturze HTML i jednocześnie weryfikują dostępność aplikacji.
Asercje - weryfikacja stanu aplikacji#
Playwright dostarcza bogaty zestaw asercji, które automatycznie ponawiają sprawdzenie do momentu spełnienia warunku lub upływu limitu czasu. Te tak zwane "web-first assertions" eliminują problem fałszywie negatywnych wyników.
test('koszyk zakupowy', async ({ page }) => {
await page.goto('/cart');
// Asercje na elemencie
const cartHeader = page.getByRole('heading', { name: 'Koszyk' });
await expect(cartHeader).toBeVisible();
await expect(cartHeader).toHaveText('Koszyk (3 produkty)');
// Asercje na liście elementów
const items = page.getByRole('listitem');
await expect(items).toHaveCount(3);
// Asercje na atrybutach CSS
const totalPrice = page.getByTestId('total-price');
await expect(totalPrice).toHaveCSS('font-weight', '700');
await expect(totalPrice).toContainText('PLN');
// Asercje na stronie
await expect(page).toHaveURL(/\/cart/);
await expect(page).toHaveTitle('Koszyk - Mój Sklep');
// Asercje z negacją
const emptyMessage = page.getByText('Koszyk jest pusty');
await expect(emptyMessage).not.toBeVisible();
// Asercje na wartości inputów
const quantityInput = page.getByLabel('Ilość').first();
await expect(quantityInput).toHaveValue('1');
});
Page Object Model - wzorzec organizacji testów#
Page Object Model (POM) to wzorzec projektowy, który enkapsuluje interakcje ze stroną w dedykowanych klasach. Dzięki temu testy są czytelniejsze, łatwiejsze w utrzymaniu i odporne na zmiany w UI.
// pages/login.page.ts
import { type Page, type Locator, expect } from '@playwright/test';
export class LoginPage {
readonly page: Page;
readonly emailInput: Locator;
readonly passwordInput: Locator;
readonly submitButton: Locator;
readonly errorMessage: Locator;
constructor(page: Page) {
this.page = page;
this.emailInput = page.getByLabel('Email');
this.passwordInput = page.getByLabel('Hasło');
this.submitButton = page.getByRole('button', { name: 'Zaloguj się' });
this.errorMessage = page.getByRole('alert');
}
async goto() {
await this.page.goto('/login');
}
async login(email: string, password: string) {
await this.emailInput.fill(email);
await this.passwordInput.fill(password);
await this.submitButton.click();
}
async expectError(message: string) {
await expect(this.errorMessage).toContainText(message);
}
async expectSuccessRedirect() {
await expect(this.page).toHaveURL('/dashboard');
}
}
// tests/login.spec.ts
import { test } from '@playwright/test';
import { LoginPage } from '../pages/login.page';
test.describe('Logowanie', () => {
let loginPage: LoginPage;
test.beforeEach(async ({ page }) => {
loginPage = new LoginPage(page);
await loginPage.goto();
});
test('poprawne logowanie', async () => {
await loginPage.login('user@example.com', 'password123');
await loginPage.expectSuccessRedirect();
});
test('błędne hasło wyświetla komunikat', async () => {
await loginPage.login('user@example.com', 'wrong');
await loginPage.expectError('Nieprawidłowe dane logowania');
});
});
Fixtures - zarządzanie stanem testów#
Fixtures w Playwright pozwalają na konfigurowanie i udostępnianie zasobów między testami. Możesz tworzyć własne fixtures, które automatycznie inicjalizują Page Objects, logują użytkownika, przygotowują dane testowe lub konfigurują mocki.
// fixtures/base.fixture.ts
import { test as base } from '@playwright/test';
import { LoginPage } from '../pages/login.page';
import { DashboardPage } from '../pages/dashboard.page';
type MyFixtures = {
loginPage: LoginPage;
dashboardPage: DashboardPage;
authenticatedPage: DashboardPage;
};
export const test = base.extend<MyFixtures>({
loginPage: async ({ page }, use) => {
const loginPage = new LoginPage(page);
await loginPage.goto();
await use(loginPage);
},
dashboardPage: async ({ page }, use) => {
await use(new DashboardPage(page));
},
authenticatedPage: async ({ page }, use) => {
// Automatyczne logowanie przed testem
await page.goto('/login');
await page.getByLabel('Email').fill('admin@example.com');
await page.getByLabel('Hasło').fill('admin123');
await page.getByRole('button', { name: 'Zaloguj się' }).click();
await page.waitForURL('/dashboard');
const dashboardPage = new DashboardPage(page);
await use(dashboardPage);
},
});
export { expect } from '@playwright/test';
// tests/dashboard.spec.ts
import { test, expect } from '../fixtures/base.fixture';
test('zalogowany użytkownik widzi dashboard', async ({ authenticatedPage }) => {
await expect(authenticatedPage.welcomeMessage).toContainText('Witaj');
});
Fixture authenticatedPage automatycznie loguje użytkownika przed każdym testem, który go używa. To eliminuje duplikację kodu i zapewnia spójny stan początkowy.
Testowanie API#
Playwright nie ogranicza się do testowania interfejsu użytkownika. Wbudowany APIRequestContext pozwala wykonywać zapytania HTTP, co jest przydatne do przygotowania danych testowych, weryfikacji stanu backendu lub testowania samego API.
import { test, expect } from '@playwright/test';
test.describe('API produktów', () => {
test('GET /api/products zwraca listę produktów', async ({ request }) => {
const response = await request.get('/api/products');
expect(response.status()).toBe(200);
const products = await response.json();
expect(products).toHaveLength(10);
expect(products[0]).toHaveProperty('name');
expect(products[0]).toHaveProperty('price');
});
test('POST /api/products tworzy nowy produkt', async ({ request }) => {
const newProduct = {
name: 'Nowy Produkt',
price: 99.99,
category: 'electronics',
};
const response = await request.post('/api/products', {
data: newProduct,
});
expect(response.status()).toBe(201);
const created = await response.json();
expect(created.name).toBe('Nowy Produkt');
expect(created.id).toBeDefined();
});
test('przygotowanie danych przez API, weryfikacja w UI', async ({ page, request }) => {
// Tworzenie produktu przez API
const response = await request.post('/api/products', {
data: { name: 'Testowy Laptop', price: 4999.00, category: 'laptops' },
});
const product = await response.json();
// Weryfikacja w interfejsie użytkownika
await page.goto(`/products/${product.id}`);
await expect(page.getByRole('heading')).toContainText('Testowy Laptop');
await expect(page.getByTestId('price')).toContainText('4 999,00 zł');
});
});
Porównywanie wizualne (Visual Comparisons)#
Playwright oferuje wbudowane porównywanie wizualne (snapshot testing), które pozwala wykrywać niezamierzone zmiany w wyglądzie strony. Przy pierwszym uruchomieniu tworzy referencyjne zrzuty ekranu, a przy kolejnych porównuje je z aktualnymi.
import { test, expect } from '@playwright/test';
test('wygląd strony głównej', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveScreenshot('homepage.png');
});
test('wygląd komponentu karty produktu', async ({ page }) => {
await page.goto('/products');
const productCard = page.getByTestId('product-card').first();
await expect(productCard).toHaveScreenshot('product-card.png', {
maxDiffPixelRatio: 0.05, // Tolerancja 5%
});
});
test('responsywny wygląd na mobile', async ({ page }) => {
await page.setViewportSize({ width: 375, height: 812 });
await page.goto('/');
await expect(page).toHaveScreenshot('homepage-mobile.png');
});
test('porównywanie z maską na dynamiczne treści', async ({ page }) => {
await page.goto('/dashboard');
await expect(page).toHaveScreenshot('dashboard.png', {
mask: [
page.getByTestId('current-date'),
page.getByTestId('random-banner'),
],
});
});
Aby zaktualizować referencyjne zrzuty ekranu po zamierzonych zmianach w UI, wystarczy uruchomić:
npx playwright test --update-snapshots
Trace Viewer - debugowanie testów#
Trace Viewer to jedno z najpotężniejszych narzędzi Playwright. Nagrywa pełny ślad wykonania testu, włącznie ze zrzutami ekranu na każdym kroku, snapshotami DOM, logami sieciowymi i konsolowymi. Dzięki temu debugowanie nawet najbardziej złożonych scenariuszy staje się intuicyjne.
// playwright.config.ts
export default defineConfig({
use: {
// Nagrywaj trace tylko przy błędach
trace: 'on-first-retry',
// Lub nagrywaj zawsze (przydatne w CI)
// trace: 'on',
},
});
Po uruchomieniu testów z nagrywaniem trace, plik .zip z danymi można otworzyć w przeglądarce:
npx playwright show-trace trace.zip
Trace Viewer pokazuje chronologiczną listę akcji z odpowiadającymi im zrzutami ekranu, pozwala przeglądać stan DOM przed i po każdej akcji, wyświetla szczegóły zapytań sieciowych i logi konsoli. To nieocenione narzędzie przy analizie błędów w środowisku CI, gdzie nie masz bezpośredniego dostępu do przeglądarki.
Test Generator (Codegen)#
Playwright Codegen to interaktywny generator testów, który nagrywa twoje interakcje z przeglądarką i automatycznie generuje kod testu. To świetny sposób na szybkie tworzenie szkieletów testów.
npx playwright codegen https://example.com
Po uruchomieniu komendy otwiera się przeglądarka z narzędziem inspektora. Każde kliknięcie, wpisanie tekstu czy nawigacja jest automatycznie tłumaczona na kod Playwright. Generator inteligentnie dobiera lokatory, preferując role ARIA i atrybuty testowe.
Codegen wspiera również generowanie kodu do nagrywania stanu uwierzytelniania:
npx playwright codegen --save-storage=auth.json https://example.com/login
Nagrane dane uwierzytelniania możesz później wykorzystać w testach, eliminując konieczność logowania w każdym teście:
export default defineConfig({
projects: [
{
name: 'setup',
testMatch: /.*\.setup\.ts/,
},
{
name: 'tests',
dependencies: ['setup'],
use: {
storageState: 'playwright/.auth/user.json',
},
},
],
});
Integracja z CI/CD#
Playwright świetnie integruje się z popularnymi systemami CI/CD. Oficjalna dokumentacja dostarcza gotowe konfiguracje dla GitHub Actions, GitLab CI, Azure Pipelines i wielu innych.
# .github/workflows/e2e-tests.yml
name: E2E Tests
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 20
- name: Install dependencies
run: npm ci
- name: Install Playwright browsers
run: npx playwright install --with-deps
- name: Run Playwright tests
run: npx playwright test
- name: Upload test results
uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Kluczową opcją w CI jest --with-deps, która instaluje systemowe zależności wymagane przez przeglądarki. Dzięki temu nie musisz ręcznie konfigurować środowiska.
Równoległe wykonywanie testów#
Playwright domyślnie uruchamia pliki testowe równolegle, wykorzystując wiele procesów roboczych (workers). Testy w ramach jednego pliku są uruchamiane sekwencyjnie, chyba że jawnie skonfigurujesz inaczej.
// playwright.config.ts
export default defineConfig({
// Liczba równoległych workerów
workers: process.env.CI ? 2 : undefined, // W CI ogranicz, lokalnie użyj auto
// Pełna równoległość - nawet testy w jednym pliku
fullyParallel: true,
// Ponawianie testów przy błędach
retries: process.env.CI ? 2 : 0,
// Limit czasu na pojedynczy test
timeout: 30_000,
});
Aby testy mogły działać równolegle, muszą być od siebie niezależne. Każdy test powinien operować na własnych danych i nie polegać na stanie zostawionym przez inne testy. Playwright ułatwia to dzięki izolacji kontekstów przeglądarki - każdy test domyślnie otrzymuje świeży kontekst.
test.describe('testy równoległe', () => {
test.describe.configure({ mode: 'parallel' });
test('test A', async ({ page }) => {
// Uruchamiany równolegle z testem B
await page.goto('/feature-a');
await expect(page.getByRole('heading')).toContainText('Feature A');
});
test('test B', async ({ page }) => {
// Uruchamiany równolegle z testem A
await page.goto('/feature-b');
await expect(page.getByRole('heading')).toContainText('Feature B');
});
});
Raportowanie wyników#
Playwright oferuje kilka wbudowanych reporterów i możliwość tworzenia własnych. Najpopularniejsze to HTML reporter, który generuje interaktywny raport z wynikami testów.
// playwright.config.ts
export default defineConfig({
reporter: [
// HTML reporter - interaktywny raport
['html', { outputFolder: 'playwright-report', open: 'never' }],
// Raport w konsoli - lista testów
['list'],
// JUnit - integracja z CI/CD
['junit', { outputFile: 'results/junit.xml' }],
// JSON - do dalszego przetwarzania
['json', { outputFile: 'results/results.json' }],
],
});
Po uruchomieniu testów raport HTML można otworzyć komendą:
npx playwright show-report
Raport HTML zawiera szczegółowe informacje o każdym teście: czas wykonania, zrzuty ekranu, logi, trace i informacje o błędach. To idealne narzędzie do analizy wyników w CI/CD i dzielenia się nimi z zespołem.
Dobre praktyki#
Na koniec zebierzmy najważniejsze dobre praktyki pracy z Playwright:
- Używaj lokatorów opartych o role ARIA -
getByRole,getByLabel,getByTextzamiast selektorów CSS. - Nie dodawaj sztucznych opóźnień - polegaj na auto-waiting i web-first assertions.
- Izoluj testy - każdy test powinien być niezależny i działać w czystym kontekście.
- Stosuj Page Object Model - enkapsuluj logikę stron w dedykowanych klasach.
- Przygotowuj dane przez API - używaj
requestfixture do szybkiego tworzenia danych testowych. - Nagrywaj trace w CI - konfiguruj
trace: 'on-first-retry'do debugowania błędów. - Uruchamiaj testy równolegle - projektuj testy jako niezależne, by korzystać z pełnej równoległości.
- Taguj testy - używaj
test.describei tagów do organizacji zestawów testów.
Podsumowanie#
Playwright to potężne i dojrzałe narzędzie, które znacząco podnosi jakość testowania aplikacji webowych. Dzięki wsparciu wielu przeglądarek, inteligentnemu auto-waiting, potężnym lokatorom, wbudowanemu Trace Viewer i generatorowi testów, pozwala pisać stabilne i łatwe w utrzymaniu testy E2E. Integracja z CI/CD, równoległe wykonywanie i bogate raportowanie sprawiają, że Playwright idealnie wpisuje się w nowoczesne procesy wytwarzania oprogramowania.
Potrzebujesz pomocy we wdrożeniu testów E2E w swoim projekcie? MDS Software Solutions Group specjalizuje się w budowaniu kompleksowych strategii testowania, automatyzacji QA i integracji testów z pipeline'ami CI/CD. Skontaktuj się z nami, aby dowiedzieć się, jak możemy pomóc Twojemu zespołowi dostarczać oprogramowanie najwyższej jakości.
Zespół ekspertów programistycznych specjalizujących się w nowoczesnych technologiach webowych.