Playwright ist ein wirklich schnelles Tool für die Automatisierung von Tests. Das Problem entsteht meistens erst dann, wenn das Projekt wächst, immer mehr Tests dazukommen und eine Test-Suite, die am Anfang vielleicht eine Minute gedauert hat, plötzlich 10, 15 oder sogar 30 Minuten läuft.
Und dann kommt irgendwann die typische Frage:
Kann man das irgendwie schneller machen?
Ja, kann man.
Natürlich will ich hier nicht versprechen, dass jedes Projekt automatisch exakt 50% schneller wird. Das hängt stark von der Anwendung, der Testumgebung, der Anzahl der Tests, der Qualität der Tests und der CI/CD-Konfiguration ab.
Aber aus meiner Erfahrung liegt das Problem sehr oft nicht daran, dass Playwright langsam ist. Die Tests sind langsam, weil wir bestimmte Dinge nicht optimal machen.
Typische Ursachen sind zum Beispiel:
- der Benutzer wird in jedem Test neu eingeloggt,
- im Code werden künstliche Timeouts verwendet,
- Tests laufen nur nacheinander,
- parallele Ausführung wird nicht genutzt,
- Tests sind voneinander abhängig,
- Testdaten werden über die UI vorbereitet,
- bei jedem kleinen Commit laufen alle Tests auf allen Browsern,
- einzelne End-to-End-Tests prüfen viel zu viele Dinge auf einmal.
In diesem Beitrag zeige ich dir mehrere praktische Ansätze, mit denen du Playwright-Tests deutlich schneller machen kannst.
1. Miss zuerst, wo du wirklich Zeit verlierst
Bevor du anfängst, irgendetwas zu optimieren, solltest du zuerst prüfen, wo die meiste Zeit verloren geht.
Das ist wichtig, weil man schnell denkt: „Playwright ist langsam“. Nach ein paar Minuten Analyse stellt sich aber oft heraus, dass ganz andere Dinge das Problem sind.
Zum Beispiel:
- Login,
- Erstellen von Testdaten,
- Warten auf API-Antworten,
- unnötig lange Wege durch die Anwendung,
- wiederholte Schritte in jedem einzelnen Test.
Du kannst deine Tests ganz normal starten:
npx playwright testDanach kannst du den HTML-Report öffnen:
npx playwright show-reportIm Report siehst du sehr schnell, welche Tests am längsten dauern. Genau dort solltest du anfangen.
Es bringt wenig, einen Test zu optimieren, der 2 Sekunden dauert, wenn daneben ein Test 45 Sekunden läuft und dabei Login, Datenerstellung, mehrere Formulare und vielleicht noch eine E-Mail-Prüfung enthält.
Erst die größten Zeitfresser finden. Dann optimieren.
2. Aktiviere parallele Testausführung
Eine der größten Stärken von Playwright ist die parallele Ausführung von Tests.
Das bedeutet: Die Tests laufen nicht alle nacheinander, sondern mehrere Tests können gleichzeitig ausgeführt werden. Gerade bei größeren Test-Suites kann das einen riesigen Unterschied machen.
Eine beispielhafte Konfiguration in playwright.config.ts könnte so aussehen:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
workers: process.env.CI ? 4 : undefined,
retries: process.env.CI ? 1 : 0,
reporter: 'html',
use: {
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
],
});Die wichtigsten Optionen sind hier:
fullyParallel: trueund:
workers: process.env.CI ? 4 : undefinedMit fullyParallel erlaubst du Playwright, Tests vollständig parallel auszuführen. Mit workers bestimmst du, wie viele Prozesse gleichzeitig laufen dürfen.
Wenn du zum Beispiel 100 Tests hast und jeder Test nur ein paar Sekunden dauert, kann parallele Ausführung die gesamte Laufzeit stark reduzieren.
Aber es gibt einen wichtigen Punkt:
Tests müssen unabhängig voneinander sein.
Wenn Test B nur funktioniert, weil vorher Test A bestimmte Daten erstellt hat, bekommst du bei paralleler Ausführung schnell zufällige Fehler.
Eine gute Regel lautet deshalb:
Jeder Test sollte seine eigenen Daten vorbereiten oder mit isolierten Testdaten arbeiten.
Tests sollten nicht davon abhängig sein, in welcher Reihenfolge sie ausgeführt werden.
3. Logge den Benutzer nicht in jedem Test neu ein
Das ist einer der häufigsten Gründe, warum Playwright-Tests unnötig langsam werden.
Stell dir vor, du hast 80 Tests. Und jeder Test macht am Anfang genau dasselbe:
- Login-Seite öffnen,
- E-Mail eingeben,
- Passwort eingeben,
- Login-Button klicken,
- auf das Dashboard warten,
- erst danach beginnt der eigentliche Test.
Wenn der Login 5 Sekunden dauert, verlierst du bei 80 Tests schon über 6 Minuten nur für Login-Schritte.
Und in den meisten Fällen testest du den Login ja gar nicht wirklich in jedem Test. Der Login ist nur die Voraussetzung, damit du in die Anwendung kommst.
Besser ist es, den eingeloggten Zustand einmal zu speichern und in weiteren Tests wiederzuverwenden.
Dafür kannst du zum Beispiel eine Setup-Datei erstellen:
import { test as setup, expect } from '@playwright/test';
setup('login', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByText('Dashboard')).toBeVisible();
await page.context().storageState({ path: 'playwright/.auth/user.json' });
});Danach kannst du diesen Zustand in der Konfiguration verwenden:
use: {
storageState: 'playwright/.auth/user.json',
}Dadurch starten deine Tests direkt als eingeloggter Benutzer.
Das spart besonders viel Zeit in Anwendungen, bei denen der Login langsam ist, viele Weiterleitungen enthält, MFA verwendet oder nach dem Login große Datenmengen lädt.
Natürlich solltest du weiterhin separate Tests für den Login selbst haben. Aber es ergibt meistens keinen Sinn, den Login in jedem einzelnen Testszenario erneut zu testen.
4. Entferne künstliche Timeouts
Wenn du im Projekt so etwas siehst:
await page.waitForTimeout(3000);dann hast du wahrscheinlich einen guten Kandidaten für Optimierung gefunden.
Künstliche Timeouts sind bequem, aber sie machen Tests oft unnötig langsam. Wenn du in einem Test 10 solcher Timeouts mit jeweils 3 Sekunden hast, hast du gerade 30 Sekunden Wartezeit eingebaut. Auch dann, wenn die Anwendung eigentlich nach einer halben Sekunde bereit war.
Besser ist es, Locators und Assertions zu verwenden, die automatisch auf den richtigen Zustand warten.
Statt:
await page.click('#save');
await page.waitForTimeout(3000);
await expect(page.locator('.success')).toBeVisible();besser:
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByText('Saved successfully')).toBeVisible();Playwright wartet automatisch darauf, dass Elemente sichtbar, klickbar und bereit für die Aktion sind. Du musst also nicht raten, ob die Anwendung 1, 2 oder 3 Sekunden braucht.
Künstliche Timeouts würde ich nur in Ausnahmefällen verwenden. Zum Beispiel, wenn du wirklich ein Verhalten nach einer bestimmten Zeit prüfen willst.
Aber als Standardlösung, um Tests „stabiler“ zu machen, sind sie meistens keine gute Idee.
5. Starte nicht bei jedem Commit alle Tests auf allen Browsern
Playwright kann Tests in Chromium, Firefox und WebKit ausführen. Das ist eine sehr starke Funktion.
Aber du musst nicht bei jedem kleinen Commit sofort alle Tests auf allen Browsern ausführen.
Wenn du bei jedem Pull Request die komplette Test-Suite auf drei Browsern startest, kann sich die Laufzeit natürlich stark erhöhen.
Ein besserer Ansatz kann so aussehen:
- bei jedem Pull Request laufen schnelle Tests nur in Chromium,
- einmal täglich läuft die komplette Regression auf Chromium, Firefox und WebKit,
- vor einem Release läuft die vollständige Suite auf allen Browsern.
In der Konfiguration kannst du mehrere Projekte definieren:
projects: [
{
name: 'chromium',
use: { ...devices['Desktop Chrome'] },
},
{
name: 'firefox',
use: { ...devices['Desktop Firefox'] },
},
{
name: 'webkit',
use: { ...devices['Desktop Safari'] },
},
]Für eine schnelle Pipeline kannst du aber nur Chromium starten:
npx playwright test --project=chromiumDas ist in der Praxis oft ein sehr sinnvoller Kompromiss. Du hast weiterhin Cross-Browser-Tests, aber du verschwendest nicht bei jeder kleinen Änderung unnötig Zeit.
6. Bereite Testdaten über die API vor, nicht über die UI
Das ist ein weiterer Punkt, der viel Zeit sparen kann.
Angenommen, du möchtest das Bearbeiten einer Bestellung testen. Viele würden so vorgehen:
- einloggen,
- zur Seite für neue Bestellung gehen,
- Formular ausfüllen,
- Bestellung speichern,
- zur Bestellliste gehen,
- Details öffnen,
- erst dann die Bearbeitung testen.
Das funktioniert, ist aber langsam.
Noch schlimmer: Wenn das Erstellen der Bestellung über die UI fehlschlägt, schlägt auch dein Test für die Bearbeitung fehl. Obwohl die Bearbeitungsfunktion vielleicht völlig korrekt funktioniert.
Besser ist es, benötigte Daten direkt über die API vorzubereiten.
Also nicht durch mehrere UI-Seiten klicken, sondern per Request Daten erstellen und direkt mit dem eigentlichen Test starten.
Beispiel:
test('user can edit order', async ({ page, request }) => {
const response = await request.post('/api/orders', {
data: {
productId: 1,
quantity: 2,
customerName: 'Test Customer',
},
});
const order = await response.json();
await page.goto(`/orders/${order.id}`);
await page.getByRole('button', { name: 'Edit' }).click();
await page.getByLabel('Quantity').fill('3');
await page.getByRole('button', { name: 'Save' }).click();
await expect(page.getByText('Order updated')).toBeVisible();
});So konzentriert sich der Test wirklich auf das, was geprüft werden soll: das Bearbeiten der Bestellung.
Du verlierst keine Zeit damit, dich durch die Anwendung zu klicken, nur um Testdaten vorzubereiten.
7. Trenne Smoke Tests von kompletter Regression
Nicht jeder Test muss immer laufen.
Das ist vor allem in größeren Projekten wichtig. Wenn du 300 End-to-End-Tests hast, ist es oft nicht sinnvoll, alle Tests nach jeder kleinen Änderung auszuführen.
Deshalb lohnt es sich, Tests zu gruppieren:
- smoke,
- regression,
- critical path,
- visual,
- API,
- e2e.
Ein Test kann zum Beispiel so markiert werden:
test('user can login @smoke', async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Login' }).click();
await expect(page.getByText('Dashboard')).toBeVisible();
});Danach kannst du nur Smoke Tests starten:
npx playwright test --grep @smokeDie vollständige Regression kann dann separat laufen, zum Beispiel nachts oder vor einem Release.
Das ist besonders in CI/CD sehr praktisch. Entwickler bekommen schnell Feedback, ob die wichtigsten Funktionen noch funktionieren, während die vollständige Regression in einem anderen Schritt laufen kann.
8. Achte auf zu große End-to-End-Tests
Nicht jedes Szenario muss ein riesiger End-to-End-Test sein.
Manchmal sieht man Tests, die wirklich alles machen:
- Login,
- Kunden erstellen,
- Bestellung erstellen,
- Zahlung durchführen,
- Rechnung prüfen,
- Versand auslösen,
- Bestellung stornieren,
- E-Mail prüfen,
- Historie prüfen.
Ein solcher Test kann als kritisches Business-Szenario sinnvoll sein. Aber wenn dein gesamtes Projekt nur aus solchen Tests besteht, wird es langsam, instabil und schwer wartbar.
Besser ist es, weniger lange End-to-End-Tests zu haben und dafür mehr kleinere Tests, die konkrete Funktionen prüfen.
Zum Beispiel:
- ein Test für Login,
- ein Test für das Erstellen einer Bestellung,
- ein Test für das Bearbeiten einer Bestellung,
- ein Test für das Stornieren einer Bestellung,
- ein Test für Benutzerrechte.
Lange Szenarien würde ich nur für wirklich kritische Geschäftsprozesse verwenden.
Kleinere Tests sind schneller, einfacher zu debuggen und meistens stabiler.
9. Begrenze Video, Screenshots und Traces
Playwright kann Videos aufnehmen, Screenshots erstellen und Traces speichern. Das ist extrem hilfreich beim Debugging, besonders in CI.
Aber wenn du alles immer und für jeden Test speicherst, kann das die Ausführung verlangsamen und sehr viele unnötige Dateien erzeugen.
Statt:
use: {
video: 'on',
trace: 'on',
screenshot: 'on',
}ist oft besser:
use: {
video: 'retain-on-failure',
trace: 'on-first-retry',
screenshot: 'only-on-failure',
}Dann werden Diagnoseinformationen hauptsächlich dann gespeichert, wenn sie wirklich gebraucht werden.
Das ist ein guter Kompromiss. Du hast weiterhin genug Material zur Fehleranalyse, belastest aber nicht jeden erfolgreichen Test unnötig.
10. Setze sinnvolle Timeouts
Timeouts sind wichtig. Aber zu hohe Werte können Probleme verstecken.
Wenn jeder Test 60 Sekunden auf ein Element warten darf, dauert ein fehlerhafter Test sehr lange, bis er endlich fehlschlägt.
Eine Beispielkonfiguration könnte so aussehen:
export default defineConfig({
timeout: 30 * 1000,
expect: {
timeout: 5000,
},
use: {
actionTimeout: 10000,
navigationTimeout: 15000,
},
});Es geht nicht darum, Timeouts extrem niedrig zu setzen. Sie sollten einfach realistisch sein.
Wenn deine Anwendung normalerweise innerhalb von 2 Sekunden lädt, ist ein Timeout von 60 Sekunden für jedes Element oft nicht sinnvoll.
Besser ist es, Probleme schneller zu erkennen und dann den Test oder die Anwendung zu verbessern.
11. Übertreibe es nicht mit beforeEach
beforeEach ist sehr praktisch. Aber es kann schnell zu einem Ort werden, an dem viel zu viel passiert.
Zum Beispiel:
test.beforeEach(async ({ page }) => {
await page.goto('/login');
await page.getByLabel('Email').fill('test@example.com');
await page.getByLabel('Password').fill('password123');
await page.getByRole('button', { name: 'Login' }).click();
await page.goto('/dashboard');
await page.getByRole('button', { name: 'Accept cookies' }).click();
});Wenn dieser Code vor jedem einzelnen Test ausgeführt wird, wird das schnell teuer.
Manchmal ist es besser:
storageStatezu verwenden,- Daten über API vorzubereiten,
- direkt auf die benötigte URL zu gehen,
- wiederholte Schritte zu vermeiden,
- gemeinsame Logik in Fixtures auszulagern.
Ein gutes beforeEach sollte nur das vorbereiten, was für den jeweiligen Test wirklich notwendig ist.
12. Nutze Sharding bei großen Test-Suites
Wenn du sehr viele Tests hast, kannst du sie in mehrere Teile aufteilen. Das nennt man Sharding.
Beispiel:
npx playwright test --shard=1/4
npx playwright test --shard=2/4
npx playwright test --shard=3/4
npx playwright test --shard=4/4Dadurch kannst du Tests auf mehrere Maschinen oder mehrere CI-Jobs verteilen.
Das lohnt sich besonders dann, wenn das Projekt größer wird und mehr Worker auf einer Maschine allein nicht mehr ausreichen.
In der Praxis bedeutet das:
Statt einer Pipeline, die 20 Minuten läuft, hast du mehrere parallele Jobs, die deutlich schneller fertig sind.
13. Verwende bessere Locators
Schlechte Locators können Tests langsamer und instabiler machen.
Wenn Tests oft komplizierte CSS-Selektoren oder XPath verwenden, sind sie meistens schwerer zu warten. Außerdem brechen solche Tests schneller, wenn sich das HTML leicht ändert.
Statt:
await page.locator('div:nth-child(3) > button').click();besser:
await page.getByRole('button', { name: 'Save' }).click();Oder:
await page.getByTestId('save-button').click();Gute Locators verbessern nicht nur die Lesbarkeit. Sie reduzieren auch Situationen, in denen Playwright lange warten muss oder versehentlich das falsche Element findet.
Ich nutze meistens:
getByRole,getByLabel,getByText,getByPlaceholder,getByTestId.
Ein Test sollte lesbar sein. Wenn jemand die Testdatei öffnet, sollte er grob verstehen, was passiert, ohne zuerst die komplette HTML-Struktur zu analysieren.
14. Teste nicht alles über die UI
Das klingt vielleicht etwas provokant, aber nicht alles sollte ein UI-Test sein.
End-to-End-Tests sind wertvoll, aber sie sind auch teuer. Sie starten einen Browser, klicken durch die Anwendung, warten auf Rendering, kommunizieren mit dem Backend und hängen oft von vielen Systemen ab.
Deshalb sollte man sich immer fragen:
Muss dieser Fall wirklich über die UI getestet werden?
Manchmal sind andere Testarten besser geeignet:
- Unit Tests,
- Integrationstests,
- API-Tests,
- Komponententests,
- wenige E2E-Tests für kritische Pfade.
Eine gute Teststrategie bedeutet nicht, alles über den Browser zu automatisieren. Es geht darum, Funktionen auf der passenden Ebene zu prüfen.
Wenn du eine Formularvalidierung sauber auf Komponentenebene testen kannst, brauchst du dafür nicht immer einen vollständigen End-to-End-Test.
15. Beispiel für eine schnelle Playwright-Konfiguration
Hier ist eine Beispielkonfiguration, die ein guter Startpunkt sein kann:
import { defineConfig, devices } from '@playwright/test';
export default defineConfig({
testDir: './tests',
fullyParallel: true,
timeout: 30 * 1000,
expect: {
timeout: 5000,
},
retries: process.env.CI ? 1 : 0,
workers: process.env.CI ? 4 : undefined,
reporter: [['html'], ['list']],
use: {
baseURL: 'https://example.com',
actionTimeout: 10000,
navigationTimeout: 15000,
trace: 'on-first-retry',
screenshot: 'only-on-failure',
video: 'retain-on-failure',
},
projects: [
{
name: 'chromium',
use: {
...devices['Desktop Chrome'],
storageState: 'playwright/.auth/user.json',
},
},
],
});Das ist natürlich keine perfekte Konfiguration für jedes Projekt.
Ich würde sie eher als Ausgangspunkt sehen. In einem echten Projekt musst du Worker, Timeouts, Browser, Testdaten und CI-Einstellungen an deine Anwendung anpassen.
Was bringt meistens die größte Beschleunigung?
Wenn ich die wichtigsten Punkte nennen müsste, wären es diese:
- parallele Ausführung der Tests,
- Wiederverwendung einer eingeloggten Session,
- Entfernen von
waitForTimeout, - Vorbereitung von Testdaten über API,
- Trennung von Smoke Tests und kompletter Regression,
- weniger Browser in schnellen Pipelines,
- kleinere und gezieltere End-to-End-Tests.
In vielen Projekten reichen schon die ersten drei Punkte, um einen deutlichen Unterschied zu sehen.
Wenn vorher jeder Test den Benutzer neu eingeloggt hat und zusätzlich mehrere künstliche Timeouts enthielt, kann die Verbesserung wirklich groß sein.
Fazit
Playwright-Tests schneller zu machen bedeutet nicht, eine einzige magische Option in der Konfiguration zu aktivieren.
Es ist eher die Summe aus mehreren guten Entscheidungen:
- Tests unabhängig schreiben,
- Tests parallel ausführen,
- Login nicht in jedem Test wiederholen,
- künstliche Timeouts vermeiden,
- Daten über API vorbereiten,
- nicht nach jeder kleinen Änderung die komplette Regression starten,
- nicht alles über die UI testen.
Meiner Meinung nach ist das einer der wichtigsten Punkte in der Testautomatisierung.
Tests sollen nicht nur funktionieren. Sie sollen auch schnell Feedback geben.
Wenn Tests zu lange laufen, sieht das Team sie irgendwann nicht mehr als Hilfe, sondern als Hindernis.
Gut geschriebene Playwright-Tests sollten schnell, stabil und verständlich sein. Dann helfen sie wirklich dabei, Qualität im Projekt zu sichern.
