Przejdź do głównej zawartości

Zestaw testów end-to-end (E2E) utrzymywany przez agenta to testy Playwright, które agent kodujący pisze na podstawie kryteriów akceptacji i utrzymuje w stanie zielonym, gdy zmienia się UI, a CI dowodzi, że zestaw nadal wyłapuje regresje. Kluczowe zastrzeżenie: testy pisze agent, ale listę ścieżek, wzorcowe zrzuty ekranu i każdą zmianę osłabiającą asercję zatwierdza człowiek.

Ta strona jest dla programisty, który prosi agenta o „dodanie testów E2E”, i dla tech leada, który musi zaufać wynikowi. Agent tworzy 40 plików testów, CI świeci na zielono, a tydzień później na produkcji psuje się checkout. Nikt nie czytał testów, więc nikt nie zauważył, że połowa z nich sprawdza toast zamiast zapisanego zamówienia, a trzy zawierają waitForTimeout(5000), który maskuje wyścig.

  • Budżet ścieżek, który rozstrzyga, które przepływy dostają test E2E, a które należą do tańszych warstw
  • playwright.config.ts, w którym niestabilne testy, zabłąkane test.only i ciche aktualizacje zrzutów zatrzymują CI
  • Połączenie z przeglądarką dla Claude Code, Codex i Cursora, dzięki któremu agent pisze lokatory na podstawie prawdziwej strony
  • Prompty do skopiowania: generowanie testów z kryteriów akceptacji, refaktoryzacja do fixture’ów, testy wizualne i naprawa niestabilności bez osłabiania asercji
  • Cztery kontrole, które pokazują, że zestaw łapie błędy, i żadna nie wymaga czytania testów linia po linii

Przykłady używają Playwright Test 1.63.0 (tag latest w npm na 2026-09-26). Na Cypressie nadal obowiązują budżet ścieżek, kontrole wyroczni i reguły z promptów (asercje na utrwalonym wyniku, bez sztucznych opóźnień (waitForTimeout), bez pomijania testów (test.skip)); polecenia, API lokatorów i klucze konfiguracji już nie.

Które ścieżki użytkownika zasługują na test E2E?

Dział zatytułowany „Które ścieżki użytkownika zasługują na test E2E?”

Testy E2E to najwolniejsza i najdroższa warstwa, a agenci piszą je szybciej, niż ktokolwiek zdąży je utrzymywać. Ustal budżet, zanim agent cokolwiek napisze. Tę listę trzyma tech lead, bo to ta część wyroczni, której żaden agent nie powinien sam rozszerzać ani skracać.

Test E2E, gdy…Tańsza warstwa, gdy…
Przepływ przechodzi przez przeglądarkę, API i magazyn danych, a użytkownik zauważyłby awarię (rejestracja, checkout, publikacja)Reguła to czysta logika, np. zaokrąglanie cen czy walidacja: testy jednostkowe
Błędy tej klasy występują tylko w przeglądarce: autouzupełnianie, fokus, iframe’y, routing po stronie klienta, hydratacjaKontrakt łączy usługi: testy API albo testy integracyjne
Regresja kosztowałaby przychód lub zaufanie, zanim wyłapie ją monitoringSprawdzenie to jednorazowy dowód dla jednej zmiany: weryfikacja w przeglądarce prowadzona przez agenta

Trzymaj listę w repozytorium, obok zestawu, jako tests/e2e/JOURNEYS.md, z jedną linią na ścieżkę: kryterium akceptacji, które ta ścieżka potwierdza, i plik testu, który to sprawdza. Recenzenci czytają ten plik zamiast testów. Same kryteria pochodzą z kryteriów akceptacji, które agent potrafi zweryfikować.

Agent kopiuje to, co robi istniejący zestaw. Zapisz reguły najpierw w konfiguracji i fixture’ach, żeby pierwszy wygenerowany test je odziedziczył.

  1. Zainstaluj Playwright Test i jedną przeglądarkę w terminalu:

    Okno terminala
    npm init playwright@latest -- --quiet --browser=chromium --install-deps
  2. Zastąp wygenerowany playwright.config.ts konfiguracją, która zamienia ciche porażki w głośne:

    playwright.config.ts
    import { defineConfig, devices } from '@playwright/test';
    const CI = !!process.env.CI;
    const baseURL = process.env.E2E_BASE_URL ?? 'http://localhost:3000';
    const port = new URL(baseURL).port || '3000'; // the dev server listens where the tests look
    export default defineConfig({
    testDir: './tests/e2e',
    fullyParallel: true,
    forbidOnly: CI, // a stray test.only fails the run
    retries: CI ? 1 : 0, // one retry so Playwright can label a test flaky…
    failOnFlakyTests: CI, // …and the flaky label fails the job
    updateSnapshots: CI ? 'none' : 'missing', // CI never writes a baseline
    reporter: CI ? 'blob' : 'list',
    use: { baseURL, trace: 'retain-on-failure' },
    projects: [
    { name: 'setup', testMatch: /auth\.setup\.ts/ },
    {
    name: 'chromium',
    use: { ...devices['Desktop Chrome'], storageState: 'tests/e2e/.auth/user.json' },
    dependencies: ['setup'],
    },
    ],
    webServer: {
    command: `npm run dev -- --port ${port}`, // assumes your dev script accepts --port
    url: baseURL,
    reuseExistingServer: !CI,
    },
    });

    Domyślna wartość updateSnapshots to 'missing': brakujący wzorzec zostaje zapisany automatycznie. Ustawienie 'none' w CI sprawia, że test zrzutu bez zatwierdzonego wzorca kończy się błędem, zamiast zatwierdzić się sam.

  3. Zaloguj się raz przez API, nie przez UI, i zapisz sesję dla wszystkich testów:

    tests/e2e/auth.setup.ts
    import { test as setup, expect } from '@playwright/test';
    setup('authenticate', async ({ request }) => {
    const res = await request.post('/api/auth/login', {
    data: { email: process.env.E2E_USER_EMAIL, password: process.env.E2E_USER_PASSWORD },
    });
    expect(res.ok()).toBeTruthy();
    await request.storageState({ path: 'tests/e2e/.auth/user.json' });
    });

    Dodaj tests/e2e/.auth/ do .gitignore. Podmień /api/auth/login na własny endpoint logowania i używaj jednorazowego konta testowego, nigdy prawdziwego użytkownika.

  4. Chroń wyrocznię: dodaj tests/e2e/, playwright.config.ts i katalogi ze zrzutami do CODEOWNERS, żeby każdą ich zmianę zatwierdzał człowiek. Ochrona wyroczni opisuje reguły, które w ogóle blokują agentowi edycję tych plików.

Agent pisze lepsze lokatory, gdy może otworzyć stronę i odczytać jej drzewo dostępności, niż gdy zgaduje na podstawie JSX. Playwright oferuje dwie drogi: serwer Playwright MCP oraz Playwright CLI ze skillami, które według README mogą lepiej służyć agentom kodującym, bo nie ładuje do kontekstu dużych schematów narzędzi ani zrzutów drzewa dostępności. Szczegółowe porównanie znajdziesz w Playwright MCP kontra Playwright CLI. Konfiguracja różni się w zależności od narzędzia:

Okno terminala
# Browser tools for this project; --isolated keeps parallel agents from sharing a profile
claude mcp add playwright -- npx @playwright/mcp@latest --isolated
# Optional: Playwright's planner, generator and healer subagents
npx playwright init-agents --loop=claude

init-agents --loop=claude zapisuje trzech subagentów w .claude/agents/ i wpis serwera playwright-test w .mcp.json. Nadpisuje przy tym istniejący plik .mcp.json (sprawdzone w 1.63.0), więc najpierw zacommituj lub scal swoje obecne wpisy.

Żeby w dowolnym z trzech narzędzi użyć CLI zamiast MCP, uruchom npm install -g @playwright/cli@latest (pakiet playwright-cli bez zakresu jest przestarzały), a potem zainstaluj skill tam, gdzie czyta go twoje narzędzie. playwright-cli install --skills domyślnie przyjmuje claude i zapisuje .claude/skills/playwright-cli/ dla Claude Code; Codex czyta skille projektu z .agents/skills/, więc tam uruchom playwright-cli install --skills=agents (oba warianty sprawdzone w @playwright/cli 0.1.22). Cursor wczytuje skille z .agents/skills/ i .cursor/skills/, a dla zgodności także z .claude/skills/ i .codex/skills/ (dokumentacja Cursora, sprawdzone 2026-10-12), więc widzi obie powyższe instalacje; zanim na nim polegasz, sprawdź, czy Cursor wymienia ten skill. Przeglądarkę opartą na skillu, niezwiązaną z Playwright, opisuje strona agent-browser.

Dawaj agentowi jedną ścieżkę naraz, razem z jej kryteriami, i każ mu uruchamiać to, co napisał. Poniższy prompt działa bez zmian w Claude Code, Codex i Cursorze.

--repeat-each=5 --retries=0 to tani filtr niestabilności: test, który lokalnie nie przechodzi raz na pięć uruchomień, na wielu runnerach CI kończy się błędem znacznie częściej. Proś o wynik polecenia, nie o podsumowanie, żeby liczba przejść była dowodem, a nie deklaracją.

Page objecty czy fixture’y: jak utrzymać testy pisane przez agenta

Dział zatytułowany „Page objecty czy fixture’y: jak utrzymać testy pisane przez agenta”

Agenci duplikują helpery. Po dziesięciu ścieżkach zwykle masz cztery helpery logowania i trzy sposoby otwierania cennika. Konsoliduj, gdy zestaw ma już kilka ścieżek, nie wcześniej, i zostaw asercje w plikach testów, gdzie recenzent je znajdzie.

Liczby „przed” i „po” pozwalają zweryfikować refaktoryzację bez jej czytania: te same tytuły testów i ta sama liczba asercji oznaczają, że agent przeniósł kod, nie gubiąc kontroli. Page objecty z asercjami ukrywają je przed recenzją; zasada „expect zostaje w pliku testu” jest tym, co umożliwia tę kontrolę.

Dodaj testy regresji wizualnej, których agent nie zatwierdzi

Dział zatytułowany „Dodaj testy regresji wizualnej, których agent nie zatwierdzi”

toHaveScreenshot() porównuje stronę z zapisanym wzorcem i domyślnie wyłącza animacje CSS. Ryzykiem przy agencie nie jest samo porównanie, tylko zatwierdzanie: npx playwright test --update-snapshots nadpisuje każdy wzorzec, który przestał pasować, także taki, który różni się z powodu błędu (domyślny tryb flagi to changed; --update-snapshots=all nadpisuje wszystkie).

Nazwy wzorców zawierają przeglądarkę i system operacyjny, więc wzorzec zrobiony na Macu nie przejdzie na linuksowym runnerze CI. Generuj je w tym samym obrazie kontenera, którego używa CI (workflow poniżej działa w mcr.microsoft.com/playwright:v1.63.0-noble), jeden raz, z katalogu głównego repozytorium. Kontener potrzebuje własnego npm ci, bo node_modules z hosta mogą zawierać natywne binaria dla innego systemu, oraz danych logowania testowego użytkownika, bo każdy test zależy od logowania w auth.setup.ts:

Okno terminala
docker run --rm --ipc=host -e E2E_USER_EMAIL -e E2E_USER_PASSWORD \
-v "$PWD":/work -w /work mcr.microsoft.com/playwright:v1.63.0-noble \
/bin/bash -c 'npm ci && npx playwright test visual.spec.ts --update-snapshots=missing'

Na hoście z Linuksem kontener zapisze wzorce jako root, więc dodaj --user $(id -u):$(id -g) albo po wszystkim uruchom chown na wygenerowanych plikach. Potem człowiek ogląda obraz różnic w raporcie HTML i zatwierdza każdą zmianę wzorca w pull requeście; CODEOWNERS na katalogu ze zrzutami to wymusza.

Uruchamiaj zestaw w CI z shardingiem i bramką niestabilności

Dział zatytułowany „Uruchamiaj zestaw w CI z shardingiem i bramką niestabilności”

Podziel zestaw na shardy i scal raporty blob w jeden raport HTML. Workflow działa na pull_request, więc uruchamia kod kontrybutora bez uprawnień zapisu. Pull requesty z forków nie dostają sekretów, więc auth.setup.ts wysłałby puste dane logowania i każdy fork kończyłby E2E błędem niezwiązanym z produktem; warunek if: poniżej pomija zadanie dla forków. Jeśli przyjmujesz zmiany z zewnątrz, dodaj dla nich osobne zadanie z podzbiorem testów bez logowania. Zadanie działa w obrazie Playwright, który zawiera już przeglądarki, więc nie ma kroku playwright install.

.github/workflows/e2e.yml
name: e2e
on: pull_request
permissions:
contents: read
jobs:
e2e:
# Forked pull requests get no secrets, so auth.setup.ts would fail; skip them
if: github.event.pull_request.head.repo.full_name == github.repository
runs-on: ubuntu-latest
container:
image: mcr.microsoft.com/playwright:v1.63.0-noble # same image that made the baselines
options: --user 1001
strategy:
fail-fast: false
matrix:
shard: [1/4, 2/4, 3/4, 4/4]
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: 22
- run: npm ci
- run: npx playwright test --shard=${{ matrix.shard }}
env:
E2E_USER_EMAIL: ${{ secrets.E2E_USER_EMAIL }}
E2E_USER_PASSWORD: ${{ secrets.E2E_USER_PASSWORD }}
- uses: actions/upload-artifact@v7
if: ${{ !cancelled() }}
with:
name: blob-report-${{ strategy.job-index }}
path: blob-report
retention-days: 7
report:
if: ${{ !cancelled() && github.event.pull_request.head.repo.full_name == github.repository }}
needs: e2e
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v7
with:
persist-credentials: false
- uses: actions/setup-node@v7
with:
node-version: 22
- run: npm ci
- uses: actions/download-artifact@v8
with:
path: all-blob-reports
pattern: blob-report-*
merge-multiple: true
- run: npx playwright merge-reports --reporter html ./all-blob-reports
- uses: actions/upload-artifact@v7
with:
name: playwright-report
path: playwright-report
retention-days: 14

Ślady (traces) nieudanych testów (trace: 'retain-on-failure') trafiają do scalonego raportu. Podlinkuj ten artefakt w pakiecie dowodów pull requesta, żeby recenzent otworzył krok, który zawiódł, zamiast uruchamiać test ponownie. Krótkotrwałe środowiska dla gałęzi opisuje strona o środowiskach efemerycznych.

Skąd wiesz, że zestaw łapie regresje, skoro go nie czytasz?

Dział zatytułowany „Skąd wiesz, że zestaw łapie regresje, skoro go nie czytasz?”

Zielony zestaw dowodzi tylko tego, że testy przeszły. Cztery kontrole dowodzą, że zestaw nie przejdzie, gdy produkt się zepsuje, i żadna z nich nie wymaga czytania testów.

  1. Każdy nowy test jest czerwony na starym kodzie. Uruchom nowy test na gałęzi bazowej w osobnym worktree; musi zakończyć się błędem. Najpierw uruchom tam npm ci i daj temu worktree własny port przez E2E_BASE_URL, na którym nic innego nie nasłuchuje; konfiguracja powyżej uruchamia serwer deweloperski właśnie na tym porcie. W przeciwnym razie reuseExistingServer podłączy się do serwera deweloperskiego z głównego checkoutu i test sprawdzi nowy kod zamiast starego. Prompt do tego dowodu „najpierw czerwony, potem zielony” znajdziesz w weryfikacji w przeglądarce prowadzonej przez agenta.

  2. Podłożone błędy zostają wykryte. Zepsuj celowo każdą kluczową ścieżkę, po jednym błędzie naraz, i policz, ile z nich zestaw wykrywa. Poniższy prompt robi to jako jednorazowe ćwiczenie; siła wyroczni zamienia je w śledzony wynik mutacyjny.

  3. Niestabilność zatrzymuje build. failOnFlakyTests z jedną ponowną próbą sprawia, że test przechodzący dopiero za drugim razem zmienia zadanie na czerwone, zamiast się ukryć.

  4. Osłabienie zostaje oznaczone. Krok CI kończy się błędem, gdy pull request usuwa linie z expect albo dodaje pominięcie testu lub sztuczne opóźnienie w tests/e2e/:

    # Steps in a job of a workflow that runs on pull_request
    - uses: actions/checkout@v7
    with: { fetch-depth: 0, persist-credentials: false } # the diff needs the base branch's history
    - name: Flag changes that weaken the E2E oracle
    env: { BASE: '${{ github.base_ref }}' } # main, master, develop: whatever the PR targets
    run: |
    diff=$(git diff "origin/$BASE...HEAD" -- tests/e2e)
    removed=$(printf '%s\n' "$diff" | grep -cE '^-.*\bexpect\(' || true)
    added=$(printf '%s\n' "$diff" | grep -cE '^\+.*(test\.(skip|fixme)|waitForTimeout)' || true)
    if [ "$removed" -gt 0 ] || [ "$added" -gt 0 ]; then
    echo "E2E oracle weakened: $removed expect lines removed, $added skip/fixme/sleep lines added"
    exit 1
    fi

    Przeniesienie asercji też uruchamia alarm. To celowe: przeniesienie ogląda człowiek. Zrób z tego osobną, niewymaganą kontrolę i traktuj czerwony wynik jako wymagający zgody właściciela tests/e2e/ z CODEOWNERS.

Odbiór należy do ludzi i CI. Tech lead jest właścicielem JOURNEYS.md i zatwierdza zmiany wzorców oraz asercji; zadanie CI jest bramką merge’a; recenzent sprawdza listę ścieżek, tabelę podłożonych błędów i artefakt z raportem zamiast linii testów.

Agenci znają dwa szybkie sposoby, by niestabilny test zrobił się zielony: dodać sztuczne opóźnienie albo poluzować asercję. Oba ukrywają błąd, dla którego test istnieje. Zabroń ich w prompcie i żądaj dowodów.

AwariaCo widziszJak wyjść
Asercje tylko na toaścieTesty przechodzą, choć zapisane dane są błędneWymagaj przeładowania lub drugiej strony w każdej ścieżce; uruchom prompt z podłożonymi błędami, żeby znaleźć luki
Agent zatwierdził zepsuty zrzutPull request aktualizuje wzorce .png razem ze zmianą UITrzymaj updateSnapshots: 'none' w CI i CODEOWNERS na katalogach ze zrzutami; oglądaj obraz różnic, nie listę plików
Niewłaściwy serwer deweloperskiZestaw przechodzi lokalnie na serwerze innego worktree na tym samym porcieDaj każdemu worktree własny port przez E2E_BASE_URL i uruchamiaj jego serwer deweloperski na tym porcie, jak robi to konfiguracja powyżej; reuseExistingServer podłącza się do czegokolwiek, co odpowiada pod url
Równolegle działający agenci kolidują w przeglądarcePrzeglądarka się nie uruchamia albo agent widzi sesję innegoUruchamiaj Playwright MCP z --isolated lub osobnym --user-data-dir dla każdego worktree
Healer albo agent naprawiający test osłabia goCzerwony test robi się zielony po zmianie expect albo nowym test.fixme()Kontrola osłabienia zmienia się na czerwoną, a CODEOWNERS na tests/e2e/ kieruje pull request do jego właściciela; wycofaj zmianę testu i napraw produkt albo lokator
Zestaw zbyt wolny na każdy pull requestProgramiści i agenci go pomijająPodziel na shardy; oznacz kluczowe ścieżki (test('…', { tag: '@critical' }, …)) i uruchamiaj --grep @critical na każdym pull requeście, a pełny zestaw nocą
Wzorce różnią się tylko w CIRóżnice w fontach lub antyaliasingu psują porównanie każdego zrzutuGeneruj i porównuj wzorce w tym samym obrazie kontenera; maskuj dynamiczne obszary z dużą ilością tekstu
Iframe zewnętrznej usługi blokuje przepływWidżety płatności lub CAPTCHA zawodzą w teścieUżyj trybu testowego i kluczy testowych dostawcy albo zastąp dostawcę na poziomie sieci przez page.route