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ückgaberecht

1. 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.

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 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ückgaberecht

Kurs · 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.

Jetzt einsteigen → Kursübersicht ansehen →

Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht