Flaky Tests gehören zu den größten Frustfaktoren in der Testautomatisierung. Ein häufiger Grund dafür sind feste Wartezeiten wie sleep() oder waitForTimeout().
In diesem Artikel zeige ich dir praxisnah, wie du in Playwright stabile, zustandsbasierte Warte-Strategien einsetzt – inklusive Negativbeispielen, Best Practices und fortgeschrittenen API-Waits. Das Ziel ist: Tests, die deterministisch laufen, verlässlich sind und leichter gewartet werden können.
Zeit vs. Zustand: Das Kernproblem Flaky Tests

In der Praxis zeigt sich immer wieder, dass instabile Tests nur selten durch fehlerhaften Code entstehen, sondern durch unzureichende Warte-Strategien. Besonders häufig betroffen sind Tests, die auf feste Zeitintervalle setzen, anstatt auf den tatsächlichen Zustand der Anwendung zu reagieren.
Dieses Problem tritt vor allem dann auf, wenn Tests lokal stabil laufen, im Continuous-Integration-Umfeld jedoch sporadisch fehlschlagen – obwohl sich am Code nichts geändert hat.
Um zu verstehen, warum das passiert und wie man es vermeidet, lohnt sich ein genauer Blick auf den Einsatz von waitForTimeout() und ähnlichen Mechanismen. Anhand eines typischen Szenarios wird im folgenden Abschnitt deutlich, warum zeitbasiertes Warten zu Flakiness führt und weshalb zustandsbasierte Strategien in Playwright die deutlich robustere Alternative sind.
Warum waitForTimeout() Tests flaky macht
Ein klassisches Szenario:
- Lokal: Test läuft grün
- CI: Test schlägt sporadisch fehl
- Code wurde nicht geändert
Der Grund:
CI-Umgebungen sind langsamer, unvorhersehbarer und parallelisiert.
Feste Wartezeiten:
- sind zu kurz → Test schlägt fehl
- sind zu lang → Test wird langsam
- prüfen keinen Zustand, sondern nur Zeit
❌ Anti-Pattern: Warten mit waitForTimeout()
Ein typischer Fehler beim Testen von Websites oder Webanwendungen ist das zeitbasierte Warten. Viele Testskripte setzen auf feste Intervalle, um Ladezeiten von Login-Formularen oder dynamischen Inhalten abzufangen.
Am folgenden Beispiel zeige ich anhand des Login-Formulars meiner WordPress-Website, wie es nicht umgesetzt werden sollte:
import { test, expect } from '@playwright/test';
test('WordPress Login zeigt Dashboard (flaky)', async ({ page }) => {
await page.goto('https://walter-test-engineering.de/login.php');
await page.fill('#user_login', 'admin');
await page.fill('#user_pass', 'geheimespasswort');
await page.click('#wp-submit');
await page.waitForTimeout(3000); // ❌ instabil
.....
});
Warum das problematisch ist:

-
Die 3 Sekunden sind geraten – meine WordPress-Seite kann mal schneller oder langsamer laden.
-
API-Aufrufe oder das Rendern des Dashboards können länger dauern, z. B. durch Plugins oder langsame Serverantworten.
-
Der Test schlägt zufällig fehl – besonders im CI-Umfeld, wenn die Umgebung plötzlich eine andere ist.
-
Fehler lassen sich nur schwer reproduzieren.
>> Das ist kein echtes Warten auf den Zustand deiner WordPress-Seite – es ist schlichtes Hoffen, dass die Zeit reicht
Playwright-Best-Practice: Zustand statt Zeit
Playwright bringt Auto-Waiting mit.
Das bedeutet: Aktionen wie click() oder fill() warten automatisch, bis das Element sichtbar und klickbar ist.
Noch stabiler wird es mit expliziten Zustands-Checks.
✅ Stabiler Test mit sichtbarkeitsbasiertem Warten
Anstatt feste Wartezeiten zu verwenden, prüft dieser Test gezielt den tatsächlichen Zustand der WordPress-Seite – in diesem Fall, ob die Admin-Leiste nach dem Login sichtbar ist:
import { test, expect } from '@playwright/test';
test('WordPress Login zeigt Dashboard stabil', async ({ page }) => {
await page.goto('https://walter-test-engineering.de/wp-login.php');
await page.fill('#user_login', 'admin');
await page.fill('#user_pass', 'geheimespasswort');
await page.click('#wp-submit');
await expect(page.locator('#wpadminbar')).toBeVisible();
// ✅ Hier: wartet gezielt auf den Zustand, ohne vorheriges Warten
});
Warum dieser Ansatz stabil ist:
- Der Test wartet aktiv darauf, dass die Admin-Leiste auf deiner WordPress-Seite sichtbar wird.
- Es wird kein hart codiertes Timeout verwendet.
- Dadurch läuft der Test schnell, deterministisch und zuverlässig – sowohl lokal als auch im CI.
- Selbst, wenn Plugins oder langsame Server die Ladezeit verändern, schlägt der Test nicht mehr zufällig fehl.
Moderne Ansätze mit Playwright
Playwright setzt konsequent auf zustandsbasiertes Warten. Anstatt feste Wartezeiten oder manuelle Prüfungen einzubauen, wartet Playwright automatisch auf den richtigen Zustand eines Elements, bevor eine Aktion ausgeführt wird.
Kompakte Variante
await page.getByRole('button', { name: 'Login' }).click();
Dieser einzelne Befehl wartet automatisch darauf, dass der Button:
- im DOM vorhanden ist
- sichtbar ist
- aktiviert und klickbar ist
Erst, wenn alle Bedingungen erfüllt sind, wird der Klick ausgeführt.
Eine weitere, gut lesbare Variante
const button = page.getByRole('button', { name: 'Login' });
await expect(button).toBeVisible();
await expect(button).toBeEnabled();
await button.click();
Diese Variante ist besonders geeignet, wenn:
- der gewünschte Zustand bewusst dokumentiert werden soll
- Tests als lebende Spezifikation dienen
- Lesbarkeit wichtiger ist als maximale Kürze
Warum diese Ansätze stabil sind
- Kein zeitbasiertes Warten
- Automatische Retries bei kurzzeitigen UI-Zuständen
- Deterministisches Verhalten
- Deutlich weniger Flakiness
Ein fortgeschrittenes Beispiel: Warten auf API + UI
Bei dynamischen Inhalten reicht es oft nicht aus, nur auf DOM-Elemente zu warten. AJAX-Aufrufe laden Daten asynchron, und nur auf die UI zu warten, kann flaky Tests verursachen. Nachfolgend möchte ich beispielhaft nach Blog-Beiträgen zur „Testautomatisierung“ auf einer Website suchen.
❌ Negativbeispiel
await page.fill('#search-input', 'Testautomatisierung');
await page.click('#search-submit');
// Prüft direkt die UI, ohne auf die API zu warten
await expect(page.locator('.post')).toHaveCountGreaterThan(0);
Problem:
-
WordPress REST-API-Aufruf (
/wp-json/wp/v2/posts) kann länger dauern -
Liste der Suchergebnisse ist möglicherweise noch leer
-
Test schlägt zufällig fehl → flakig und schwer reproduzierbar
✅ Best Practice: API-Wait + UI-Check
await page.fill('#search-input', 'Testautomatisierung');
await page.click('#search-submit');
// Warten, bis die WordPress REST-API die Beiträge liefert
await page.waitForResponse(response =>
response.url().includes('/wp-json/wp/v2/posts') &&
response.status() === 200
);
// Danach die UI prüfen
await expect(page.locator('.post')).toHaveCountGreaterThan(0);
Warum diese Strategie robust ist:
-
Test wartet, bis die API tatsächlich Beiträge liefert
-
UI wird erst geprüft, wenn die Inhalte vorhanden sind
-
Test ist deterministisch, zuverlässig und robust – auch bei langsamen Servern oder Netzwerkverzögerungen
Typische Fehler bei Warte-Strategien
- waitForTimeout() in Produktivtests
– Warten auf falsche API-Endpunkte
– Nur auf DOM warten, obwohl Daten asynchron geladen werden
– Zustände nicht explizit prüfen
Cheat-Sheet: Stabile Warte-Strategien in Playwright
| Strategie | Playwright-Befehl | Wann einsetzen |
|---|---|---|
| Auto-Waiting | click(), fill() |
Standard |
| Sichtbarkeit | expect(locator).toBeVisible() |
UI-Zustand |
| Aktiviert | expect(locator).toBeEnabled() |
Buttons / Aktionen |
| Text / Count | toHaveText(), toHaveCountGreaterThan() |
Inhalte prüfen |
| Netzwerk | waitForResponse() |
API-abhängige Views |
| Load-State | waitForLoadState() |
Navigation |
| Timeout | waitForTimeout() |
Nur Debug / Demo |
Fazit
Stabile Playwright-Tests entstehen nicht durch längere Wartezeiten, sondern durch sauberes Warten auf Zustände. Wer feste Timeouts vermeidet und stattdessen gezielt prüft, wann ein Element sichtbar, aktiv oder inhaltlich korrekt ist, reduziert Flakiness erheblich.
Besonders wirkungsvoll wird das, wenn UI-Checks mit API- oder Netzwerk-Waits kombiniert werden, sodass Tests erst dann weitermachen, wenn die zugrunde liegenden Daten tatsächlich geladen sind.
Playwright unterstützt diesen Ansatz von Haus aus mit Auto-Waiting, das viele klassische Selenium-Probleme bereits löst – vorausgesetzt, man nutzt es bewusst und ergänzt es dort, wo explizite Zustandsprüfungen sinnvoll sind. Das Ergebnis sind Tests, die nicht nur stabiler, sondern auch schneller, CI-tauglich und langfristig leichter wartbar sind.
Abschließend möchte ich Dir mitgeben:
Wer auf Zeit wartet, hofft.
Wer auf Zustände wartet, testet zuverlässig. : )