Automatyzacja testów end-to-end
ENZestaw 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.
Co daje zestaw E2E utrzymywany przez agenta
Dział zatytułowany „Co daje zestaw E2E utrzymywany przez agenta”- 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łąkanetest.onlyi 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, hydratacja | Kontrakt łączy usługi: testy API albo testy integracyjne |
| Regresja kosztowałaby przychód lub zaufanie, zanim wyłapie ją monitoring | Sprawdzenie 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ć.
Przygotuj zestaw, zanim agent napisze pierwszy test
Dział zatytułowany „Przygotuj zestaw, zanim agent napisze pierwszy test”Agent kopiuje to, co robi istniejący zestaw. Zapisz reguły najpierw w konfiguracji i fixture’ach, żeby pierwszy wygenerowany test je odziedziczył.
-
Zainstaluj Playwright Test i jedną przeglądarkę w terminalu:
Okno terminala npm init playwright@latest -- --quiet --browser=chromium --install-deps -
Zastąp wygenerowany
playwright.config.tskonfiguracją, 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 lookexport default defineConfig({testDir: './tests/e2e',fullyParallel: true,forbidOnly: CI, // a stray test.only fails the runretries: CI ? 1 : 0, // one retry so Playwright can label a test flaky…failOnFlakyTests: CI, // …and the flaky label fails the jobupdateSnapshots: CI ? 'none' : 'missing', // CI never writes a baselinereporter: 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 --porturl: baseURL,reuseExistingServer: !CI,},});Domyślna wartość
updateSnapshotsto'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. -
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/loginna własny endpoint logowania i używaj jednorazowego konta testowego, nigdy prawdziwego użytkownika. -
Chroń wyrocznię: dodaj
tests/e2e/,playwright.config.tsi katalogi ze zrzutami doCODEOWNERS, żeby każdą ich zmianę zatwierdzał człowiek. Ochrona wyroczni opisuje reguły, które w ogóle blokują agentowi edycję tych plików.
Podłącz agenta do przeglądarki
Dział zatytułowany „Podłącz agenta do przeglądarki”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:
# Browser tools for this project; --isolated keeps parallel agents from sharing a profileclaude mcp add playwright -- npx @playwright/mcp@latest --isolated
# Optional: Playwright's planner, generator and healer subagentsnpx playwright init-agents --loop=claudeinit-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.
codex mcp add playwright -- npx @playwright/mcp@latest --isolated
# Optional: the same three test agents, written to .codex/agents/npx playwright init-agents --loop=codexDodaj serwer do .cursor/mcp.json:
{ "mcpServers": { "playwright": { "command": "npx", "args": ["@playwright/mcp@latest", "--isolated"] } }}init-agents w wersji 1.63.0 nie ma pętli dla Cursora (dostępne wartości to claude, codex, copilot, opencode, vscode i vscode-legacy), więc użyj serwera MCP z promptami z tej strony.
Ż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.
Generuj testy z kryteriów akceptacji
Dział zatytułowany „Generuj testy z kryteriów akceptacji”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:
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.
name: e2eon: pull_requestpermissions: contents: readjobs: 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.
-
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 cii daj temu worktree własny port przezE2E_BASE_URL, na którym nic innego nie nasłuchuje; konfiguracja powyżej uruchamia serwer deweloperski właśnie na tym porcie. W przeciwnym raziereuseExistingServerpodłą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. -
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.
-
Niestabilność zatrzymuje build.
failOnFlakyTestsz jedną ponowną próbą sprawia, że test przechodzący dopiero za drugim razem zmienia zadanie na czerwone, zamiast się ukryć. -
Osłabienie zostaje oznaczone. Krok CI kończy się błędem, gdy pull request usuwa linie z
expectalbo dodaje pominięcie testu lub sztuczne opóźnienie wtests/e2e/:# Steps in a job of a workflow that runs on pull_request- uses: actions/checkout@v7with: { fetch-depth: 0, persist-credentials: false } # the diff needs the base branch's history- name: Flag changes that weaken the E2E oracleenv: { BASE: '${{ github.base_ref }}' } # main, master, develop: whatever the PR targetsrun: |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 ]; thenecho "E2E oracle weakened: $removed expect lines removed, $added skip/fixme/sleep lines added"exit 1fiPrzeniesienie 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/zCODEOWNERS.
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.
Napraw niestabilny test E2E, nie osłabiając go
Dział zatytułowany „Napraw niestabilny test E2E, nie osłabiając go”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.
Kiedy zestaw E2E pisany przez agenta zawodzi
Dział zatytułowany „Kiedy zestaw E2E pisany przez agenta zawodzi”| Awaria | Co widzisz | Jak wyjść |
|---|---|---|
| Asercje tylko na toaście | Testy przechodzą, choć zapisane dane są błędne | Wymagaj przeładowania lub drugiej strony w każdej ścieżce; uruchom prompt z podłożonymi błędami, żeby znaleźć luki |
| Agent zatwierdził zepsuty zrzut | Pull request aktualizuje wzorce .png razem ze zmianą UI | Trzymaj updateSnapshots: 'none' w CI i CODEOWNERS na katalogach ze zrzutami; oglądaj obraz różnic, nie listę plików |
| Niewłaściwy serwer deweloperski | Zestaw przechodzi lokalnie na serwerze innego worktree na tym samym porcie | Daj 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ądarce | Przeglądarka się nie uruchamia albo agent widzi sesję innego | Uruchamiaj Playwright MCP z --isolated lub osobnym --user-data-dir dla każdego worktree |
| Healer albo agent naprawiający test osłabia go | Czerwony 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 request | Programiś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 CI | Różnice w fontach lub antyaliasingu psują porównanie każdego zrzutu | Generuj i porównuj wzorce w tym samym obrazie kontenera; maskuj dynamiczne obszary z dużą ilością tekstu |
| Iframe zewnętrznej usługi blokuje przepływ | Widżety płatności lub CAPTCHA zawodzą w teście | Użyj trybu testowego i kluczy testowych dostawcy albo zastąp dostawcę na poziomie sieci przez page.route |