Was ist Cypress?
Cypress ist ein End-to-End-Testing-Tool, das innerhalb des Browsers direkt neben deiner Anwendung läuft. Diese architektonische Entscheidung ermöglicht eine außergewöhnlich gute Developer Experience: ein interaktiver Runner, in dem du die Ausführung der Befehle live verfolgen kannst, die Möglichkeit, den DOM in jedem Schritt zu untersuchen, und ein Time-Travel-Debugger, der jede Aktion erneut abspielt.
Es ist eines der beliebtesten Tools für Browser-Tests und wird besonders für seine Zugänglichkeit geschätzt. Befehle werden automatisch in eine Queue eingereiht, Assertions werden so lange wiederholt, bis sie erfolgreich sind, und der Runner macht bei einem Testfehler sofort deutlich, was genau passiert ist.
Einen Test schreiben
Cypress-Tests verwenden eine kettebare Command-API und eine describe/it-Struktur.
// cypress/e2e/todos.cy.js
describe("todos", () => {
beforeEach(() => {
cy.visit("/todos");
});
it("adds a todo", () => {
cy.get('[data-cy="new-todo"]').type("Write tests");
cy.get('[data-cy="add"]').click();
cy.get('[data-cy="todo"]').should("have.length", 1);
cy.contains("Write tests").should("be.visible");
});
});
Cypress-Commands werden in eine Queue eingereiht und nacheinander ausgeführt, auch wenn sie sofort zurückkehren. Aus diesem Grund kettest du .then oder verwendest Aliase, anstatt das Ergebnis eines Commands einer Variable zuzuweisen.
Befehle und Assertions
Die Kernbefehle decken Navigation, Abfragen und Interaktionen ab:
cy.visit(url)öffnet eine Seite.cy.get(selector)fragt per CSS-Selector ab.cy.contains(text)findet ein Element anhand seines Textes..type(),.click(),.select(),.check()interagieren mit Elementen..should()führt Assertions durch und wiederholt den Vorgang bei Bedarf.
// assertions.cy.js
cy.get("form").should("be.visible");
cy.get('[data-cy="count"]').should("have.text", "3");
cy.get("button").should("be.disabled");
cy.get('[data-cy="row"]').should("have.length", 5);
Da .should() so lange wiederholt, bis der Test besteht oder ein Timeout eintritt, müssen Sie nur selten explizit warten. Dies ist derselbe retry-basierte Ansatz, der Playwright-Tests so resilient macht, angewandt auf eine chained API.
Network-Stubbing mit cy.intercept
cy.intercept ist der Weg, um Tests deterministisch zu gestalten. Damit lassen sich Antworten stubben, echter Traffic überwachen, Requests modifizieren und das Timing steuern.
// intercept.cy.js
cy.intercept("GET", "/api/users", {
statusCode: 500,
body: { message: "Server error" },
}).as("users");
cy.visit("/users");
cy.wait("@users");
cy.contains("Something went wrong").should("be.visible");
Die Verwendung eines Alias mit cy.wait("@users") ermöglicht es, Assertions auf den Request anzuwenden und auf die Antwort zu warten, ohne das Timing schätzen zu müssen. Das Stubben von Fehlerzuständen auf diese Weise ist weitaus zuverlässiger, als zu versuchen, diese von einem echten Backend zu provozieren.
Benutzerdefinierte Befehle und Fixtures
Wiederkehrende Abläufe gehören in einen benutzerdefinierten Befehl, was die Tests lesbarer macht.
// cypress/support/commands.js
Cypress.Commands.add("login", (email, password) => {
cy.visit("/login");
cy.get('[data-cy="email"]').type(email);
cy.get('[data-cy="password"]').type(password);
cy.get('[data-cy="submit"]').click();
});
// usage
cy.login("[email protected]", "secret");
Fixtures laden statische Daten aus cypress/fixtures, und cy.fixture("users.json").then(...) sorgt für vorhersehbare Inputs in den Tests. In Kombination mit cy.intercept sind Fixtures der Standardweg, um Tests ohne ein Live-Backend durchzuführen.
Elemente auswählen
Cypress arbeitet mit CSS-Selektoren, daher ist die Versuchung groß, Tests an das Styling zu binden. Vermeiden Sie dies. Bevorzugen Sie stattdessen in dieser Reihenfolge:
- Accessible Queries via
@testing-library/cypress(findByRole,findByLabelText). cy.containsfür sichtbaren Text.- Ein dediziertes
data-cy-Attribut, wenn es keine benutzerseitige Identität gibt.
Ein data-cy-Attribut ist explizit und stabil, sodass Tests auch Redesigns überstehen. Klassenbasierte Selektoren tun dies nicht.
Wo Cypress ins Spiel kommt
Cypress bildet die End-to-End-Schicht. Halten Sie die Testpyramide im Gleichgewicht: schnelle Unit-Tests mit Vitest oder Jest, Komponententests mit Testing Library und ein gezielter Satz an End-to-End-Journeys in Cypress oder Playwright. Verlagern Sie Edge-Cases in die kostengünstigeren Schichten und reservieren Sie E2E-Tests für die Pfade, die zwingend von Anfang bis Ende funktionieren müssen.
Best Practices
- Nutzen Sie
cy.interceptfür das Stubbing des Netzwerks, um deterministische Tests zu gewährleisten. - Verwenden Sie
data-cyoder accessible queries anstelle von Styling-Selektoren. - Halten Sie Tests unabhängig, damit sie parallel ausgeführt werden können.
- Kapseln Sie wiederkehrende Abläufe in custom commands.
- Loggen Sie sich über einen custom command oder einen API-Aufruf ein, anstatt dies jedes Mal über die UI zu tun.
- Prüfen Sie benutzer-sichtbare Ergebnisse und nicht den internen Zustand.
- Führen Sie Tests in der CI headless aus und nutzen Sie den interaktiven Runner für das lokale Debugging.
Häufige Fehler
- Warten mit
cy.wait(milliseconds)anstatt auf einen Netzwerk-Alias oder eine Assertion. - Kopplung von Selektoren an CSS-Klassen und die DOM-Struktur.
- Gemeinsame Nutzung von State zwischen Tests, was zu fehlgeschlagenen Tests abhängig von der Ausführungsreihenfolge führt.
- Versuche, Command-Ergebnisse in Variablen zu speichern, ohne
.thenoder Aliase zu verwenden. - Alles End-to-End zu testen, anstatt auf der jeweils richtigen Ebene.
- Vor jedem Test der Login über die UI, was die gesamte Test-Suite verlangsamt.
Wie geht es weiter?
Cypress ist ein einsteigerfreundlicher und leistungsstarker Weg, um echte User Journeys zu testen. Vergleiche es mit Playwright als browserübergreifende Alternative und baue die unteren Testebenen mit Vitest und Testing Library auf. Füge anschließend einen benutzerdefinierten Login-Command sowie einen kritischen Flow hinzu und erweitere deine Test-Suite von dort aus.