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.
Inhaltsverzeichnis
- 1. Warum lokal grüne Tests in der CI rot werden
- 2. Cypress headless ausführen: cypress run im Detail
- 3. GitLab CI: Ein produktionsreifer Cypress-Job
- 4. GitHub Actions: Cypress-Workflow Schritt für Schritt
- 5. Artefakte sichern: Video und Screenshots bei Fehlschlägen
- 6. Cypress Cloud Recording vs. selbst gehostete Artefakte
- 7. Caching: node_modules und Cypress-Binary beschleunigen
- 8. CI-only Flakiness: Ursachen und Gegenmaßnahmen
- 9. Parallelisierung über mehrere CI-Jobs hinweg
- 10. Zusammenfassung
- 11. FAQ
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.