Playwright Auto-Waiting verstehen statt sleep() zu nutzen
PASS
expect()
Playwright · E2E-Testing · Web-First Assertions · Testautomatisierung
Playwright Auto-Waiting verstehen statt sleep() zu nutzen
Warum Actionability-Checks Flaky Tests verhindern

Wer E2E-Tests mit page.waitForTimeout oder festen Sleep-Aufrufen stabilisiert, bekämpft nur Symptome statt Ursachen. Playwright prüft vor jeder Aktion automatisch, ob ein Element sichtbar, stabil, aktiviert und tatsächlich klickbar ist, und wiederholt diese Prüfung, bis sie erfüllt ist oder ein Timeout erreicht wird. Dieser Artikel zeigt, wie diese Actionability-Checks funktionieren, warum feste Wartezeiten Flakiness verschleiern statt beheben und wie man Timeout-Fehler mit dem Trace Viewer gezielt diagnostiziert.

12 Min. Lesezeit Actionability-Checks · Web-First Assertions · Trace Viewer Playwright · Cypress · E2E-Testing

1. Was Auto-Waiting ist und welches Problem es löst

Auto-Waiting ist das Kernprinzip, das Playwright von älteren Test-Frameworks unterscheidet: Bevor eine Aktion wie click(), fill() oder check() ausgeführt wird, prüft Playwright automatisch, ob das Ziel-Element tatsächlich bereit für diese Aktion ist. Es gibt also keinen manuellen driver.wait(condition) Aufruf wie bei klassischem Selenium, bei dem Entwickler selbst festlegen müssen, worauf gewartet wird und wie lange. Playwright übernimmt diese Entscheidung selbst und wiederholt die Prüfung in kurzen Intervallen, bis sie erfüllt ist oder ein konfigurierbares Timeout erreicht wird.

Das Problem, das Auto-Waiting löst, ist so alt wie E2E-Testing selbst: Eine Webseite lädt Inhalte asynchron nach, Animationen verzögern die Interaktivität, und ein Server-Request braucht mal 100 Millisekunden, mal 1500. Ein Test, der sofort nach dem Laden der Seite klickt, trifft oft ein Element, das zwar im DOM existiert, aber visuell noch nicht bereit ist. Auto-Waiting verschiebt diese Verantwortung komplett vom Testautor auf das Framework, wodurch ganze Klassen von Timing-Fehlern gar nicht erst entstehen.

2. Die Actionability-Checks im Detail

Vor jeder Aktion führt Playwright eine feste Sequenz von Actionability-Checks aus. Ein Element muss im DOM angehängt sein (attached), sichtbar sein (visible, also eine von Null verschiedene Bounding Box ohne visibility: hidden), stabil sein (stable, also während mindestens zwei aufeinanderfolgenden Frames an derselben Position, was CSS-Transitions und Animationen berücksichtigt) und darf nicht durch ein anderes Element überdeckt werden (receives events). Für click() kommt zusätzlich enabled hinzu, für fill() zusätzlich editable.

Jeder dieser Checks wird nicht einmalig geprüft, sondern in einer Polling-Schleife wiederholt, typischerweise alle paar Millisekunden, bis alle Bedingungen gleichzeitig erfüllt sind. Erst dann führt Playwright die eigentliche Aktion aus. Schlägt ein einzelner Check dauerhaft fehl, etwa weil ein Modal-Overlay das Zielelement dauerhaft verdeckt, bricht Playwright nach Ablauf des actionTimeout mit einer präzisen Fehlermeldung ab, die genau benennt, welcher Check nicht erfüllt wurde.

3. Warum waitForTimeout und sleep() fast immer falsch sind

page.waitForTimeout() in Playwright oder cy.wait(ms) in Cypress pausieren die Testausführung für eine feste Anzahl Millisekunden, unabhängig davon, was auf der Seite tatsächlich passiert. Dieses Pattern ist fast immer die falsche Lösung, weil es zwei Fehlerarten gleichzeitig erzeugt: Ist die Wartezeit zu kurz, bleibt der Test flaky, weil die Bedingung manchmal noch nicht erfüllt ist. Ist sie zu lang, verschwendet jeder einzelne Testlauf Zeit, die sich über hunderte Tests in der CI-Pipeline zu Minuten summiert.

Der eigentliche Schaden ist subtiler: Ein fester Sleep behebt nicht die Ursache der Verzögerung, er verdeckt sie nur so lange, wie die reale Latenz unter dem gewählten Wert bleibt. Steigt die Serverlast, ändert sich ein Netzwerkpfad oder läuft die CI-Umgebung auf langsameren Runnern, reißt derselbe Sleep-Wert plötzlich und der Test wird flaky, ohne dass sich am Testcode etwas geändert hat. Ein Team mit vielen festen Sleeps in der Test-Suite bekämpft also Symptome in einer Endlosschleife, statt die zugrunde liegende Timing-Abhängigkeit einmalig sauber zu modellieren.


// bad-test.spec.js - Anti-Pattern: fester Sleep nach dem Klick auf "In den Warenkorb"
import { test, expect } from '@playwright/test';

test('add product to cart shows confirmation', async ({ page }) => {
  await page.goto('/catalog/product/view/id/42');
  await page.click('#product-addtocart-button');

  // Anti-Pattern: feste Wartezeit, rät wie lange der AJAX-Call dauert
  await page.waitForTimeout(2000);

  const toast = page.locator('.message-success');
  const text = await toast.textContent();
  expect(text).toContain('You added');
});

4. Web-First Assertions statt manuellem Polling

Web-First Assertions wie expect(locator).toBeVisible() oder expect(locator).toHaveText() unterscheiden sich fundamental von klassischen Assertions auf einem einmalig ausgelesenen Wert. Statt den Text sofort mit textContent() abzurufen und zu vergleichen, wiederholt Playwright die zugrunde liegende Prüfung automatisch, bis sie entweder erfolgreich ist oder das expect-Timeout, standardmäßig 5 Sekunden, erreicht ist. Das Ergebnis: Der Test wartet exakt so lange wie nötig, nicht länger und nicht kürzer.

Der Unterschied zu einer manuellen Polling-Schleife, die zum Beispiel alle 500 Millisekunden isVisible() abfragt, liegt vor allem in der Robustheit und Lesbarkeit. Eigene Polling-Logik muss Sonderfälle wie Exceptions bei nicht existierenden Elementen, Race Conditions und Abbruchbedingungen selbst behandeln, was schnell zu inkonsistentem Code über verschiedene Testdateien hinweg führt. Web-First Assertions kapseln dieses Verhalten einmal korrekt im Framework, liefern bei Fehlschlag eine aussagekräftige Fehlermeldung mit dem letzten beobachteten Zustand und sind der De-facto-Standard für stabile Playwright-Tests.


// fixed-test.spec.js - Web-First Assertion wartet automatisch, bis der Toast erscheint
import { test, expect } from '@playwright/test';

test('add product to cart shows confirmation', async ({ page }) => {
  await page.goto('/catalog/product/view/id/42');
  await page.click('#product-addtocart-button');

  // Kein fester Wait nötig: toBeVisible() pollt, bis die Assertion erfüllt ist
  const toast = page.locator('.message-success');
  await expect(toast).toBeVisible();
  await expect(toast).toContainText('You added');
});

5. Praxisbeispiel: Einen Flaky Test reparieren

Ein typisches Beispiel aus der Praxis: Ein Add-to-Cart-Button löst einen AJAX-Request aus, der Warenkorb wird über Knockout- oder Alpine-Bindings aktualisiert, und danach erscheint eine Erfolgsmeldung. Der ursprüngliche Test aus dem ersten Codebeispiel nutzt page.waitForTimeout(2000), um auf diese Kette zu warten. Lokal auf einem schnellen Rechner lief der Test zuverlässig, in der CI-Pipeline mit mehreren parallelen Workern und geteilten Ressourcen schlug er sporadisch fehl, weil der Request gelegentlich über zwei Sekunden brauchte.

Die Lösung im zweiten Codebeispiel ersetzt den festen Sleep durch expect(toast).toBeVisible() gefolgt von expect(toast).toContainText(). Playwright wartet jetzt exakt so lange, bis die Erfolgsmeldung tatsächlich im DOM erscheint und lesbar ist, egal ob das 200 Millisekunden oder 3 Sekunden dauert. Der Test wurde dadurch nicht nur zuverlässiger, sondern im Median auch schneller, weil er nicht mehr unnötig die vollen zwei Sekunden wartet, wenn die Meldung längst da ist.

6. Timeouts richtig konfigurieren

Playwright kennt mehrere Timeout-Ebenen, die unabhängig voneinander konfiguriert werden. Der globale test.timeout begrenzt die Gesamtlaufzeit eines einzelnen Tests, standardmäßig 30 Sekunden. Der actionTimeout begrenzt, wie lange eine einzelne Aktion wie click() oder fill() auf erfüllte Actionability-Checks wartet. Der separate expect-Timeout, standardmäßig 5 Sekunden, gilt ausschließlich für Web-First Assertions und ist bewusst kürzer, weil eine fehlgeschlagene Assertion meist ein reales Problem anzeigt und nicht endlos maskiert werden soll.

In der Praxis ist es sinnvoller, diese Timeouts einmalig zentral in der playwright.config.js zu justieren, statt in einzelnen Tests wiederholt Sonderwerte zu setzen. Ein zu kurzer actionTimeout erzeugt False Positives auf langsamen CI-Runnern, ein zu langer versteckt echte Performance-Regressionen, weil Tests einfach länger warten, statt sichtbar fehlzuschlagen. Die Empfehlung: Timeouts so knapp wie möglich halten, damit ein Testfehler tatsächlich ein Signal für ein reales Problem bleibt, nicht nur eine willkürliche Zahl.


// playwright.config.js - Timeouts zentral justieren statt manuelle Waits einbauen
import { defineConfig } from '@playwright/test';

export default defineConfig({
  timeout: 30_000, // Gesamt-Timeout pro Test
  expect: {
    timeout: 5_000, // Standard-Timeout für Web-First Assertions wie toBeVisible()
  },
  use: {
    actionTimeout: 10_000, // Timeout für Actionability-Checks vor click(), fill(), etc.
    navigationTimeout: 15_000, // Timeout für page.goto() und Navigationen
    trace: 'retain-on-failure', // Trace nur bei fehlgeschlagenen Tests aufzeichnen
  },
});

7. Robuste Locator-Strategien und Custom Assertions

Die Stabilität eines Locators beeinflusst direkt, wie zuverlässig die Actionability-Checks greifen. Locators wie getByRole() oder getByTestId() adressieren Elemente über stabile, semantische Attribute statt über CSS-Klassen oder DOM-Struktur, die sich bei jedem Frontend-Refactoring ändern können. Ein Locator, der bei jedem Build ins Leere greift, weil sich eine CSS-Klasse geändert hat, erzeugt Timeouts, die wie ein Actionability-Problem aussehen, tatsächlich aber ein reines Selektor-Problem sind.

Für Fälle, die über einfache Sichtbarkeits- oder Text-Assertions hinausgehen, etwa das Warten auf einen komplexen berechneten Zustand in einem Drittanbieter-Widget, bietet Playwright expect.poll() und toPass(). Beide wiederholen einen beliebigen Callback automatisch, bis er ohne Fehler durchläuft oder das Timeout erreicht ist, und übernehmen damit dieselbe Retry-Logik wie die eingebauten Web-First Assertions, nur für benutzerdefinierte Prüfungen. So bleibt auch komplexe Wartelogik deklarativ und muss nicht als manuelle while-Schleife nachgebaut werden.


// custom-locator.spec.js - flakiges Drittanbieter-Widget mit expect.poll() prüfen
import { test, expect } from '@playwright/test';

test('mini cart badge reflects item count', async ({ page }) => {
  await page.goto('/');
  const badge = page.getByTestId('minicart-item-count');

  await page.getByRole('button', { name: 'Add to cart' }).click();

  // Auto-retrying: wiederholt den Callback, bis er passt oder das Timeout erreicht ist
  await expect.poll(async () => {
    return await badge.textContent();
  }, { message: 'minicart badge did not update', timeout: 10_000 }).toBe('1');

  // Gleichwertige, idiomatischere Web-First Assertion für den einfachen Fall
  await expect(badge).toHaveText('1');
});

8. Timeout-Fehler lesen und verstehen

Wenn eine Aktion nach Ablauf des actionTimeout fehlschlägt, gibt Playwright keine generische Timeout-Meldung aus, sondern protokolliert den Verlauf der Actionability-Checks im Detail. Die Fehlermeldung listet typischerweise auf: Der Locator wurde aufgelöst, das Element wurde gefunden, das Element ist sichtbar, das Element ist aber nicht stabil, weil eine CSS-Transition noch läuft, oder das Element wird von einem anderen Element überdeckt. Diese Reihenfolge zeigt exakt, an welchem Check die Aktion hängen geblieben ist.

Ein häufiges Muster in der Fehlermeldung ist element is not visible, obwohl das Element im DOM existiert. Das deutet fast immer auf display: none, eine display: none übergeordnete Elternstruktur oder eine Opacity von 0 hin. Ebenso häufig: element is outside of the viewport, was oft durch fehlendes Scrollen ausgelöst wird, das Playwright zwar automatisch versucht, aber nicht bei jeder Scroll-Container-Konstruktion zuverlässig auflösen kann. Das genaue Lesen dieser Fehlermeldung spart oft mehr Zeit als jedes Trial-and-Error mit zusätzlichen Waits.

9. Trace Viewer und Inspector im Einsatz

Der Trace Viewer macht die Actionability-Checks visuell nachvollziehbar. Mit trace: 'retain-on-failure' in der Konfiguration oder --trace on beim Testlauf zeichnet Playwright pro Test eine vollständige Timeline auf, inklusive DOM-Snapshots vor und nach jeder Aktion, Netzwerk-Requests und Konsolenausgaben. Nach einem fehlgeschlagenen Lauf öffnet npx playwright show-trace den Trace in einer interaktiven Oberfläche, in der jeder Schritt einzeln durchgeklickt werden kann.

Für die Entwicklung neuer Tests ist der Playwright Inspector, aktiviert über PWDEBUG=1 oder den --debug Flag, oft direkter: Er pausiert die Ausführung vor jeder Aktion und zeigt in Echtzeit, welcher Actionability-Check gerade aktiv geprüft wird. Beide Werkzeuge ersetzen das Trial-and-Error-Debugging mit zusätzlichen console.log-Aufrufen oder testweise eingefügten Sleeps durch eine präzise, reproduzierbare Diagnose. Die folgende Tabelle fasst zusammen, wie sich explizite Waits und Auto-Waiting über die relevanten Dimensionen unterscheiden.


# Suite mit aktiviertem Tracing ausführen, danach den Trace eines fehlgeschlagenen Tests öffnen
npx playwright test --trace on

# Einzelne Trace-Datei nach dem Lauf inspizieren
npx playwright show-trace test-results/checkout-add-to-cart/trace.zip

# Alternativ: Playwright Inspector für Schritt-für-Schritt-Debugging starten
PWDEBUG=1 npx playwright test checkout.spec.js
Dimension Explizite Waits (sleep/waitForTimeout) Auto-Waiting / Web-First Assertions Auswirkung
Zuverlässigkeit Test bricht ab, wenn der Server langsamer ist als der feste Sleep Wartet exakt so lange, bis die Bedingung erfüllt ist Weniger Flaky Tests
Testlaufzeit Immer die volle Sleep-Dauer, auch wenn das Element längst bereit ist Endet sofort, sobald die Bedingung erfüllt ist Kürzere CI-Laufzeiten
Wartungsaufwand Sleep-Werte müssen bei jeder Performance-Änderung manuell nachjustiert werden Kein Nachjustieren nötig, Timeout ist ein Sicherheitsnetz Weniger technische Schulden
Fehlerdiagnose Timeout meldet nur "2000ms vorbei", keine Ursache Trace Viewer zeigt exakt, welcher Actionability-Check fehlschlägt Schnelleres Debugging
Skalierbarkeit unter Last Feste Werte reichen in CI oft nicht, lokal aber schon Timeout skaliert unabhängig von der tatsächlichen Systemlast Stabil in CI und lokal

Mironsoft

E2E-Testing, Playwright-Setups und stabile CI-Pipelines für Magento- und Hyvä-Shops

Flaky Tests endgültig loswerden?

Wir analysieren eure bestehende Playwright- oder Cypress-Suite, ersetzen feste Sleeps durch Web-First Assertions und richten Trace-Viewer-Auswertung und Timeout-Konfiguration für stabile CI-Läufe ein.

Flaky-Test-Audit

Analyse bestehender Tests auf waitForTimeout, Sleeps und instabile Locators

Test-Refactoring

Feste Waits durch Web-First Assertions und expect.poll() ersetzen

CI-Integration

Timeout-Konfiguration und Trace-Auswertung in der Pipeline aufbauen

10. Zusammenfassung

Auto-Waiting ist der Grund, warum gut geschriebene Playwright-Tests ohne einen einzigen manuellen Sleep auskommen. Vor jeder Aktion prüft Playwright automatisch, ob ein Element angehängt, sichtbar, stabil, aktiviert und nicht überdeckt ist, und wiederholt diese Prüfung, bis sie erfüllt ist oder ein konfigurierbares Timeout erreicht wird. page.waitForTimeout() und vergleichbare feste Wartezeiten unterlaufen dieses Prinzip, weil sie weder auf reale Bedingungen reagieren noch die Ursache einer Verzögerung beheben, sondern sie nur so lange verdecken, wie die reale Latenz unter dem gewählten Wert bleibt.

Web-First Assertions wie toBeVisible() und toHaveText() übertragen dasselbe Retry-Prinzip auf Prüfungen und sind die richtige Wahl gegenüber manuellen Polling-Schleifen. Wenn ein Timeout dennoch auftritt, liefert die Fehlermeldung selbst schon die wichtigste Diagnose, und der Trace Viewer macht den Verlauf der Actionability-Checks vollständig sichtbar. Wer diese Werkzeuge konsequent nutzt, reduziert Flaky Tests strukturell, statt sie mit immer größeren Sleep-Werten notdürftig zu kaschieren.

Playwright Auto-Waiting - Das Wichtigste auf einen Blick

Actionability-Checks

Attached, visible, stable, enabled und receives events werden vor jeder Aktion automatisch geprüft und wiederholt.

waitForTimeout vermeiden

Feste Sleeps verdecken Timing-Probleme statt sie zu lösen und sind eine Hauptursache für Flaky Tests.

Web-First Assertions

toBeVisible()/toHaveText() retryen automatisch bis zum expect-Timeout, keine manuelle Polling-Schleife nötig.

Trace Viewer nutzen

trace: 'retain-on-failure' und show-trace zeigen exakt, welcher Check bei einem Timeout fehlschlug.

11. FAQ: Playwright Auto-Waiting verstehen

1Was ist Auto-Waiting in Playwright?
Der Mechanismus, mit dem Playwright vor jeder Aktion automatisch prüft, ob das Zielelement bereit ist, statt dass der Testautor manuell wartet. Die Prüfung wird wiederholt, bis sie erfüllt ist oder ein Timeout erreicht wird.
2Welche Actionability-Checks führt Playwright vor einer Aktion aus?
Attached, visible, stable, enabled und receives events. Für fill() zusätzlich editable. Alle Checks werden gemeinsam geprüft, bevor die Aktion ausgeführt wird.
3Warum ist page.waitForTimeout() ein Anti-Pattern?
Reagiert nicht auf den tatsächlichen Zustand der Seite. Zu kurz heißt flaky, zu lang heißt verschwendete Zeit. Verdeckt zudem die eigentliche Ursache der Verzögerung.
4Was unterscheidet eine Web-First Assertion von einer normalen Assertion?
Web-First Assertions wiederholen die Prüfung automatisch bis zum expect-Timeout. Normale Assertions lesen einen Wert einmalig aus und prüfen ihn sofort ohne erneuten Versuch.
5Wie lange wartet Playwright standardmäßig auf eine Aktion?
actionTimeout ist Teil des globalen Test-Timeouts von 30 Sekunden. Web-First Assertions nutzen ein separates expect-Timeout von standardmäßig 5 Sekunden.
6Wie unterscheiden sich actionTimeout und expect-Timeout?
actionTimeout gilt für Aktionen wie click(). Der expect-Timeout gilt nur für Web-First Assertions und ist bewusst kürzer, damit Fehler schneller sichtbar werden.
7Wie finde ich heraus, welcher Check bei einem Timeout fehlschlug?
Die Fehlermeldung listet den Verlauf der geprüften Bedingungen chronologisch auf. Der Trace Viewer zeigt denselben Verlauf zusätzlich visuell mit DOM-Snapshots.
8Was macht der Playwright Trace Viewer?
Zeichnet pro Test eine vollständige Timeline mit DOM-Snapshots, Requests und Konsolenausgaben auf. npx playwright show-trace macht jeden Schritt einzeln nachvollziehbar.
9Wann ist ein manueller Wait doch gerechtfertigt?
Nur in seltenen Fällen wie dem gezielten Testen von Debounce-Verhalten. Als generelle Lösung für Timing-Probleme bleibt ein fester Wait die falsche Wahl.
10Gilt Auto-Waiting auch für Cypress?
Cypress hat ein ähnliches Prinzip mit eingebautem Retry, aber andere Timeout-Defaults. cy.wait(ms) mit fester Zahl ist dort genauso ein Anti-Pattern wie waitForTimeout() in Playwright.