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.
Inhaltsverzeichnis
- 1. Was Test-Flakiness ist und warum blindes Retry-bis-Erfolg gefährlich ist
- 2. Wie Retry-bis-Erfolg echte Bugs verschleiert und das Vertrauen in die Suite untergräbt
- 3. Retry als Signal- und Triage-Werkzeug statt permanenter Fix
- 4. Retry-Konfiguration in Cypress: runMode vs. openMode
- 5. Retry-Konfiguration in Playwright: globale Retries und test.describe.configure
- 6. Umgebungsbedingte Flakiness von echten Anwendungsfehlern unterscheiden
- 7. Quarantäne- und Known-Flaky-Test-Workflows
- 8. Metriken für Flaky-Test-Management: Retry-Rate und Flake-Rate-Trend
- 9. Organisatorischer Prozess: Owner-Zuweisung und SLA für Flaky Tests im Vergleich
- 10. Zusammenfassung
- 11. FAQ
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.