Test-Retry-Strategien ohne Flakiness zu verschleiern
PASS
expect()
Testing · Flaky Tests · Cypress · Playwright
Test-Retry-Strategien ohne Flakiness zu verschleiern
Retry als Signal, nicht als Versteck

Wer Tests einfach so lange wiederholen lässt, bis sie grün werden, verschiebt echte Bugs und Race Conditions unsichtbar in die Zukunft und verliert nach und nach das Vertrauen in die eigene Testsuite. Dieser Artikel zeigt, wie Cypress und Playwright Retries technisch sauber konfiguriert werden und wie Teams wiederholte Testläufe als Frühwarnsystem statt als bequemen Persilschein nutzen.

13 Min. Lesezeit Cypress · Playwright · CI/CD Flaky Tests · Quality Engineering

1. Was Test-Flakiness ist und warum blindes Retry-bis-Erfolg gefährlich ist

Ein flaky Test ist ein Test, der bei unverändertem Code und unverändertem Anwendungsverhalten mal grün und mal rot ausfällt. Ursachen reichen von echten Race Conditions in der Anwendung über Netzwerklatenz und Animationen bis zu Testfällen, die sich gegenseitig durch geteilten Zustand beeinflussen. In größeren E2E-Suiten mit hunderten Tests ist ein gewisses Grundrauschen an Flakiness fast unvermeidlich, entscheidend ist aber, wie ein Team damit umgeht.

Die naheliegendste, aber gefährlichste Reaktion ist das globale Hochsetzen der Retry-Anzahl, etwa retries: 3 für die gesamte Suite, ohne dass jemand nachschaut, welche Tests davon betroffen sind. Damit wird jeder intermittierende Fehler automatisch in einen dauerhaft grünen Status verwandelt, ohne dass die zugrunde liegende Ursache je untersucht wird. Genau dieses blinde Retry-bis-Erfolg ist der Kern des Problems: Es beseitigt nicht die Flakiness, sondern macht sie für alle Beteiligten unsichtbar.

2. Wie Retry-bis-Erfolg echte Bugs verschleiert und das Vertrauen in die Suite untergräbt

Ein Retry-Mechanismus kennt keinen Unterschied zwischen einem Test, der wegen eines kurzen Netzwerk-Hakelns fehlschlägt, und einem Test, der eine echte Race Condition im Checkout-Code aufdeckt. Beide werden nach demselben Muster einfach erneut ausgeführt, bis irgendwann ein grüner Lauf zustande kommt. Für die Anwendung selbst bedeutet das: Ein Bug, der unter Last oder bei bestimmten Timing-Konstellationen auftritt, bleibt im Code, wird aber von der Testsuite nicht mehr gemeldet, sondern durch Wiederholung kaschiert.

Mittelfristig untergräbt das die Aussagekraft der gesamten Suite. Entwickler lernen schnell, dass ein einzelner roter Lauf nichts bedeutet, weil er ohnehin beim nächsten Versuch grün wird, und beginnen, fehlgeschlagene Pipelines reflexhaft neu zu starten statt sie zu untersuchen. Dieses Verhalten, in der Qualitätssicherung oft mit dem Broken-Windows-Effekt verglichen, breitet sich aus: Sobald ein Team akzeptiert, dass Rot nicht zwingend einen Fehler bedeutet, verliert die gesamte Suite ihre Funktion als verlässliches Signal.

3. Retry als Signal- und Triage-Werkzeug statt permanenter Fix

Der entscheidende Perspektivwechsel: Retry ist kein Fix, sondern ein Diagnoseinstrument. Ein Test, der erst im zweiten oder dritten Versuch besteht, liefert eine wertvolle Information, nämlich dass an dieser Stelle etwas nicht deterministisch ist, ob in der Anwendung oder in der Testinfrastruktur. Diese Information darf nicht stillschweigend verworfen werden, nur weil der Testlauf am Ende grün war. Stattdessen sollte jeder retried-but-passed-Test automatisch protokolliert und für eine spätere Untersuchung markiert werden.

In der Praxis bedeutet das: Retry-Ergebnisse werden separat erfasst, etwa über einen Custom Reporter oder eine Test-Fixture, die die Anzahl der Versuche pro Test mitschreibt. Diese Daten fließen in ein Dashboard oder eine automatisch erstellte Ticket-Liste, die dem verantwortlichen Team zeigt, welche Tests wiederholt nur knapp bestanden haben. Retry kauft also Zeit für die Pipeline, aber niemals Entlastung von der eigentlichen Untersuchung.


// Custom fixture: log when a test only passed after a retry
import { test as base, expect } from '@playwright/test';

export const test = base.extend({
  page: async ({ page }, use, testInfo) => {
    await use(page);

    // testInfo.retry is 0 on the first attempt, 1+ on retries
    if (testInfo.retry > 0 && testInfo.status === 'passed') {
      console.warn(
        `[FLAKY-SIGNAL] "${testInfo.title}" passed only after ` +
        `${testInfo.retry} retry(s). Flagging for investigation.`
      );

      // Persist to a triage attachment instead of silently accepting the pass
      await testInfo.attach('flaky-signal', {
        body: JSON.stringify({
          test: testInfo.title,
          file: testInfo.file,
          retries: testInfo.retry,
          project: testInfo.project.name,
        }),
        contentType: 'application/json',
      });
    }
  },
});

export { expect };

4. Retry-Konfiguration in Cypress: runMode vs. openMode

Cypress unterscheidet bei der Retry-Konfiguration explizit zwischen runMode, dem headless CI-Lauf, und openMode, dem interaktiven Modus während der Entwicklung. Diese Trennung ist kein Implementierungsdetail, sondern ein bewusstes Designprinzip: Im CI-Lauf sind ein oder zwei Retries oft ein sinnvoller Kompromiss, um bekannte Infrastruktur-Latenzen abzufedern, ohne die Pipeline bei jedem kleinen Netzwerk-Hänger rot werden zu lassen. Im interaktiven Modus dagegen soll ein Entwickler jeden Fehlschlag sofort sehen, weshalb openMode in aller Regel auf 0 stehen sollte.

Wichtig ist, die Retry-Konfiguration nicht als Blackbox zu betreiben. Über das after:spec-Event lässt sich für jeden Test auslesen, wie viele Versuche tatsächlich nötig waren, bevor er bestanden hat. Diese Information direkt beim Testlauf zu protokollieren, statt sie im Cypress Dashboard verschwinden zu lassen, ist der einfachste Einstieg in ein systematisches Flaky-Test-Tracking ohne zusätzliche Tools.


const { defineConfig } = require('cypress');

module.exports = defineConfig({
  // Different retry strategy for CI (runMode) vs local debugging (openMode)
  retries: {
    // CI runs headless: absorb known environmental noise automatically
    runMode: 2,
    // Interactive mode: never hide a failure while a developer is watching
    openMode: 0,
  },

  e2e: {
    setupNodeEvents(on, config) {
      // Track retry attempts explicitly instead of letting them pass silently
      on('after:spec', (spec, results) => {
        const flaky = results.tests?.filter(
          (t) => t.attempts.length > 1 && t.state === 'passed'
        );

        if (flaky && flaky.length > 0) {
          flaky.forEach((t) => {
            console.warn(
              `[FLAKY-SIGNAL] ${spec.relative} > "${t.title.join(' > ')}" ` +
              `needed ${t.attempts.length} attempts to pass.`
            );
          });
        }
      });
    },
  },
});

5. Retry-Konfiguration in Playwright: globale Retries und test.describe.configure

Playwright behandelt Retries global über die retries-Option in der Konfigurationsdatei, typischerweise nur für CI aktiviert und lokal auf 0 belassen, damit Entwickler Fehler sofort sehen statt sie wegretryt zu bekommen. Zusätzlich erlaubt test.describe.configure() gezielte Ausnahmen für einzelne Testblöcke, etwa für einen bekanntermaßen instabilen Checkout-Flow mit einem Drittanbieter-Zahlungs-iFrame, ohne die Retry-Zahl für die gesamte Suite anzuheben.

Der eigentliche Mehrwert entsteht durch einen Custom Reporter, der bei jedem onTestEnd-Aufruf prüft, ob result.retry größer als 0 und der Status trotzdem passed ist. Genau diese Kombination markiert einen Test als retried-but-passed und damit als Kandidat für die Triage-Liste. Ohne einen solchen Reporter verschwindet die Information über nötige Wiederholungen unwiederbringlich im internen Playwright-Report, sobald die Pipeline insgesamt grün ist.


// playwright.config.js
import { defineConfig } from '@playwright/test';

export default defineConfig({
  // Global retry budget, only meaningful in CI
  retries: process.env.CI ? 2 : 0,
  reporter: [
    ['list'],
    ['./reporters/flaky-reporter.js'],
  ],

  projects: [
    {
      name: 'checkout-flow',
      // Known-unstable third-party payment iframe: slightly higher budget
      retries: process.env.CI ? 3 : 0,
    },
  ],
});

// reporters/flaky-reporter.js: flag tests that needed a retry to pass
export default class FlakyReporter {
  onTestEnd(test, result) {
    if (result.retry > 0 && result.status === 'passed') {
      console.warn(
        `[FLAKY-SIGNAL] "${test.title}" passed on retry ${result.retry}. ` +
        'Opening a triage ticket instead of treating this as fixed.'
      );
    }
  }
}

6. Umgebungsbedingte Flakiness von echten Anwendungsfehlern unterscheiden

Nicht jeder flaky Test zeigt einen Bug in der Anwendung. Umgebungsbedingte Flakiness entsteht typischerweise durch Netzwerklatenz zu externen Services, überlastete CI-Runner, unzureichende explizite Waits statt robuster Selektoren oder Testreihenfolge-Abhängigkeiten durch geteilten Datenbankzustand zwischen Tests. Diese Klasse von Fehlern lässt sich meist durch robustere Selektoren, isolierte Testdaten pro Testlauf und Retry-Mechanismen mit klar begrenztem Budget in den Griff bekommen.

Echte Anwendungsfehler dagegen zeigen sich oft als Race Conditions im Produktivcode selbst, etwa wenn ein Warenkorb-Update und eine Preisberechnung nicht korrekt synchronisiert sind. Der zuverlässigste Weg zur Unterscheidung: den betroffenen Test isoliert und mehrfach hintereinander lokal ausführen, dabei Netzwerklogs, Screenshots und Videos auswerten und prüfen, ob der Fehlschlag mit einem konkreten Deployment korreliert. Bleibt der Fehler auch isoliert und unter stabilen Bedingungen reproduzierbar, handelt es sich fast immer um einen echten Bug, keine Infrastruktur-Störung.

7. Quarantäne- und Known-Flaky-Test-Workflows

Ein pragmatischer Zwischenschritt zwischen sofortigem Fix und dauerhaftem Ignorieren ist die Quarantäne: Ein als flaky identifizierter Test wird mit einem Tag wie @flaky markiert und aus dem blockierenden Hauptlauf der Pipeline entfernt, läuft aber weiterhin in einer separaten, nicht-blockierenden Suite mit. So bleibt die Hauptpipeline aussagekräftig, während der Test nicht in Vergessenheit gerät, sondern weiterhin Daten für die Diagnose liefert.

Damit Quarantäne nicht zur dauerhaften Ablage wird, gehört ein automatisiertes Gate in die CI-Pipeline, das den Build hart stoppt, sobald die Flake-Rate über einen definierten Schwellenwert steigt oder eine bestimmte Anzahl quarantänierter Tests überschritten wird. Ohne diese Schranke sammeln sich Quarantäne-Tests unbegrenzt an, und das ursprüngliche Signal geht trotzdem verloren, nur eine Ebene später.


#!/usr/bin/env bash
# ci/check-flake-rate.sh - fail the build if flake rate exceeds threshold
set -euo pipefail

THRESHOLD=5.0
REPORT_FILE="reports/flaky-tests.json"

if [[ ! -f "$REPORT_FILE" ]]; then
  echo "No flaky-test report found, skipping flake-rate gate."
  exit 0
fi

FLAKE_RATE=$(jq '.flakeRatePercent' "$REPORT_FILE")

echo "Current flake rate: ${FLAKE_RATE}% (threshold: ${THRESHOLD}%)"

if (( $(echo "$FLAKE_RATE > $THRESHOLD" | bc -l) )); then
  echo "FAIL: flake rate ${FLAKE_RATE}% exceeds threshold of ${THRESHOLD}%."
  echo "Quarantine or fix the flagged tests before merging."
  exit 1
fi

echo "PASS: flake rate within acceptable range."

8. Metriken für Flaky-Test-Management: Retry-Rate und Flake-Rate-Trend

Ohne Messung bleibt Flaky-Test-Management Bauchgefühl. Die wichtigste Kennzahl auf Testebene ist die Retry-Rate pro Test: der Anteil der Läufe, in denen ein bestimmter Test mindestens einen Retry benötigt hat, um zu bestehen. Ein Test mit einer Retry-Rate von 40 % ist ein weit dringlicherer Kandidat für eine Untersuchung als einer, der einmal im Monat durch einen kurzen Infrastruktur-Hänger fehlschlägt.

Auf Suiten-Ebene lohnt sich die Flake-Rate als Trend über Zeit, nicht als einzelner Momentwert. Ein leichter Anstieg über mehrere Wochen zeigt oft schleichende Probleme, etwa wachsende Testdatenmengen oder neue geteilte Fixtures, lange bevor die Pipeline insgesamt spürbar instabil wird. Beide Kennzahlen lassen sich aus den Reporter-Daten von Cypress und Playwright extrahieren und in einem einfachen JSON-Report konsolidieren, der wiederum die Basis für Dashboards und automatische Quarantäne-Entscheidungen bildet.


{
  "generatedAt": "2026-07-10T06:15:00Z",
  "totalTests": 842,
  "flakeRatePercent": 3.1,
  "flaggedTests": [
    {
      "title": "checkout > applies discount code before payment",
      "file": "e2e/checkout.spec.js",
      "retryRate": 0.42,
      "lastPassedOnRetry": 1,
      "owner": "team-checkout",
      "status": "quarantined",
      "slaDueDate": "2026-07-24"
    },
    {
      "title": "search > shows autocomplete suggestions",
      "file": "e2e/search.spec.js",
      "retryRate": 0.18,
      "lastPassedOnRetry": 1,
      "owner": "team-search",
      "status": "investigating",
      "slaDueDate": "2026-07-19"
    }
  ]
}

9. Organisatorischer Prozess: Owner-Zuweisung und SLA für Flaky Tests im Vergleich

Technische Tools allein lösen das Flakiness-Problem nicht, wenn niemand für die gemeldeten Tests verantwortlich ist. Ein funktionierender Prozess weist jedem als flaky markierten Test automatisch einen Owner zu, meist das Team, das den betroffenen Code oder Testbereich verantwortet, und setzt eine klare Frist von beispielsweise zwei Wochen, um den Test entweder zu reparieren oder bewusst zu löschen. Ohne diese Frist verstauben Quarantäne-Tests unbegrenzt, weil sie die Pipeline formal nicht mehr stören.

Die folgende Übersicht vergleicht typische Retry-Ansätze mit den empfohlenen Alternativen und zeigt, worauf es bei jedem Schritt konkret ankommt.

Ansatz Problem Empfohlenes Pattern Nutzen
Globale Retries ohne Analyse Verschleiert echte Bugs dauerhaft Retry nur als Signal, mit Logging Sichtbare Flakiness statt stiller Fehler
Retry auch im interaktiven Modus Entwickler übersieht Fehler beim Debuggen openMode-Retries auf 0 setzen Sofortiges Feedback während der Entwicklung
Kein Tracking retried Tests Trend bleibt unsichtbar, Flakiness wächst Custom Reporter + Dashboard Frühwarnung vor Qualitätsverfall
Flaky Test bleibt im Hauptlauf Blockiert Pipeline wiederholt, Team ignoriert Rot Quarantäne-Tag mit CI-Gate Pipeline bleibt aussagekräftig
Kein Owner für flaky Test Niemand fühlt sich zuständig, Ticket verstaubt Automatische Zuweisung + SLA-Frist Klare Verantwortung, schnellere Fixes

Am Ende zahlt sich dieser Prozess doppelt aus: Die Pipeline bleibt als Qualitätssignal glaubwürdig, und die Zahl echter, unentdeckter Bugs sinkt, weil Flakiness konsequent untersucht statt wiederholt wegretryt wird.

Mironsoft

E2E-Testing, Testautomatisierung und CI/CD-Qualitätssicherung für Magento-Shops

Stabile E2E-Tests ohne Flakiness-Falle?

Wir analysieren eure Cypress- und Playwright-Suiten, identifizieren echte Flakiness-Ursachen und richten Retry-Strategien, Quarantäne-Workflows und Metriken so ein, dass eure Pipeline wieder ein verlässliches Signal liefert, statt Bugs zu verschleiern.

Flaky-Test-Audit

Retry-Raten analysieren, echte Bugs von Infrastruktur-Rauschen trennen

Cypress & Playwright Setup

Retry-Konfiguration, Custom Reporter und CI-Gates produktionsreif einrichten

Prozess & Monitoring

Quarantäne-Workflows, Dashboards und Owner-SLAs für nachhaltige Testqualität

10. Zusammenfassung

Blindes Retry-bis-Erfolg löst kein Flakiness-Problem, es verschiebt es lediglich aus dem Blickfeld: Echte Bugs bleiben im Code, während die Testsuite ihre Aussagekraft verliert und Teams beginnen, rote Pipelines reflexhaft neu zu starten statt sie zu untersuchen. Der nachhaltige Ansatz behandelt Retry als Diagnoseinstrument statt als Fix, protokolliert jeden retried-but-passed-Test explizit und macht daraus einen Auftrag zur Untersuchung statt eines stillen Erfolgs.

Sowohl Cypress mit der Trennung von runMode und openMode als auch Playwright mit globalen retries und gezielten test.describe.configure-Overrides bieten die technischen Grundlagen dafür. Entscheidend ist aber der Prozess drumherum: Metriken wie Retry-Rate und Flake-Rate-Trend, ein Quarantäne-Workflow mit hartem CI-Gate und klare Owner-Zuweisungen mit Frist verhindern, dass Flakiness zu dauerhaft ignoriertem Hintergrundrauschen wird.

Test-Retry-Strategien - Das Wichtigste auf einen Blick

Retry als Signal, nicht als Fix

Jeder retried-but-passed-Test wird protokolliert und markiert, statt stillschweigend als Erfolg zu gelten.

Cypress: runMode vs. openMode

CI verträgt begrenzte Retries, im interaktiven Modus sollte openMode auf 0 stehen.

Playwright: retries + Custom Reporter

Globale retries nur für CI, gezielte test.describe.configure-Overrides, Reporter flaggt retried-but-passed.

Prozess: Metriken, Quarantäne, Owner

Retry-Rate und Flake-Rate-Trend messen, Quarantäne mit CI-Gate, Owner-Zuweisung mit Frist.

11. FAQ: Test-Retry-Strategien

1Was ist ein flaky Test und wodurch entsteht Flakiness?
Ein flaky Test liefert bei unverändertem Code mal ein grünes, mal ein rotes Ergebnis. Ursachen: Race Conditions in der Anwendung, Netzwerklatenz, Animationen oder Testreihenfolge-Abhängigkeiten durch geteilten Zustand.
2Warum ist globales Retry-bis-Erfolg gefährlich?
Es unterscheidet nicht zwischen echten Bugs und Infrastruktur-Rauschen. Jeder intermittierende Fehlschlag wird automatisch dauerhaft grün, ohne dass die Ursache untersucht wird.
3Was bedeutet Retry als Triage-Werkzeug?
Ein Test, der erst nach Retry besteht, wird protokolliert und für Untersuchung markiert, statt still als Erfolg zu gelten. Retry kauft Zeit, ersetzt aber nie die Ursachenanalyse.
4Wie konfiguriere ich Retries in Cypress richtig?
Über retries mit getrennten Werten für runMode (CI, meist 1-2) und openMode (interaktiv, meist 0). after:spec protokolliert benötigte Versuche pro Test.
5Wie konfiguriere ich Retries in Playwright richtig?
Über die globale retries-Option, meist nur für CI, plus test.describe.configure für gezielte Ausnahmen. Ein Custom Reporter flaggt retried-but-passed-Tests bei onTestEnd.
6Wie unterscheide ich Umgebungs-Flakiness von echten Bugs?
Test isoliert und mehrfach lokal ausführen, Netzwerklogs/Screenshots/Videos auswerten. Reproduzierbar unter stabilen Bedingungen deutet fast immer auf einen echten Bug hin.
7Was ist ein Quarantäne-Workflow für flaky Tests?
Mit @flaky markierte Tests laufen in einer nicht-blockierenden Suite weiter. Ein CI-Gate stoppt den Build, sobald Flake-Rate oder Quarantäne-Anzahl einen Schwellenwert übersteigen.
8Welche Metriken sollte ich für Flaky Tests tracken?
Retry-Rate pro Test und die Flake-Rate der Suite als Trend über Zeit. Beide lassen sich aus Cypress- und Playwright-Reporter-Daten extrahieren.
9Wer ist verantwortlich, wenn ein Test als flaky markiert wird?
Automatisch zugewiesener Owner, meist das verantwortliche Team, mit einer klaren Frist von z.B. zwei Wochen zum Fixen oder bewussten Löschen.
10Sollte ich flaky Tests einfach löschen?
Nur, wenn sie keinen relevanten Pfad mehr absichern oder redundant sind. Tests für kritische Flows sollten repariert statt gelöscht werden.