Cypress in der CI-Pipeline zuverlässig integrieren
PASS
expect()
Testing · Cypress · CI/CD · E2E-Testing
Cypress in der CI-Pipeline zuverlässig integrieren
Headless Tests, Artefakte und Parallelisierung ohne Flakiness

Cypress-Tests, die lokal zuverlässig grün sind, scheitern in der CI oft unerwartet an Ressourcenlimits, abweichenden Viewports und Timing-Unterschieden auf geteilten Runnern. Dieser Artikel zeigt, wie ihr Cypress in GitLab CI und GitHub Actions headless ausführt, Video- und Screenshot-Artefakte bei Fehlschlägen sichert, node_modules und die Cypress-Binary cacht und Testläufe über mehrere Jobs hinweg parallelisiert.

12 Min. Lesezeit Cypress · CI/CD GitLab CI · GitHub Actions

1. Warum lokal grüne Tests in der CI rot werden

Die lokale Entwicklungsmaschine unterscheidet sich in fast jeder Hinsicht von einem CI-Runner: mehr CPU-Kerne, mehr RAM, ein sichtbares Browserfenster statt eines headless Renderers und keine Konkurrenz durch parallel laufende Jobs auf demselben Host. Cypress selbst ist deterministisch, die Umgebung, in der es läuft, ist es nicht. Genau diese Lücke ist die häufigste Ursache dafür, dass ein Test lokal zuverlässig grün ist, aber in der Pipeline sporadisch fehlschlägt.

Typische Auslöser sind fest codierte Wartezeiten, die auf der schnelleren lokalen Maschine ausreichen, auf einem gedrosselten Shared Runner aber zu kurz sind, sowie Animationen und CSS-Transitions, die in einem headless Browser anders getimt werden als im sichtbaren Fenster. Wer diese Unterschiede früh versteht, vermeidet zeitraubende Debugging-Sessions und schreibt von Anfang an Tests, die auf explizite Zustände statt auf feste Zeitspannen warten.

2. Cypress headless ausführen: cypress run im Detail

cypress open ist für die interaktive Entwicklung gedacht und in einer CI-Umgebung ohne grafische Oberfläche schlicht nicht nutzbar. Für automatisierte Pipelines ist cypress run der richtige Befehl: Er führt alle Spec-Dateien headless im Hintergrund aus, gibt einen aussagekräftigen Exit Code zurück und terminiert den Prozess sauber, sobald alle Tests durchgelaufen sind. Ein Exit Code ungleich null signalisiert der Pipeline zuverlässig einen fehlgeschlagenen Testlauf, was für die Build-Status-Logik von GitLab CI und GitHub Actions essenziell ist.

Wichtig ist die explizite Wahl des Browsers über --browser chrome oder --browser electron, da CI-Images häufig mehrere Browser-Binaries mitbringen und der Default nicht immer der performanteste ist. Zusätzlich lohnt sich npx cypress verify vor dem eigentlichen Testlauf: Der Befehl prüft, ob die Cypress-Binary korrekt installiert und lauffähig ist, und deckt kaputte Cache-Zustände auf, bevor sie zu kryptischen Fehlermeldungen mitten im Testlauf führen.

3. GitLab CI: Ein produktionsreifer Cypress-Job

Für GitLab CI bietet Cypress offizielle Docker-Images mit vorinstalliertem Chrome, Firefox und Edge, sodass kein manuelles Browser-Setup im Job notwendig ist. Der Job sollte den CYPRESS_CACHE_FOLDER explizit in das Projektverzeichnis legen, damit GitLabs eingebauter Cache-Mechanismus die Cypress-Binary zwischen Pipeline-Läufen wiederverwenden kann, statt sie bei jedem Lauf neu herunterzuladen.

Entscheidend für die Fehlersuche ist die artifacts-Konfiguration mit when: on_failure: So werden Videos und Screenshots nur bei tatsächlichen Fehlschlägen hochgeladen, was Speicherplatz spart und die Pipeline-Historie übersichtlich hält. Eine begrenzte expire_in-Zeit verhindert, dass sich über Monate hinweg ungenutzte Artefakte im GitLab-Storage ansammeln.


# .gitlab-ci.yml: Cypress E2E-Tests headless in der Pipeline ausfuehren
stages:
  - test

cypress:e2e:
  stage: test
  image: cypress/browsers:node-20.11.0-chrome-123.0.6312.86-1-ff-124.0-edge-123.0.2420.65-1
  variables:
    CYPRESS_CACHE_FOLDER: "$CI_PROJECT_DIR/.cache/Cypress"
  cache:
    key:
      files:
        - package-lock.json
    paths:
      - node_modules/
      - .cache/Cypress/
  script:
    - npm ci
    - npx cypress verify
    # Headless im Chrome-Container ausfuehren, kein Cypress-Cloud-Recording
    - npx cypress run --browser chrome --headless --record false
  artifacts:
    when: on_failure
    expire_in: 7 days
    paths:
      - cypress/videos/
      - cypress/screenshots/

4. GitHub Actions: Cypress-Workflow Schritt für Schritt

Die offizielle cypress-io/github-action übernimmt in GitHub Actions gleich mehrere Schritte in einem Befehl: npm-Installation, Cypress-Binary-Cache und den eigentlichen Testlauf. Über die Parameter build und start lässt sich der Anwendungsserver direkt vor dem Testlauf bauen und starten, während wait-on sicherstellt, dass Cypress erst dann startet, wenn die Anwendung tatsächlich erreichbar ist, statt gegen einen noch nicht bereiten Server zu laufen.

Für den Artefakt-Upload kommt die separate Action actions/upload-artifact zum Einsatz, gesteuert über die Bedingung if: failure(). Dieses Muster spiegelt exakt die GitLab-CI-Logik wider: Nur bei einem tatsächlichen Fehlschlag entstehen Speicherkosten und zusätzliche Pipeline-Laufzeit durch den Upload von Video- und Screenshot-Dateien.


# .github/workflows/cypress.yml: Cypress-Workflow mit offiziellem GitHub-Action
name: Cypress E2E Tests

on:
  pull_request:
  push:
    branches: [main]

jobs:
  cypress-run:
    runs-on: ubuntu-latest
    steps:
      - name: Repository auschecken
        uses: actions/checkout@v4

      - name: Cypress ausfuehren (inkl. npm-Install und Binary-Cache)
        uses: cypress-io/github-action@v6
        with:
          browser: chrome
          build: npm run build
          start: npm run start:ci
          wait-on: 'http://localhost:8080'

      - name: Artefakte bei Fehlschlag hochladen
        uses: actions/upload-artifact@v4
        if: failure()
        with:
          name: cypress-artifacts
          path: |
            cypress/videos
            cypress/screenshots
          retention-days: 7

5. Artefakte sichern: Video und Screenshots bei Fehlschlägen

Ohne sichtbaren Browser ist der CI-Runner eine Blackbox: Ein fehlgeschlagener Test ohne Artefakte liefert selten genug Kontext, um die Ursache zuverlässig einzugrenzen. Cypress zeichnet standardmäßig ein Video der gesamten Spec-Ausführung auf und erstellt bei jedem fehlgeschlagenen Test automatisch einen Screenshot des Zustands im Moment des Scheiterns, inklusive Fehlermeldung und Stacktrace-Overlay im Bild.

In der Praxis lohnt es sich, Videos nur bei tatsächlichen Fehlschlägen zu behalten, da vollständige Aufzeichnungen erfolgreicher Läufe schnell mehrere Gigabyte pro Pipeline belegen. Der after:spec-Hook in der Cypress-Konfiguration erlaubt genau diese Filterung programmatisch, sodass nur die Aufnahmen erhalten bleiben, die für die Fehlersuche tatsächlich relevant sind, während erfolgreiche Läufe schlank durch die Pipeline laufen.


// cypress.config.js: Video- und Screenshot-Erfassung fuer CI-Laeufe konfigurieren
const { defineConfig } = require('cypress');

module.exports = defineConfig({
  video: true,
  videoCompression: 32,
  screenshotOnRunFailure: true,
  trashAssetsBeforeRuns: true,
  retries: {
    runMode: 2,   // In der CI zweimal wiederholen, um Flakiness abzufedern
    openMode: 0,  // Lokal im interaktiven Modus keine automatischen Retries
  },
  viewportWidth: 1280,
  viewportHeight: 720,
  e2e: {
    baseUrl: 'http://localhost:8080',
    setupNodeEvents(on, config) {
      // In der CI Videos nur bei Fehlschlaegen behalten
      on('after:spec', (spec, results) => {
        if (results && results.video && results.stats.failures === 0) {
          return require('fs').promises.unlink(results.video).catch(() => {});
        }
      });
    },
  },
});

6. Cypress Cloud Recording vs. selbst gehostete Artefakte

Cypress Cloud übernimmt Video- und Screenshot-Hosting, liefert eine durchsuchbare Test-Historie über alle Läufe hinweg und ermöglicht mit --record sowie einem Projekt-Token das automatische Zusammenführen paralleler Testläufe zu einem Gesamtergebnis. Für Teams mit vielen parallelen Jobs und häufigen Merge-Requests reduziert das den manuellen Aufwand für Artefakt-Verwaltung erheblich, kostet aber je nach Nutzungsumfang eine monatliche Gebühr und schickt Testdaten an einen externen Dienst.

Die selbst gehostete Alternative über artifacts-Uploads in GitLab CI oder GitHub Actions ist kostenlos, hält alle Daten im eigenen Infrastruktur-Perimeter und eignet sich besonders für Projekte mit strengen Datenschutzanforderungen oder ohne Cypress-Cloud-Budget. Der Nachteil: Die Ergebnisse mehrerer parallel laufender Jobs müssen manuell zusammengeführt werden, und eine durchsuchbare Historie über viele Pipeline-Läufe hinweg existiert nicht ohne zusätzliches Tooling.

7. Caching: node_modules und Cypress-Binary beschleunigen

Die Cypress-Binary ist ein eigenständiges, mehrere hundert Megabyte großes Electron-Paket, das getrennt von den npm-Paketen heruntergeladen wird. Ohne Caching lädt jeder Pipeline-Lauf sowohl node_modules als auch die Binary komplett neu, was je nach Netzwerkanbindung des Runners mehrere Minuten pro Job kostet, noch bevor der erste Test überhaupt startet.

Ein Cache-Key auf Basis des Hashes von package-lock.json stellt sicher, dass der Cache bei geänderten Abhängigkeiten automatisch invalidiert wird, statt veraltete Pakete auszuliefern. Sowohl GitLab CI als auch GitHub Actions bringen eingebaute Cache-Mechanismen mit; für Setups außerhalb dieser Plattformen oder mit eigenem Runner-Fleet lässt sich dasselbe Prinzip auch mit einem einfachen Shell-Skript nachbauen, das den Cache-Ordner vor dem Testlauf wiederherstellt und danach aktualisiert.


#!/usr/bin/env bash
# scripts/ci-cache-restore.sh: node_modules und Cypress-Binary gezielt cachen
set -euo pipefail

CACHE_KEY=$(sha256sum package-lock.json | awk '{print $1}')
CACHE_DIR="/cache/cypress-ci/${CACHE_KEY}"

if [ -d "$CACHE_DIR/node_modules" ]; then
  echo "Cache-Treffer: node_modules wird wiederhergestellt"
  cp -r "$CACHE_DIR/node_modules" ./node_modules
  cp -r "$CACHE_DIR/cypress-binary" "$HOME/.cache/Cypress"
else
  echo "Kein Cache-Treffer: npm ci und Cypress-Binary werden neu installiert"
  npm ci
  npx cypress install
  mkdir -p "$CACHE_DIR"
  cp -r ./node_modules "$CACHE_DIR/node_modules"
  cp -r "$HOME/.cache/Cypress" "$CACHE_DIR/cypress-binary"
fi

8. CI-only Flakiness: Ursachen und Gegenmaßnahmen

Drei Ursachen erklären die meisten Fälle von Flakiness, die ausschließlich in der CI auftritt: Erstens CPU-Drosselung auf Shared Runnern, wenn mehrere Jobs gleichzeitig auf demselben Host laufen und Rendering sowie JavaScript-Ausführung dadurch spürbar langsamer werden als lokal. Zweitens abweichende Viewport- und DPI-Einstellungen: Der headless Standard-Viewport unterscheidet sich häufig von der Fenstergröße, mit der lokal entwickelt und manuell getestet wurde, was zu anderem responsivem Verhalten führt.

Drittens Netzwerk- und Timing-Varianz zwischen Containern auf demselben Host, die zu unterschiedlichen Antwortzeiten führt, selbst wenn Frontend und Backend im selben Pipeline-Job laufen. Die wirksamste Gegenmaßnahme gegen alle drei Ursachen ist derselbe Grundsatz: niemals auf feste Zeitspannen warten, sondern auf explizite Zustände mit cy.intercept() und automatischen Retries in Assertions, kombiniert mit einem fest definierten Viewport in der Konfiguration statt eines plattformabhängigen Defaults.

9. Parallelisierung über mehrere CI-Jobs hinweg

Mit wachsender Testsuite steigt die Laufzeit eines einzelnen, sequenziellen Cypress-Jobs schnell auf zehn oder mehr Minuten. Parallelisierung verteilt die Spec-Dateien auf mehrere gleichzeitig laufende Jobs oder Maschinen und reduziert die Gesamtlaufzeit proportional zur Anzahl der Parallel-Instanzen. Cypress Cloud übernimmt die Verteilung automatisch und intelligent nach bisheriger Laufzeit jeder Spec-Datei, sodass die Jobs möglichst gleichmäßig ausgelastet sind.

Ohne Cypress Cloud lässt sich Parallelisierung auch manuell über eine GitLab-CI-Matrix oder eine GitHub-Actions-Job-Matrix mit fest zugewiesenen Spec-Gruppen umsetzen, etwa nach Feature-Bereich wie Checkout oder Katalog. Der Nachteil der manuellen Variante: Bei ungleich verteilter Testlaufzeit zwischen den Gruppen wartet die gesamte Pipeline auf den langsamsten Job, während die intelligente Verteilung von Cypress Cloud dieses Ungleichgewicht automatisch ausgleicht.


{
  "scripts": {
    "cypress:run": "cypress run --browser chrome --headless",
    "cypress:run:parallel": "cypress run --record --parallel --ci-build-id $CI_PIPELINE_ID --group ci-parallel",
    "cypress:run:group-checkout": "cypress run --spec cypress/e2e/checkout/**/* --headless",
    "cypress:run:group-catalog": "cypress run --spec cypress/e2e/catalog/**/* --headless"
  }
}

Die folgende Übersicht fasst zusammen, wie sich eine lokale Testausführung von einem Lauf auf einem geteilten CI-Runner in den entscheidenden Dimensionen unterscheidet.

Dimension Lokale Entwicklung CI (Shared Runner) Empfohlene Maßnahme
Viewport / Auflösung Fenstergröße des Entwicklers Abweichender Headless-Default Feste viewportWidth/Height in der Config
CPU / RAM Dedizierte Workstation Geteilter Runner, Throttling unter Last Jobs parallelisieren statt seriell stapeln
Netzwerk / Timing Lokaler Dev-Server, niedrige Latenz Variable Container-zu-Container-Latenz cy.intercept() statt fester Wartezeiten
Artefakte Interaktive Fehlersuche im Browser Kein direkter Zugriff auf den Runner Video + Screenshot als Artefakt hochladen
Parallelisierung Ein Testlauf pro Entwickler Mehrere Jobs/Maschinen möglich Cypress Cloud oder Job-Matrix mit Spec-Split

Mironsoft

Cypress-CI-Integration und E2E-Testautomatisierung für Magento- und Hyvä-Shops

Stabile Cypress-Pipeline gesucht?

Wir richten eure Cypress-Tests für GitLab CI oder GitHub Actions ein, beheben CI-only Flakiness und sorgen mit Caching und Parallelisierung für schnelle, verlässliche Pipelines.

CI-Pipeline-Audit

Analyse bestehender GitLab-CI- oder GitHub-Actions-Jobs auf Schwachstellen

Flakiness-Analyse

Root-Cause-Analyse für sporadische Fehlschläge und gezielte Fixes

Setup & Parallelisierung

Caching, Artefakt-Handling und Job-Matrix für schnellere Pipelines

10. Zusammenfassung

Eine zuverlässige Cypress-CI-Integration löst ein klar umrissenes Problem: Tests, die lokal grün sind, müssen auch auf einem geteilten, ressourcenbegrenzten Runner reproduzierbar grün bleiben. Headless-Ausführung über cypress run in GitLab CI oder GitHub Actions bildet die Grundlage, ergänzt um Video- und Screenshot-Artefakte, die bei jedem Fehlschlag automatisch gesichert werden und die Fehlersuche ohne direkten Runner-Zugriff überhaupt erst möglich machen.

Caching von node_modules und der Cypress-Binary verkürzt die Pipeline-Laufzeit spürbar, während das gezielte Adressieren von CPU-Drosselung, Viewport-Abweichungen und Timing-Varianz die häufigsten Ursachen für CI-only Flakiness beseitigt. Wer zusätzlich Testläufe über mehrere Jobs parallelisiert, sei es über Cypress Cloud oder eine manuelle Job-Matrix, reduziert die Gesamtlaufzeit der Pipeline proportional zur Anzahl der Parallel-Instanzen und hält die Feedback-Schleife für das gesamte Team kurz.

Cypress in der CI-Pipeline - Das Wichtigste auf einen Blick

Headless-Ausführung

cypress run statt cypress open, expliziter Browser und cypress verify vor dem Testlauf.

Artefakte sichern

Video und Screenshots nur bei Fehlschlägen hochladen, begrenzte Aufbewahrungsfrist konfigurieren.

Caching

node_modules und Cypress-Binary über Hash von package-lock.json cachen, Downloads pro Lauf vermeiden.

Flakiness & Parallelisierung

Explizite Zustände statt fester Wartezeiten, feste Viewports, Spec-Split über mehrere CI-Jobs.

11. FAQ: Cypress in der CI-Pipeline

1Warum bestehen Cypress-Tests lokal, schlagen aber in der CI fehl?
Weil sich die CI-Umgebung von der lokalen Maschine unterscheidet: weniger CPU/RAM, abweichender Viewport-Default und Timing-Unterschiede durch geteilte Runner.
2Wie führe ich Cypress headless in GitLab CI aus?
Mit den offiziellen Cypress-Docker-Images und cypress run --browser chrome --headless. CYPRESS_CACHE_FOLDER ins Projektverzeichnis legen und cachen.
3Wie integriere ich Cypress in GitHub Actions?
Über die offizielle cypress-io/github-action mit build, start und wait-on, die npm-Install, Binary-Cache und Testlauf in einem Schritt übernimmt.
4Wie sichere ich Videos und Screenshots bei fehlgeschlagenen Tests?
Cypress erstellt sie automatisch. artifacts mit when: on_failure bzw. upload-artifact mit if: failure() setzen, um nur bei Fehlschlägen hochzuladen.
5Lohnt sich Cypress Cloud oder reicht ein selbst gehostetes Artefakt-Setup?
Cloud lohnt sich bei vielen parallelen Jobs und Test-Historie-Bedarf, kostet aber Gebühren. Selbst gehostet ist kostenlos, erfordert aber manuelles Zusammenführen.
6Wie cache ich node_modules und die Cypress-Binary in der CI?
Mit einem Cache-Key aus dem Hash von package-lock.json über beide Pfade. GitLab CI/GitHub Actions bringen eingebaute Mechanismen mit.
7Was sind die häufigsten Ursachen für CI-only Flakiness?
CPU-Drosselung auf Shared Runnern, abweichende Viewport-/DPI-Einstellungen und Netzwerk- bzw. Timing-Varianz zwischen Containern.
8Wie stelle ich sicher, dass der Viewport in der CI mit lokal übereinstimmt?
viewportWidth und viewportHeight fest in cypress.config.js definieren, statt sich auf den headless Default zu verlassen.
9Wie parallelisiere ich Cypress-Tests über mehrere CI-Jobs?
Über Cypress Cloud mit --record --parallel oder manuell über eine Job-Matrix mit fest zugewiesenen Spec-Gruppen.
10Wie viele Retries sollte ich in der CI konfigurieren?
Üblich ist retries.runMode: 2 in der CI, openMode bleibt lokal bei 0. Retries kaschieren keine echten Bugs, sondern federn Timing-Flakiness ab.