Zurück zum Blog
EngineeringNovember 6, 20267 Min. Lesezeit

Ein Roboter hat jeden Button geklickt. Er fand acht Sicherheitslücken.

Schluss mit Einzelteil-Tests. Wir testen jetzt Seiten. Echter Browser, echter Server, echte Klicks - auf jedem Endpoint, den Kunden berühren.

Unser Code bestand Typprüfung, Linter und Unit-Tests. Und hatte trotzdem Bugs. Keine exotischen - ein Reset-Formular, das das Bestätigungspasswort ignorierte, Rechnungslinks ins Leere, ein Feed mit gelöschten Threads.

Jeder davon steckte in der Lücke zwischen den Teilen. Die Funktion war korrekt. Die Seite drumherum nicht. Also haben wir ein Testframework gebaut, das sich nur für eins interessiert: was ein Kunde wirklich sieht und abschickt.

Warum die echte Seite testen?

Jede Testebene fängt eine andere Art von Fehler. Nur die letzte sieht, was deine Nutzer sehen.

Unit-Test

Prüft eine Funktion

Schnell und billig. Blind für die Verdrahtung: falsche URL, falscher Feldname, ein Button, der nie sendet.

API-Test

Prüft einen Request

Beweist, dass der Server antwortet. Nie, dass die Seite diesen Request auch schickt - oder die Antwort anzeigt.

End-to-End

Prüft den ganzen Weg

Öffnet die Seite in Chromium, tippt wie ein Mensch, klickt auf Senden, liest die Antwort direkt vom Netzwerk. Läuft es hier, läuft es für Kunden.

So funktioniert der Harness

Playwright, gesteuert aus bun test, aufgesetzt auf die Endpoint-Definitionen von next-vibe. Die Definition kennt bereits jedes Feld - der Test muss es nicht.

1

Echte Seite öffnen

Der Test öffnet die Kundenseite - /user/login, /story/framework - keinen Test-Stub. Getippt wird erst, wenn React das Formular übernommen hat.

2

Mit typisierten Werten ausfüllen

Du übergibst Werte, keine Selektoren. Field-Driver lesen die Definition und bedienen die sichtbare UI - tippen, Option per Label wählen, Datei anhängen.

3

Request vom Netzwerk abgreifen

Submit liefert die Wahrheit: Request-Body, Status, Antwort. Der Harness prüft, dass der Request exakt das enthält, was getippt wurde.

4

Einmal aufnehmen, immer abspielen

Bezahlte Anbieter - KI-Modelle, Bild, Musik, Video - werden pro Test beim ersten Lauf aufgezeichnet und danach abgespielt. Keine Überraschungsrechnung, deterministische Läufe.

5

Abdeckung messen

Ein Coverage-Report zählt jeden öffentlichen Endpoint: je ein Erfolg und eine Ablehnung. Lücken sind sichtbar, nicht geschätzt.

contact.e2e.test.ts
describe("E2E: contact form", () => {
  const e2e = useE2E("contact");

  e2e.it("sends a message", async ({ open, uniqueEmail }) => {
    const form = await open(contactEndpoints.POST, {
      path: "/story/framework",
    });
    await form.fill({
      name: "E2E",
      email: uniqueEmail("contact"),
      subject: ContactSubject.BUG_REPORT,
      message: "...",
    });
    const result = await form.submit();
    expect(result.sent).toBe(true);
  });
});

Eine komplette Spec. Keine Selektoren, keine Mocks.

Die Bilanz

50
Test-Suites
47/56
öffentliche Endpoints voll abgedeckt
8
Sicherheitslücken geschlossen

Die restlichen Endpoints brauchen kostenpflichtige Anbieter-Keys für Suche, Scraping und Sprache, bevor sie aufgezeichnet werden können.

Die Sicherheitslücken, die er fand

Keine davon tauchte in Typprüfung oder Unit-Tests auf. Alle tauchten auf, sobald ein echter Browser den echten Ablauf durchlief.

Open Redirect auf /track

Jeder konnte einen Link bauen, der über unsere Domain auf beliebige Seiten weiterleitete. Externe Ziele brauchen jetzt eine HMAC-Signatur.

SSRF über Nutzer-URLs

URLs, die du der KI gibst, konnten auf interne Adressen zeigen. Ein Guard löst jetzt DNS auf und prüft jeden Redirect-Schritt.

Server-Cache zwischen Requests geteilt

Der Query-Cache beim Server-Rendering konnte Daten eines Nutzers in die Seite eines anderen tragen. Er ist jetzt pro Request isoliert.

System-Prompt-Debug las jeden Thread

Ein Debug-Endpoint prüfte nicht, wem der Thread gehört. Jetzt schon.

Öffentlicher Feed zeigte versteckte Threads

Versteckte und gelöschte Threads erschienen im öffentlichen Feed. Sie werden jetzt herausgefiltert.

Login-Optionen verrieten Konten

Die Antwort der Login-Optionen verriet, ob zu einer E-Mail ein Konto existiert. Das tut sie nicht mehr.

Reset ignorierte die Bestätigung

Der Bestätigungsschritt beim Passwort-Reset ignorierte das Bestätigungsfeld. Abweichungen werden jetzt abgelehnt.

Private Dateien öffentlich gecacht

Private Dateien kamen mit Cache-Control: public, geteilte Caches konnten sie also speichern. Jetzt sind sie privat.

Und was einfach kaputt war

Nicht gefährlich - aber für echte Menschen still kaputt:

  • Chat-Anhang-URLs und Client-Fehlerberichte lieferten 404.
  • Root-Berechtigungen wurden nie geladen - Kunden konnten in Shared nichts posten.
  • Das Einbettungs-Widget startete nicht.
  • Links in Rechnungs-Mails lieferten 404.
  • Die Modellseite war unbenutzbar.
  • Musikgenerierung über OpenRouter schlug fehl.
  • Der Fixture-Scope der Tests ließ bezahlte Live-Calls durch.
  • Lokale PGlite-Datenbanken gaben bei INSERT…RETURNING die falschen Zeilen zurück.
  • Die Besucher-Paginierung auf /tools war kaputt.
Dazu eine lange Liste unübersetzter Texte und Widget-Fixes.

Was das für dich bedeutet

Jede Seite, über die du dich registrierst, einloggst, bezahlst, chattest, generierst oder abonnierst, hat jetzt einen Test, der sie in einem echten Browser öffnet und tut, was du tust. Ändern wir etwas, läuft der Roboter den Weg ab, bevor du es tust.

Das Framework ist Teil von next-vibe. Baust du darauf, bekommen deine Endpoints denselben Harness gratis.

Lies den Harness. Nutz ihn für deinen Code.

Das E2E-Framework ist Open Source und Teil von next-vibe. Eine Spec-Datei pro Feature, direkt neben der Definition.

Zurück zum Blog