Przejdź do głównej zawartości

E2E agent-driven — agent sam jeździ po przeglądarce zanim powie 'done'

Pytanie ze scorecard: Jak pokrywasz E2E “feature działa w przeglądarce”? Sekcja: Równoległość i automatyzacja. Odpowiedź na maks (3 pkt): Agent sam odpala E2E (Playwright MCP, browser-harness) i weryfikuje feature.

Type-checks i unit testy weryfikują kod. Nie weryfikują featurów. Kiedy kończysz zmianę w UI i jedyna rzecz między “diff napisany” a “shipped” to npm run type-check && npm test, nie udowodniłeś niczego o tym, co realnie zobaczy człowiek wchodzący na Twoją stronę. Przycisk może tam być, podpięty do właściwego handlera, z przechodzącym unit testem na handlerze — i wciąż produkować pusty ekran, bo zmieniła się kolejność hydratacji w Astro, albo zabłąkany CSS przesunął go o 600px w prawo, albo dropdown, który go napędza, otwiera się teraz za modalem. Każdy developer, który shipował UI, zna tę kategorię bugów “wszystkie checki przeszły, popsuło się na produkcji”. Przez 2024–2025 odpowiedzią na nią było “no to napisz więcej Playwright testów” — czego większość zespołów nie robiła, bo ręczne autorowanie suite’ów E2E było żmudne, testy były flaky, a PR-y spowalniały. W 2026 ta kalkulacja się odwróciła. Agent, który właśnie napisał diff, może też prowadzić prawdziwą przeglądarkę, kliknąć rzecz, którą właśnie shipnął, przeczytać wyrenderowaną stronę i potwierdzić, że feature działa — w sekundy, na każdej zmianie, bez tego, żeby ktokolwiek autorował plik testowy.

Zmiana jest strukturalna, nie kosmetyczna. Playwright 1.56 (październik 2025) wysłał Test Agents i utwardzony serwer Playwright MCP, co oznacza, że ta sama pętla agenta, która edytuje Twój TypeScript, może teraz wejść na localhost:4321, otworzyć nowy modal i potwierdzić, że przycisk submit jest widoczny. browser-harness, agent-browser i chrome-devtools MCP dodały podobne powierzchnie do kontroli na poziomie CDP bez pisania pełnego suite’u testów. Wynik: najtańsza możliwa warstwa E2E to już nie “napisz spec Playwright i odpal go w CI” — to “poproś agenta, żeby zweryfikował, że feature działa, zanim raportnie done”. Zespoły, które zinternalizowały to w 2026, ścięły kategorię regresji niemal do zera i przestały potrzebować rozbudowanych ręcznie autorowanych suite’ów E2E, które wcześniej zżerały kwartał cykli QA. Q18 to pytanie, które mierzy, czy zrobiłeś ten skok. Wynik max oznacza, że agent sam prowadzi przeglądarkę jako część kończenia taska UI — nie “mamy suite Playwright, który kiedyś odpala się w CI” i zdecydowanie nie “poklikam ręcznie przed mergem”.

Setup na maks Q18 ma trzy warstwy. Po pierwsze, agent robiący Twoją robotę w UI ma dostęp do prawdziwej przeglądarki przez jedną ze wspieranych powierzchni — Playwright MCP, browser-harness, agent-browser albo chrome-devtools MCP wpięty przez .mcp.json / ~/.claude/mcp.json / ~/.codex/config.toml. Po drugie, agent używa tej powierzchni jako normalnego kroku w kończeniu taska UI, nie jako osobnego rytuału. Po zmianie komponentu nawiguje na dev server, robi screenshot albo accessibility snapshot, potwierdza wyrenderowany output i dopiero wtedy raportuje done. Po trzecie — i to jest krok, który dzieli 2 pkt od 3 — ta weryfikacja jest wymuszona przez Stop hook albo własny slash command, więc nie da się jej pominąć w zmęczony piątkowy wieczór. Agent dosłownie nie może powiedzieć “done” bez dostarczenia dowodu (screenshot, przechodzący asercja, zacytowany fragment DOM), że feature się renderuje.

Typowy zmaksowany flow w projekcie Claude Code z Playwright MCP wygląda tak. Inżynier mówi “dodaj dialog potwierdzający do przycisku delete na stronie ustawień”. Agent czyta komponent, pisze zmianę, odpala npm run type-check && npm test. Na razie to samo, co 2 pkt. Potem — bo Stop hook sprawdza “czy ruszyłeś jakikolwiek plik .astro / .tsx / .jsx pod src/?” i odpowiedź brzmi tak — hook wstrzykuje krok weryfikacji. Agent woła browser_navigate("http://localhost:4321/settings"), robi accessibility snapshot, woła browser_click({ ref: "delete-button" }), potwierdza, że tekst dialogu pojawia się w następnym snapshocie, i dopiero wtedy pisze podsumowanie zawierające ten snapshot. Cała pętla dodaje 10–20 sekund. Wyłapuje całą klasę bugów, w której unit test przechodzi, ale wyrenderowana strona jest popsuta — kolejność hydratacji, regresje CSS, brakujące importy, źle skonfigurowane islands w Astro, przypadkowy display:none. Produkuje też screenshot albo snapshot, który można wkleić do opisu PR-a, co podwaja się jako dokumentacja dla reviewera.

Porównaj to z niższymi poziomami. 0 pkt: brak E2E w ogóle — shippujesz, jeśli unit testy przechodzą, i mergujesz, jeśli “wygląda OK na staging URL”. 1 pkt: ręcznie autorowany suite Playwright/Cypress, który odpala się w CI, ale agent go nie dotyka, a weryfikacja to “poczekaj aż CI zazieleni”. 2 pkt: agent może odpalić Playwright na wyraźną prośbę, ale weryfikacja nie jest częścią domyślnej pętli — musisz pamiętać, żeby powiedzieć “teraz jeszcze zweryfikuj w przeglądarce”, a 4 na 10 razy zapominasz. 3 pkt: agent weryfikuje domyślnie, wymuszone przez hooks; diff nigdy nie jest raportowany jako done bez dowodu z przeglądarki.

Powierzchnia “agent prowadzi przeglądarkę” w 2026 to warstwowy stos. Na dole jest Chrome DevTools Protocol (CDP), ten sam protokół, którego Puppeteer i Playwright używają wewnętrznie. Na CDP siedzą cztery opcje skierowane do agentów, które mają znaczenie dla Q18, każda z innym mocnym miejscem. Nie potrzebujesz wszystkich czterech. Potrzebujesz jednej wpiętej w swojego głównego agenta — ale zrozumienie kompromisów chroni przed wyborem złej opcji dla Twojego repo.

Playwright MCP to oficjalny serwer Model Context Protocol od Microsoftu, wysłany jako część projektu Playwright i zweryfikowany pod playwright.dev/docs/getting-started-mcp. Eksponuje pełne API Playwright jako narzędzia MCP, które dowolny agent zdolny do MCP (Claude Code, Codex CLI, Cursor) może wywołać. Kluczowa decyzja projektowa: nie napędza agenta screenshotami. Zamiast tego zwraca drzewo accessibility przeglądarki jako uporządkowany, tekstowy snapshot — typowo 2–5 KB na stronę vs 100+ KB na screenshot, co daje różnicę kosztu tokenów 20–50x. Agent czyta snapshot, wybiera deterministyczną referencję do elementu (ref: "delete-button-7"), a narzędzie MCP dispatchuje kliknięcie na ten dokładny węzeł. Interakcja jest bezselektorowa z perspektywy agenta, ale deterministyczna na drucie — nie ma rozmytego kroku LLM-vision pośrodku, co utrzymuje szybkość, niski koszt i powtarzalność.

Instalacja (Claude Code):

Okno terminala
claude mcp add playwright --transport http http://localhost:8931
# albo z npx podczas deva:
npx -y @playwright/mcp@latest

Albo przez .mcp.json w korzeniu repo, żeby zespół podchwycił to po klonie:

{
"mcpServers": {
"playwright": {
"command": "npx",
"args": ["-y", "@playwright/mcp@latest"]
}
}
}

Po instalacji agent może wołać browser_navigate, browser_snapshot, browser_click, browser_type, browser_evaluate itd. Wydanie 1.56 dodało trzy “Test Agents” — Planner, Generator, Healer — ale dla Q18 głównym zastosowaniem jest weryfikacja in-place, nie autorowanie testów. Poproś agenta o weryfikację strony po zmianie UI; nawiguje, snapshotuje, asercja, raport. Jeśli Twój stack jest gdziekolwiek blisko “Microsoft, Playwright, accessibility-first”, to jest domyślny wybór.

Serwer chrome-devtools MCP (mcp__plugin_chrome-devtools-mcp_chrome-devtools__* w wielu setupach Claude Code) to opcja, gdy potrzebujesz konkretnie powierzchni DevTools: audytów Lighthouse, trace’ów performance, snapshotów pamięci, inspekcji request’ów sieciowych na poziomie protokołu. Odpala własną instancję headless Chrome na sesję agenta i eksponuje narzędzia takie jak lighthouse_audit, performance_start_trace, list_network_requests, take_memory_snapshot. Do zwykłej weryfikacji “czy przycisk zadziałał?” jest overkillem — Playwright MCP jest szybszy i tańszy — ale dla “czy nowy feature pogorszył LCP?” albo “czy nowy skrypt analytics odpala się na tej stronie?” jest właściwym narzędziem. Kompromis: odpala świeży Chromium na sesję, co oznacza, że strony chronione przez boty (Cloudflare, PerimeterX, Skyscanner) walą captchą natychmiast. Używaj chrome-devtools MCP do swojego localhost; używaj przeglądarki z dołączoną sesją do wszystkiego, co potrzebuje zalogowanego stanu usera.

Częsta pułapka: chrome-devtools MCP potrafi zostawić nieaktualne instancje Chrome. Jeśli new_page rzuca błąd “browser is already running for …/chrome-devtools-mcp/chrome-profile”, odpal pkill -f "chrome-devtools-mcp/chrome-profile" i retry. Wbij to w swój Stop hook, jeśli intensywnie używasz tego narzędzia.

browser-harness to obudowa oparta na CDP zaprojektowana konkretnie pod workflow agent-driven. Podpina się pod uruchomiony Chrome użytkownika (zamiast odpalać własny), eksponuje małe Pythonowe API (new_tab, wait_for_load, click, screenshot, js, page_info, http_get, cdp) i działa jako długo żyjący daemon przez unix socket, dzięki czemu agent nie płaci kosztu startu na każde wywołanie. Kształt wywołania narzędzia to pojedynczy bash heredoc z inline Pythonem:

Okno terminala
browser-harness <<'PY'
new_tab("http://localhost:4321/settings")
wait_for_load()
click(640, 412) # współrzędne z wcześniejszego screenshot()
screenshot() # zweryfikuj, że dialog się pojawił
print(page_info())
PY

Definiująca siła to to, że dzieli sesję Chrome użytkownika. To różnica między przejściem przez bot-check Cloudflare a captcha-wallem na pierwszym requeście — harness widzi te same cookies, ten sam zalogowany stan, te same rozszerzenia co użytkownik. Do weryfikacji zalogowanego flow na staging albo interakcji z trzecią stroną, która bot-blokuje świeże instancje Chromium, to jest opcja. Druga siła to to, że jest celowo mały — kliknięcia współrzędnymi przez Input.dispatchMouseEvent przechodzą przez iframes, shadow DOM i granice cross-origin na poziomie kompozytora, co jest bardziej odporne niż kliknięcia oparte na DOM w skomplikowanych aplikacjach. Kompromis: kliknięcie współrzędnymi jest mniej stabilne niż kliknięcie po referencji accessibility, gdy layout się zmienia, więc Playwright MCP wygrywa na weryfikacji komponentu na czystym localhost, a browser-harness wygrywa na flow cross-origin i autentykowanych.

agent-browser to ogólnego przeznaczenia CLI do automatyzacji przeglądarki zbudowane dla agentów AI, wystawione jako skill (agent-browser skill w wielu setupach Claude Code). Pokrywa się z browser-harness na osi “podpięty do prawdziwej przeglądarki”, ale jest bardziej CLI-kształtne, mniej Python-API-kształtne. Używaj go, gdy chcesz oskryptować wyżej-poziomowe operacje (“otwórz tę stronę, wypełnij ten formularz, zrób screenshot, zwróć JSON”) bez pisania imperatywnych kroków samodzielnie. Workflow core skilla zajmuje się odkrywaniem dostępnych subkomend; odpal agent-browser skills get core, żeby przeczytać kanoniczny workflow przed prowadzeniem go. Dla Q18 konkretnie agent-browser jest dobrą drugorzędną powierzchnią do połączenia z Playwright MCP — Playwright do “czy nowy komponent jest na stronie”, agent-browser do “czy cały signup flow nadal działa end-to-end”.

Nie każdy diff potrzebuje weryfikacji w przeglądarce, a dokręcanie śruby za mocno robi pętlę irytującą. Sensowna polityka:

  • Zawsze weryfikuj w przeglądarce: zmiany w .astro / .tsx / .jsx / .vue / .svelte pod src/, zmiany w .css / tailwind.config.* wpływające na publiczne strony, zmiany w routach API, do których woła frontend, zmiany w skrypcie paywalla.
  • Pomiń weryfikację: czyste zmiany backendowe (migracja DB bez powierzchni UI), zmiany tylko konfiguracji, dokumentacja MDX, wewnętrzne skrypty. Stop hook powinien matchować po ścieżce, żeby to było automatyczne.
  • Weryfikuj na staging zamiast localhost: zmiany zależne od Cloudflare bindings, zewnętrznego OAuth albo featurów edge runtime, które nie odpalają się na npm run dev. Użyj browser-harness przeciwko URL stagingu z zalogowaną sesją usera.

Hook decyduje, nie człowiek. Jeśli glob ścieżki matchuje, agent weryfikuje; jeśli nie, nie weryfikuje. To różnica między dyscypliną a uciążliwością.

Wdrożenie krok po kroku: E2E weryfikacja jako krok Stop hooka

Dział zatytułowany „Wdrożenie krok po kroku: E2E weryfikacja jako krok Stop hooka”
  1. Wybierz powierzchnię i potwierdź, że agent może ją prowadzić. Z korzenia repo zapytaj swojego głównego agenta: “wymień narzędzia browser, które masz dostępne”. Jeśli pojawi się Playwright MCP, chrome-devtools MCP albo browser-harness, jesteś ustawiony. Jeśli nic związanego z przeglądarką nie jest wymienione, najpierw zainstaluj Playwright MCP (claude mcp add playwright ... dla Claude Code; odpowiednik w ~/.codex/config.toml dla Codex; panel MCP w Cursor Agents).

  2. Wystaw dev server pod znany stabilny URL. Potwierdź, że npm run dev podnosi localhost:4321 (default Astro) niezawodnie. Jeśli Twój projekt potrzebuje Cloudflare bindings, użyj raczej npm run dev:cf (Wrangler + Workers) i dostosuj URL. Agent uderzy w ten URL podczas weryfikacji, więc musi się podnosić niezawodnie ze startu na zimno.

  3. Stwórz slash command do weryfikacji. Dodaj .claude/commands/verify-ui.md (albo odpowiednik w .cursor/skills/):

    ---
    description: Zweryfikuj, że zmiana w UI działa w prawdziwej przeglądarce
    argument-hint: "[ścieżka URL, default /]"
    ---
    Upewnij się, że dev server jest uruchomiony na http://localhost:4321 (odpal
    go z `npm run dev` w bashu w tle, jeśli nie jest).
    Nawiguj na http://localhost:4321$ARGUMENTS używając swojego narzędzia
    browser MCP (Playwright MCP preferowany, browser-harness jako fallback).
    Zrób accessibility snapshot wyrenderowanej strony. Potwierdź:
    - Komponent, który właśnie zmieniłem, jest obecny i widoczny.
    - Brak błędów w konsoli.
    - Brak ostrzeżeń o layout-shift.
    - Jeśli zmiana dodaje interakcję (przycisk, modal, formularz), wywołaj ją raz
    i zweryfikuj wynikowy stan.
    Wyrzuć podsumowanie snapshotu plus jedno-linijkowy werdykt: SHIPPABLE albo BROKEN.

    To robi z weryfikacji jeden keystroke jeszcze zanim hook ją zautomatyzuje.

  4. Dodaj Stop hook. W ~/.claude/settings.json (albo .claude/settings.json, jeśli ma być scoped do repo):

    {
    "hooks": {
    "Stop": [
    {
    "matcher": "",
    "hooks": [
    {
    "type": "command",
    "command": "bash -c 'git diff --name-only HEAD | grep -E \"\\.(astro|tsx?|jsx?|vue|svelte|css)$\" -q && echo \"Zmienione pliki UI — odpal /verify-ui przed raportowaniem done\"'"
    }
    ]
    }
    ]
    }
    }

    Hook odpala się na każdym Stop. Jeśli pliki UI się zmieniły, wstrzykuje przypomnienie dla agenta, żeby odpalił /verify-ui przed końcem tury. Pierwsza wersja jest celowo przypomnieniem, nie twardą bramką — pozwól agentowi zdecydować, którą ścieżkę zweryfikować. Dokręć, gdy rytm będzie wbity.

  5. Odpal pętlę na prawdziwym tickecie. Wybierz mały, ale prawdziwy ticket UI (zmiana etykiety przycisku, nowe pole formularza, poprawka dialogu). Pozwól agentowi edytować, type-checkować i testować. Patrz, jak trafia w Stop hook, odpala /verify-ui /dotknięta-strona i produkuje snapshot. Przeczytaj snapshot. Czy realnie potwierdza zmianę? Jeśli tak, zmaksowałeś Q18 w tym repo. Jeśli nie, slash command potrzebuje doostrzenia — zwykle lepszej asercji na “nowy element jest obecny z nowym tekstem”.

  6. Wrzuć screenshot/snapshot do PR-a. Przekieruj output snapshotu do opisu PR-a. Reviewerzy przestają pytać “czy to faktycznie testowałeś?”, bo odpowiedź jest tam. To efekt produktywności drugiego rzędu — weryfikacja staje się dokumentacją.

  7. Dodaj drugi tier: flow cross-origin / autentykowane. Gdy weryfikacja localhost jest automatyczna, dodaj /verify-staging command, który uderza w Twój staging URL przez browser-harness z zalogowaną sesją Chrome usera. Używaj do zmian dotykających auth, płatności albo integracji third-party.

  8. Dokręcaj hook z czasem. Gdy zespół jest komfortowy, eskaluj Stop hook z “przypomnienia” na “blokera” — exit code 2 z komunikatem STOP: zweryfikuj w przeglądarce przed zakończeniem. Agent traktuje to jako twardy sygnał, żeby wywołać /verify-ui przed końcem tury. Nie eskaluj przed wbudowaniem mięśnia; tylko zirytujesz ludzi do wyłączenia hooka.

  9. Wepnij CI w te same komendy Playwright. Gdy agent produkuje niezawodną weryfikację lokalnie, te same komendy Playwright MCP można promować do kroku npm run test:e2e w CI. Wersja CI nie zastępuje weryfikacji agenta — backupuje ją na wypadek, gdy ktoś obejdzie hook albo commitnie z innej maszyny.

  10. Retro kwartalne. Co kwartał patrz na bugi, które jednak przeleciały na produkcję. Ile z nich to “UI się popsuł mimo przechodzących testów”? W setupie zmaksowanym pod Q18 ta kategoria powinna być bliska zera. Jeśli nie jest, slash command weryfikacji brakuje asercji — zwykle wokół interaktywności (element się renderuje, ale click handler jest zły) albo responsywności (działa na desktopie, ale wali się na mobile).

  • Mockowanie całego frontendu w unit testach i nazywanie tego “pokryte”. 90% pokrycia Vitest, który mockuje router, klient API i globalny store, dowodzi, że logika jednostkowa działa w izolacji, ale nie dowodzi nic o tym, czy złożona strona się renderuje. Weryfikacja w przeglądarce nie jest substytutem unit testów; jest warstwą nad nimi. Obie są potrzebne.
  • Ręczne autorowanie testów Playwright zamiast pozwolić agentowi weryfikować. Przez 2024–2025 standardową radą było “pisz więcej testów Playwright”. W 2026 z Test Agents i MCP ręczne autorowanie to wolna ścieżka. Autoryzuj testy dla flow zasługujących na permanentne pokrycie CI (signup, checkout, login). Pozwól agentowi weryfikować wszystko inne ad-hoc na każdej zmianie.
  • Prowadzenie świeżego Chromium przeciwko stronom chronionym przed botami. Playwright MCP i chrome-devtools MCP odpalają świeże przeglądarki, które trzaskają w bot-walle Cloudflare/PerimeterX/Skyscanner natychmiast. Jeśli weryfikacja potrzebuje prawdziwej sesji usera, użyj browser-harness albo Playwright z atachem — dzielą istniejącą sesję Chrome usera.
  • Kliknięcia współrzędnymi ze nieaktualnego screenshota. browser-harness używa współrzędnych viewportu, nie pikseli ze screenshota. Gdy screenshot() zwraca pomniejszony obraz, przeskaluj przed click(x, y) albo pomiń współrzędne i kliknij przez js("document.querySelector('button').click()"). To najczęstszy tryb awarii “weryfikacja powiedziała sukces, a klik się nie zdarzył”.
  • Brak wait_for_load() po nawigacji. Asynchroniczna hydratacja oznacza, że DOM jest zamontowany, ale interaktywność jeszcze nie jest podpięta. Zawsze wołaj wait_for_load() (browser-harness) albo browser_wait_for (Playwright MCP) przed asercją na interaktywnym stanie, w przeciwnym razie dostaniesz false-negative, który wygląda dokładnie jak prawdziwy bug.
  • Pozwalanie agentowi klikać losowe rzeczy, żeby “zweryfikować”. Slash command weryfikacji potrzebuje konkretnych asercji: “przycisk z etykietą ‘Potwierdź’ jest widoczny”, “modal zawiera tekst ‘Czy na pewno?’”, nie “strona wygląda OK”. Bez jawnych asercji agent zaraportuje sukces na pustej stronie.
  • Pomijanie weryfikacji na “trywialnych” zmianach. “To tylko zmiana koloru” to najczęstszy prekursor produkcyjnej regresji CSS. Stop hook powinien odpalać się na każdej zmianie pliku UI, kropka. Dwie sekundy weryfikacji biją dwie godziny post-mortemu.
  • Traktowanie Q18 jako “mamy Cypress w CI”. Suite E2E w CI jest dobry, ale nie maksuje Q18. Pytanie dotyczy konkretnie tego, czy agent weryfikuje podczas pracy — pętli feedbacku, która łapie bug zanim trafi do CI, zanim trafi do reviewera, zanim trafi na produkcję.
  • Twój główny agent ma co najmniej jedną powierzchnię do prowadzenia przeglądarki zainstalowaną i wymienioną w swoich narzędziach (Playwright MCP, browser-harness, agent-browser albo chrome-devtools MCP).
  • Stop hook (albo odpowiednik w Codex / Cursor) odpala się na zmianach plików UI i przypomina agentowi o weryfikacji.
  • Slash command /verify-ui istnieje i odpala się niezawodnie na dev serverze ze startu na zimno.
  • Ostatnie pięć PR-ów UI w Twoim repo zawiera screenshot albo accessibility snapshot w opisie PR-a.
  • Możesz wymienić regresję z ostatniego kwartału, którą weryfikacja w przeglądarce złapała przed mergem — i zero regresji typu “przeszło wszystkie checki, popsuło się na produkcji”.
  • Nowi w zespole dostają ten sam flow weryfikacji po git clone — slash command i hook oba są w repo, nie tylko w Twoim prywatnym ~/.claude/.
  • Agent mówi “zweryfikowałem to w przeglądarce na /settings i dialog pojawia się zgodnie z oczekiwaniem” bez pytania.