Claude Code Cypress: E2E-Tests schreiben wie ein Profi
E2E-Tests gelten als notwendiges Übel: teuer zu schreiben, fragil im Betrieb, schwer zu debuggen. Viele Teams testen lieber manuell — bis etwas in der Produktion bricht. Claude Code und Cypress zusammen verändern diese Gleichung. Was früher Stunden gekostet hat, geht jetzt in Minuten: Testaufbau, Selektoren, Fixtures, CI-Integration — alles mit präzisen Prompts, die direkt den richtigen Code liefern.
Dieser Artikel zeigt dir den kompletten Weg: von der Installation bis zum laufenden Test in GitHub Actions. Kein Theorie-Overhead — jeder Abschnitt enthält lauffähigen Code und die passenden Claude Code Prompts, mit denen du ihn generieren oder anpassen kannst.
Claude Code Mastery — Testing, Agents, Hooks auf Deutsch
Nicht nur Cypress: der Kurs zeigt, wie du Claude Code wirklich produktiv einsetzt — für automatisierte Tests, autonome Agents und professionelle Workflows. Einmalig bezahlt, kein Abo.
Zum Kurs — Jetzt starten → Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht1. Warum Cypress mit Claude Code?
Cypress ist heute der Standard für E2E- und Component-Testing in modernen Web-Projekten. Der Grund ist einfach: Die Developer Experience ist ungeschlagen. Tests laufen im echten Browser, das Time-Travel-Debugging zeigt jeden Schritt mit Screenshot, und der Testrunner gibt sofort Feedback statt erst nach einem langen Build-Zyklus.
Der einzige Haken: Gute Cypress-Tests zu schreiben braucht Zeit. Selektoren finden, Assertions richtig formulieren, Fixtures anlegen, Custom Commands sinnvoll strukturieren — das ist kein Hexenwerk, aber es ist Arbeit. Genau hier hilft Claude Code. Es kennt die Cypress-API, versteht deinen Anwendungskontext durch Lesen der Komponenten, und schreibt Tests, die tatsächlich das Richtige testen — nicht nur das Offensichtliche.
- E2E Testing: Komplette User-Journeys von Login bis Checkout automatisiert testen
- Component Testing: Einzelne React/Vue/Angular-Komponenten isoliert testen, ohne Browser-Overhead
- Developer Experience: Interaktiver Testrunner, Time-Travel-Debugging, automatisches Retry bei flaky Tests
- CI-first: Headless-Mode für GitHub Actions, fertige Docker-Images, parallele Testläufe
Cypress vs. Playwright: Beide sind heute ausgezeichnete Optionen. Cypress hat die bessere DX und das intuitivere Debugging. Playwright hat nativen Multi-Browser-Support und ist bei sehr großen Test-Suiten schneller. Für Teams, die zum ersten Mal systematisch E2E-Tests aufbauen, ist Cypress der einfachere Einstieg.
2. Installation und cypress.config.ts Setup
Die Installation ist mit Claude Code in zwei Minuten erledigt. Du musst die Konfigurationsoptionen nicht aus der Dokumentation heraussuchen — beschreibe einfach dein Projekt und Claude Code generiert eine passende cypress.config.ts.
npm install --save-dev cypress
npx cypress open
Für eine TypeScript-basierte React-App mit einer lokalen Dev-Umgebung auf Port 5173 würde der Prompt so aussehen:
claude "Erstelle eine cypress.config.ts für eine React-App mit Vite.
Basis-URL: http://localhost:5173. Viewport: 1280x720.
Testdateien in cypress/e2e/**/*.cy.ts.
Retries: 2 im CI-Modus, 0 lokal. Screenshots nur bei Fehlern."
Das Ergebnis:
import { defineConfig } from 'cypress'
export default defineConfig({
e2e: {
baseUrl: 'http://localhost:5173',
viewportWidth: 1280,
viewportHeight: 720,
specPattern: 'cypress/e2e/**/*.cy.ts',
screenshotOnRunFailure: true,
retries: {
runMode: 2,
openMode: 0,
},
setupNodeEvents(on, config) {
// Node-Events hier registrieren
},
},
})
Wichtig bei Umgebungsvariablen: Zugangsdaten wie Passwörter gehören niemals als Klartext in den Code oder in Konfigurationsdateien. Verwende statt password: 'meinpasswort' immer Cypress.env('PASSWORD') und setze den Wert über CYPRESS_PASSWORD als Umgebungsvariable oder in cypress.env.json (die in .gitignore steht). So bleiben Secrets aus dem Repository draußen.
3. cy.visit(), cy.get(), cy.contains(), cy.click(), cy.type() — Grundlagen
Cypress-Tests lesen sich wie eine Beschreibung dessen, was ein Nutzer tut — das ist der Kern der API. Die fünf am häufigsten genutzten Befehle decken 80 % aller Test-Szenarien ab.
describe('Login-Flow', () => {
it('meldet einen Nutzer erfolgreich an', () => {
// Seite öffnen
cy.visit('/login')
// Elemente finden und befüllen
cy.get('[data-cy="email-input"]').type('test@example.com')
cy.get('[data-cy="password-input"]').type(Cypress.env('PASSWORD'))
// Button klicken
cy.get('[data-cy="submit-btn"]').click()
// Ergebnis prüfen
cy.contains('Willkommen zurück').should('be.visible')
cy.url().should('include', '/dashboard')
})
})
Der Claude Code Prompt für diesen Test:
claude "Schreib einen Cypress E2E-Test für den Login-Flow.
Die App läuft auf /login. Es gibt ein Email-Feld (data-cy='email-input'),
ein Passwort-Feld (data-cy='password-input') und einen Submit-Button
(data-cy='submit-btn'). Nach erfolgreichem Login erscheint 'Willkommen zurück'
und die URL enthält /dashboard. Nutze Cypress.env für das Passwort."
Selektoren: data-cy statt CSS-Klassen
Das wichtigste Prinzip für stabile Tests: Selektiere nie über CSS-Klassen oder XPath, sondern über data-cy-Attribute. CSS-Klassen ändern sich mit dem Styling, data-cy-Attribute ändern sich nur, wenn du sie bewusst änderst.
// Fragil — bricht bei CSS-Änderungen
cy.get('.btn-primary.submit')
// Stabil — unabhängig vom Styling
cy.get('[data-cy="submit-btn"]')
Claude Code weiß das und verwendet automatisch data-cy-Attribute, wenn du nach Best Practices fragst. Wenn deine bestehenden Komponenten noch keine data-cy-Attribute haben:
claude "Füge data-cy Attribute zu allen interaktiven Elementen
in src/components/LoginForm.tsx hinzu, die für Tests relevant sind."
4. Assertions: should(), expect(), have.text, be.visible
Assertions sind das Herzstück jedes Tests. Cypress verwendet Chai-Assertions mit einer fließenden, lesbaren Syntax. Die häufigsten Patterns:
// Sichtbarkeit
cy.get('[data-cy="error-message"]').should('be.visible')
cy.get('[data-cy="loading-spinner"]').should('not.exist')
// Text-Inhalt
cy.get('[data-cy="user-greeting"]').should('have.text', 'Hallo, Max')
cy.get('[data-cy="item-count"]').should('contain.text', '3 Artikel')
// Werte in Inputs
cy.get('[data-cy="search-input"]').should('have.value', 'Cypress')
// URL und Routing
cy.url().should('eq', 'http://localhost:5173/dashboard')
cy.location('pathname').should('eq', '/dashboard')
// Mehrere Assertions auf einem Element
cy.get('[data-cy="submit-btn"]')
.should('be.visible')
.and('not.be.disabled')
.and('have.text', 'Absenden')
Für komplexere Assertions hilft expect() innerhalb eines cy.then()-Callbacks:
cy.get('[data-cy="product-list"]').then(($list) => {
expect($list.children()).to.have.length(5)
expect($list.find('[data-cy="product-card"]')).to.exist
})
Automatisches Retry: Cypress wiederholt Assertions automatisch bis zu 4 Sekunden lang (konfigurierbar). Das macht Tests deutlich robuster gegenüber asynchronen Operationen — du brauchst in den meisten Fällen kein explizites Warten.
5. Custom Commands in commands.ts
Wenn du denselben Code in mehreren Tests wiederholst — zum Beispiel den Login-Vorgang — gehört er in einen Custom Command. Das vermeidet Duplikation und macht Tests lesbarer.
// cypress/support/commands.ts
declare global {
namespace Cypress {
interface Chainable {
loginAs(email: string): Chainable<void>
addToCart(productId: string): Chainable<void>
}
}
}
Cypress.Commands.add('loginAs', (email: string) => {
cy.visit('/login')
cy.get('[data-cy="email-input"]').type(email)
cy.get('[data-cy="password-input"]').type(Cypress.env('PASSWORD'))
cy.get('[data-cy="submit-btn"]').click()
cy.url().should('include', '/dashboard')
})
Cypress.Commands.add('addToCart', (productId: string) => {
cy.get(`[data-cy="add-to-cart-${productId}"]`).click()
cy.get('[data-cy="cart-count"]').should('be.visible')
})
Mit diesen Commands werden die eigentlichen Tests klar und prägnant:
describe('Checkout-Flow', () => {
beforeEach(() => {
cy.loginAs('test@example.com')
})
it('kauft ein Produkt erfolgreich', () => {
cy.visit('/shop')
cy.addToCart('produkt-123')
cy.get('[data-cy="checkout-btn"]').click()
cy.contains('Bestellung aufgegeben').should('be.visible')
})
})
Der Claude Code Prompt dafür:
claude "Extrahiere die wiederholten Login-Schritte aus meinen Cypress-Tests
in einen Custom Command 'loginAs' in cypress/support/commands.ts.
Füge TypeScript-Typen hinzu. Das Passwort soll über Cypress.env bezogen werden."
6. Fixtures und cy.intercept() für API-Mocking
E2E-Tests, die echte API-Calls machen, sind langsam und fragil. Bessere Strategie: API-Antworten mit cy.intercept() abfangen und mit Fixtures ersetzen. So laufen Tests schnell, deterministisch und unabhängig vom Backend-Status.
// cypress/fixtures/products.json
{
"products": [
{ "id": "1", "name": "Laptop Pro", "price": 1299 },
{ "id": "2", "name": "Wireless Maus", "price": 49 }
],
"total": 2
}
// Im Test: API-Call mit Fixture ersetzen
describe('Produktliste', () => {
it('zeigt Produkte aus der API an', () => {
cy.intercept('GET', '/api/products', { fixture: 'products.json' }).as('getProducts')
cy.visit('/shop')
cy.wait('@getProducts')
cy.get('[data-cy="product-card"]').should('have.length', 2)
cy.contains('Laptop Pro').should('be.visible')
cy.contains('1.299 €').should('be.visible')
})
it('zeigt Fehlerseite bei API-Fehler', () => {
cy.intercept('GET', '/api/products', {
statusCode: 500,
body: { error: 'Internal Server Error' }
}).as('getProductsError')
cy.visit('/shop')
cy.wait('@getProductsError')
cy.get('[data-cy="error-state"]').should('be.visible')
cy.contains('Produkte konnten nicht geladen werden').should('be.visible')
})
})
Claude Code generiert Fixtures aus deinen bestehenden API-Typen:
claude "Erstelle eine Cypress Fixture-Datei für den /api/products Endpoint.
Nutze den TypeScript-Typ aus src/types/Product.ts als Vorlage.
Erstelle 3 realistische Beispielprodukte."
7. Page Object Model Pattern
Sobald eine Test-Suite wächst, lohnt sich das Page Object Model (POM): Jede Seite oder Komponente bekommt eine eigene Klasse, die alle Selektoren und Interaktionen kapselt. Ändert sich die UI, muss nur die Page-Object-Klasse angepasst werden — nicht jeder einzelne Test.
// cypress/support/pages/LoginPage.ts
export class LoginPage {
visit() {
cy.visit('/login')
return this
}
fillEmail(email: string) {
cy.get('[data-cy="email-input"]').clear().type(email)
return this
}
fillPassword() {
cy.get('[data-cy="password-input"]').clear().type(Cypress.env('PASSWORD'))
return this
}
submit() {
cy.get('[data-cy="submit-btn"]').click()
return this
}
assertErrorMessage(message: string) {
cy.get('[data-cy="error-message"]')
.should('be.visible')
.and('contain.text', message)
return this
}
}
// Verwendung im Test — lesbar wie Prosa
import { LoginPage } from '../support/pages/LoginPage'
const loginPage = new LoginPage()
describe('Login-Validierung', () => {
it('zeigt Fehlermeldung bei ungültiger E-Mail', () => {
loginPage
.visit()
.fillEmail('keine-gueltige-email')
.fillPassword()
.submit()
.assertErrorMessage('Bitte gib eine gültige E-Mail-Adresse ein')
})
})
Prompt für den kompletten Aufbau:
claude "Erstelle Page Object Classes für LoginPage und DashboardPage
basierend auf den Komponenten in src/pages/Login.tsx und src/pages/Dashboard.tsx.
Verwende das Method-Chaining-Pattern. Passwörter über Cypress.env."
8. Component Testing mit Cypress
Cypress kann nicht nur ganze Seiten testen — es kann auch einzelne Komponenten isoliert rendern und testen. Das ist schneller als E2E, trotzdem echter Browser statt jsdom wie bei Jest.
// cypress.config.ts — Component Testing hinzufügen
import { defineConfig } from 'cypress'
import { devServer } from '@cypress/vite-dev-server'
export default defineConfig({
component: {
devServer: {
framework: 'react',
bundler: 'vite',
},
specPattern: 'src/**/*.cy.tsx',
},
// ... e2e config
})
// src/components/ProductCard.cy.tsx
import { ProductCard } from './ProductCard'
describe('ProductCard', () => {
it('zeigt Produktname und Preis an', () => {
cy.mount(<ProductCard
name="Laptop Pro"
price={1299}
currency="EUR"
/>)
cy.get('[data-cy="product-name"]').should('have.text', 'Laptop Pro')
cy.get('[data-cy="product-price"]').should('contain.text', '1.299')
})
it('ruft onAddToCart auf beim Klick', () => {
const onAddToCart = cy.stub().as('addToCart')
cy.mount(<ProductCard
name="Laptop Pro"
price={1299}
currency="EUR"
onAddToCart={onAddToCart}
/>)
cy.get('[data-cy="add-to-cart-btn"]').click()
cy.get('@addToCart').should('have.been.calledOnce')
})
})
Wann E2E, wann Component Testing? Component Tests für Logik innerhalb einer Komponente: Props, Events, State-Änderungen. E2E-Tests für User-Journeys über mehrere Seiten hinweg: Login, Checkout, Navigation. Beide Ebenen zusammen geben maximale Abdeckung bei minimalem Overhead.
9. CI-Integration mit GitHub Actions
Cypress läuft im Headless-Mode und ist CI-ready. Mit dem offiziellen GitHub Action ist die Integration in wenigen Zeilen erledigt:
# .github/workflows/cypress.yml
name: Cypress E2E Tests
on:
push:
branches: [main, develop]
pull_request:
branches: [main]
jobs:
cypress-run:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Setup Node
uses: actions/setup-node@v4
with:
node-version: 20
cache: 'npm'
- name: Install dependencies
run: npm ci
- name: Start dev server
run: npm run dev &
env:
PORT: 5173
- name: Wait for server
run: npx wait-on http://localhost:5173 --timeout 30000
- name: Run Cypress tests
uses: cypress-io/github-action@v6
with:
browser: chrome
headed: false
env:
CYPRESS_PASSWORD: ${{ secrets.CYPRESS_PASSWORD }}
- name: Upload screenshots on failure
uses: actions/upload-artifact@v4
if: failure()
with:
name: cypress-screenshots
path: cypress/screenshots
Das Passwort wird sicher als GitHub Secret gesetzt und über die Umgebungsvariable CYPRESS_PASSWORD an Cypress übergeben — im Test dann über Cypress.env('PASSWORD') gelesen. Kein Klartext-Secret im Code.
Claude Code Prompt für die komplette CI-Konfiguration:
claude "Erstelle eine GitHub Actions Workflow-Datei für Cypress E2E-Tests.
Die App wird mit 'npm run dev' auf Port 5173 gestartet.
Tests sollen bei Push auf main und bei Pull Requests laufen.
Screenshots bei Fehlern als Artefakte hochladen.
Passwort als Secret CYPRESS_PASSWORD übergeben."
10. Debugging: cy.pause(), cy.debug(), Time-Travel-Debugging
Wenn ein Test fehlschlägt und du nicht verstehst warum, hat Cypress drei starke Debugging-Werkzeuge.
cy.pause() — Test anhalten und manuell weitergehen
it('debuggt den Checkout-Flow', () => {
cy.visit('/shop')
cy.addToCart('produkt-123')
cy.pause() // Test hält hier an — Browser ist offen, du kannst manuell inspizieren
cy.get('[data-cy="checkout-btn"]').click()
})
cy.debug() — Aktuelles Subject in der Konsole ausgeben
cy.get('[data-cy="product-list"]')
.debug() // Gibt das DOM-Element in der Browser-Konsole aus
.should('be.visible')
Time-Travel-Debugging — der eigentliche Killer-Feature
Im Cypress Test Runner siehst du jeden Befehl als einzelnen Schritt in der Sidebar. Klicke auf einen beliebigen Schritt — der Browser springt zum Zustand der App zu genau diesem Moment. Du siehst den Screenshot, den DOM, alle CSS-Eigenschaften. Das ist Time-Travel-Debugging: Statt die Fehlersuche in der Vergangenheit zu rekonstruieren, siehst du sie direkt.
"cy.get('[data-cy=\"submit-btn\"]') — Element nicht gefunden. Timeout nach 4000ms."
Im Time-Travel siehst du beim fehlgeschlagenen Schritt sofort: der Button ist vorhanden, aber hinter einem modalen Dialog versteckt, der noch nicht geschlossen wurde. Eine Assertion die davor fehlte.
Claude Code Prompt für systematisches Debugging:
claude "Dieser Cypress-Test schlägt fehl: [Fehlermeldung einfügen].
Analysiere was fehlt und zeig mir die fehlende Assertion oder den
falschen Selektor. Hier ist der aktuelle Test-Code: [Code einfügen]"
11. Typische Fehler und praktische Claude Code Prompts
Diese Fehler begegnen fast jedem, der mit Cypress anfängt — und für jeden gibt es einen präzisen Claude Code Prompt zur Lösung.
Fehler 1: Flaky Tests durch Timing
Test schlägt manchmal fehl, manchmal nicht — klassisches Timing-Problem.
// Problem: wartet nicht auf den API-Call
cy.visit('/shop')
cy.get('[data-cy="product-card"]').should('have.length', 3)
// Lösung: auf API-Call warten
cy.intercept('GET', '/api/products').as('products')
cy.visit('/shop')
cy.wait('@products')
cy.get('[data-cy="product-card"]').should('have.length', 3)
claude "Mein Cypress-Test ist flaky: [Test-Code]. Wie mache ich ihn robust?"
Fehler 2: Selektor zu fragil
// Problem: CSS-Klasse ändert sich mit Redesign
cy.get('.MuiButton-containedPrimary')
// Lösung: semantischer Selektor
cy.get('[data-cy="primary-action-btn"]')
// oder: ARIA-Attribute
cy.get('[role="button"][aria-label="Jetzt kaufen"]')
claude "Meine Cypress-Tests nutzen CSS-Klassen als Selektoren.
Refactore alle Selektoren auf data-cy Attribute in cypress/e2e/checkout.cy.ts"
Fehler 3: Zu viele Details in einem Test
// Problem: ein Test prüft alles auf einmal
it('testet die gesamte App', () => {
// 50 Zeilen...
})
// Lösung: Ein Test, eine Aussage
it('zeigt Produkte nach dem Laden', () => { /* ... */ })
it('filtert nach Kategorie', () => { /* ... */ })
it('zeigt Fehlermeldung bei leerem Suchergebnis', () => { /* ... */ })
claude "Dieser Cypress-Test ist zu groß und testet zu viel auf einmal.
Zerlege ihn in kleine, fokussierte Tests mit je einer Aussage: [Test-Code]"
Fehler 4: Kein Test für den Fehlerfall
claude "Ich habe Tests für den Happy Path des Login-Flows.
Schreib zusätzlich Tests für: falsche E-Mail, falsches Passwort,
Netzwerkfehler, und Account gesperrt. Nutze cy.intercept für die
Fehlerfälle. Passwort über Cypress.env."
12. Von manuellen Tests zu systematischer Abdeckung
Die stärkste Nutzung von Claude Code für Cypress: du zeigst ihm deine Anwendung, und es erstellt systematisch eine vollständige Test-Suite — nicht nur für das Offensichtliche, sondern für alle Randfälle, die ein erfahrener QA-Ingenieur testen würde.
claude "Analysiere src/pages/ und erstelle eine vollständige Cypress-Testmatrix:
Welche User-Journeys gibt es? Welche Randfälle müssen getestet werden?
Welche API-Calls müssen gemockt werden? Erstelle danach die komplette
Test-Suite in cypress/e2e/. Passwörter und Secrets immer über Cypress.env."
Oder für eine spezifische Komponente:
claude "Lies src/components/PaymentForm.tsx und schreib Cypress Component Tests für:
- Valide Kartendaten werden akzeptiert
- Ungültige Kartennummer zeigt Fehlermeldung
- Abgelaufene Karte zeigt korrekten Hinweis
- Submit-Button ist disabled solange das Formular unvollständig ist
- Formular-Reset nach erfolgreichem Submit"
Verwandte Artikel die dieses Thema ergänzen:
- Claude Code Debugging — wie du Bugs systematisch findest und Root-Causes analysierst
- Claude Code für Python-Projekte — Testing und Workflows für Python-Entwicklung
Claude Code Mastery — von Cypress bis zum produktiven Agenten
Testing ist eine Stärke von Claude Code — aber nicht die einzige. Im Kurs lernst du Agents, MCP-Server, Hooks, Multi-Agent-Workflows und mehr. Vollständig auf Deutsch, einmalig bezahlt.
Jetzt starten → Einmalzahlung · Kein Abo · 14 Tage RückgaberechtKurs · Claude Code Mastery
Von Cypress-Tests zum produktiven AI-Agenten
Testing. Agents. MCP. Hooks. Multi-Agent-Workflows. Alles auf Deutsch, einmalig bezahlt — kein Abo, keine Plattformabhängigkeit.
Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht