End-to-End-Testing
Ein End-to-End-Test führt die echte Anwendung aus, die so gebaut und gestartet wird, wie Benutzer sie erhalten, und steuert sie von außen: ein Browser, der durch Seiten klickt, eine Shell, die eine CLI ausführt, oder ein Client, der eine API aufruft. Er fängt ein, was Unit-Tests nicht können: Verdrahtung, Konfiguration, Routing und die Nähte zwischen Teilen. Es ist auch die langsamste und zerbrechlichste Art von Test, daher muss jeder seinen Platz verdienen und so geschrieben sein, dass er bei jedem Durchlauf die gleiche Antwort liefert.
Die Schwester-Skill unit-testing behandelt Tests einzelner Einheiten.
Wenn ein Verhalten auf beiden Ebenen geprüft werden kann, überprüfen Sie es mit einem Unit-Test.
Repo-Regeln haben Vorrang
Verwenden Sie das E2E-Framework, die Konfiguration, den Ordner, die Fixtures und die Skripte des Repositorys. Lesen Sie die
vorhandenen Tests und die Konfiguration (playwright.config.*, cypress.config.* usw., bevor Sie
einen schreiben, und gleichen Sie sie ab. AGENTS.md, CONTRIBUTING.md und die Paket-Skripte
sagen, wie E2E-Tests ausgeführt werden und wogegen. Wo sie mit dieser Skill in Konflikt stehen, gewinnen sie.
Fügen Sie kein zweites E2E-Framework hinzu.
Standardtool
Wenn das Repository noch kein E2E-Tool hat, verwenden Sie Playwright
(@playwright/test) für alles mit einem Browser. Es funktioniert für jede Webanwendung, egal in welcher
Sprache das Backend geschrieben ist. Beispiele hier verwenden Playwright. Die Regeln gelten mit
jedem Tool.
Was einen E2E-Test verdient
- Kritische Journeys: Registrierung, Anmeldung, die Kernaufgabe, für die das Produkt existiert, Bezahlvorgang und alles, was in der Produktion schon einmal kaputt gegangen ist.
- Integrationspunkte: Eine Seite, die das echte Backend, Authentifizierung oder Routing benötigt.
- Ein Happy Path pro Journey, plus die Fehler, auf die ein Benutzer tatsächlich stoßen würde (ein falsches Passwort, eine abgelehnte Zahlung).
Schieben Sie alles andere auf Unit-Tests: jede Validierungsregel, jeden Formatierungsfall, jeden Zweig einer Berechnung. Eine E2E-Suite, die alle Fälle abdecken will, wird zu langsam zum Ausführen und zu flaky, um ihr vertrauen zu können.
Selektoren
Finden Sie Elemente so, wie ein Benutzer es tut, damit Tests Markup- und Stiländerungen überleben.
- Bevorzugen, in dieser Reihenfolge: Rolle und zugänglicher Name (
getByRole('button', { name: 'Speichern' })), Label (getByLabel('E-Mail')), sichtbarer Text, dann eine Test-ID (getByTestId), wenn nichts Benutzersichtbares stabil ist. - Vermeiden: CSS-Klassen, XPath,
nth-childund Elementpositionen. Sie brechen, wenn sich das Layout ändert, und sagen nichts darüber aus, was der Benutzer sieht. - Wenn ein Element nicht nach Rolle oder Label gefunden werden kann, ist das oft ein Barrierefreiheitsfehler. Sagen Sie das, anstatt zu einem brüchigen Selektor zu greifen.
Warten
Warten Sie niemals eine feste Zeit. sleep, waitForTimeout und setTimeout sind auf einer langsamen
CI-Maschine zu kurz und verschwenden auf einer schnellen Zeit.
- Verwenden Sie Assertions, die wiederholt werden, bis sie bestehen oder ein Timeout auftritt:
await expect(page.getByRole('alert')).toHaveText('Gespeichert'). - Warten Sie auf die Bedingung, auf die der Benutzer warten würde: ein sichtbares Element, eine geänderte URL, eine
empfangene Antwort (
page.waitForResponse), eine geschriebene Datei. - Erhöhen Sie keine Timeouts, um einen Fehler zu beheben. Wenn etwas legitim lange dauert, erhöhen Sie das Timeout für diesen einen Schritt und sagen Sie warum.
- Erzwingen Sie keine Aktionen über die Überprüfungen des Frameworks hinweg (
{ force: true }). Wenn ein Klicken erzwungen werden muss, bedeckt etwas das Element oder es ist nicht bereit, und ein echter Benutzer hätte das gleiche Problem.
Testdaten und Isolation
- Jeder Test richtet seinen eigenen Zustand ein und besteht allein, in beliebiger Reihenfolge und parallel zu den anderen. Kein Test ist davon abhängig, dass ein anderer zuerst gelaufen ist.
- Machen Sie Daten pro Test eindeutig (eine generierte E-Mail oder ID), damit parallele Worker und Wiederholungen nicht in Konflikt geraten.
- Richten Sie über die Hintertür ein. Erstellen Sie Benutzer und Datensätze über eine API, ein Seed-Skript oder ein Fixture, nicht durch Klicken durch die Benutzeroberfläche. Nur die getestete Journey geht durch die Benutzeroberfläche.
- Einmal anmelden und wiederverwenden. Speichern Sie den authentifizierten Zustand in einem Setup-Schritt
(Playwrights
storageState), anstatt sich in jedem Test über das Formular anzumelden. - Faken Sie nur, was Sie nicht besitzen. Stubben Sie Drittanbieterdienste (Zahlungen, E-Mail, Karten) an der
Netzwerkgrenze mit
page.route()oder einem lokalen Fake. Behalten Sie Ihr eigenes Frontend, Backend und Ihre Datenbank echt, sonst ist es kein End-to-End-Test.
CLI- und API-End-to-End-Tests
Die gleichen Regeln gelten ohne Browser. Führen Sie CLI-Tests mit dem Unit-Test-Runner des Repositorys aus
(Vitest für JavaScript und TypeScript), in einer separaten Konfiguration oder einem separaten Projekt, damit sie die Unit-Suite
nicht verlangsamen. API-Tests können Playwrights request-Fixture oder den gleichen Runner verwenden.
- Führen Sie das gebaute Artefakt aus, so wie Benutzer es ausführen, nicht den Quellcode über einen test-spezifischen Einstiegspunkt.
- Geben Sie jedem Test ein frisches temporäres Verzeichnis als Arbeitsverzeichnis und zeigen Sie
HOME, Konfigurationspfade und Caches dorthin, damit Tests nicht die echten Dateien des Entwicklers lesen oder ändern. - Überprüfen Sie Exit-Code, stdout, stderr und die erstellten Dateien separat. Ein Test, der nur prüft "er ist nicht geworfen", übersieht falsche Ausgabe.
- Entfernen oder frieren Sie, was sich zwischen Durchläufen ändert (Zeitstempel, temporäre Pfade, Dauern), bevor Sie die Ausgabe vergleichen.
Wenn ein Test fehlschlägt
Lesen Sie die Beweise, bevor Sie etwas ändern.
- Holen Sie sich die Artefakte. Schalten Sie Traces, Screenshots und Video für Fehler ein
(Playwright:
trace: 'on-first-retry',screenshot: 'only-on-failure'), und öffnen Sie den Trace vom CI-Lauf (npx playwright show-trace <trace.zip>). Der Trace zeigt die Seite, das Netzwerk und die Konsole bei jedem Schritt. - Entscheiden Sie, wessen Fehler es ist. Wenn die Anwendung falsch ist, beheben Sie die Anwendung; der Test hat seine Arbeit getan. Wenn der Test falsch ist, beheben Sie den Test. Wenn die Umgebung falsch ist (ein Dienst ausgefallen, ein Port belegt), beheben Sie die Einrichtung.
- Reproduzieren Sie ihn mit dem gleichen Befehl, Browser und den Einstellungen, die CI verwendet, einschließlich headless-Modus und Worker-Anzahl.
- Finden Sie bei einem flaky Test die Race Condition. Die üblichen Ursachen: ein festes Warten, Handeln bevor
die Seite bereit ist, mit einem anderen Test geteilte Daten oder eine Animation oder ein Toast, der das Element
bedeckt. Beheben Sie diese Ursache, dann führen Sie den Test viele Male hintereinander aus
(
npx playwright test <datei> --repeat-each=20 --retries=0), bevor Sie ihn als behoben bezeichnen.
CI-Wiederholungen können eine Pipeline grün halten, aber ein Test, der nur bei Wiederholung bestanden hat, schlägt immer noch fehl. Behandeln Sie "flaky" in einem Bericht als einen zu behebenden Fehler, nicht als Status. Überspringen, löschen oder isolieren Sie niemals einen Test, um ohne Zustimmung des Benutzers grün zu werden, und lockern Sie niemals eine Assertion, um ihn bestehen zu lassen.
Checkliste
Vor der Übergabe von E2E-Tests oder bei deren Überprüfung:
- Die Journey braucht einen E2E-Test. Fälle, die ein Unit-Test prüfen kann, sind Unit-Tests.
- Selektoren verwenden Rolle, Label, Text oder eine Test-ID. Keine CSS-Ketten oder XPath.
- Keine festen Wartezeiten, erzwungene Aktionen oder erhöhte globale Timeouts.
- Jeder Test erstellt eigene eindeutige Daten und besteht allein und parallel.
- Nur Drittanbieterdienste werden gestubbt.
- Fehler hinterlassen einen Trace oder Screenshot in CI.
- Der neue oder reparierte Test bestand viele Male hintereinander ohne Wiederholungen.