Wie ich Kundenseiten ohne QA-Team teste
Das Playwright-Setup einer Ein-Personen-Agentur: die sieben Prüfungen, die sich für eine Kundenseite lohnen, was man weglässt, wie man gegen Produktion testet.
Kemal Esensoy·aktualisiert am November 4, 2026
Eine Kundenseite, die ich gebaut habe, hat ein Kontaktformular, das der einzige Weg ist, dieses Unternehmen zu erreichen. Keine Telefonnummer auf der Seite. Kein Live-Chat. Ein Formular. Wenn es kaputtgeht, wird das Geschäft nicht leiser, es wird still, und niemand merkt es, bis jemand zufällig fragt, warum das ein schwacher Monat war.
Das ist der Fehler, gegen den ich Tests schreibe. Nicht Bugs im Allgemeinen. Dieser eine.
Ich bin eine Ein-Personen-Agentur in Augsburg. Es gibt keine QA-Person. Es gibt kein Staging-Abnahmemeeting. Es gibt mich, einen Laptop und einen Kunden, der seine eigene Website ungefähr nie prüft. Wenn Leute also fragen, wie Playwright-Testing für kleine Agenturen in der Praxis aussieht, lautet die ehrliche Antwort: viel kleiner als du denkst, und genau auf die Dinge gerichtet, die still versagen.
Die Rechnung, mit der du Tests auslässt (und wo sie bricht)
Hier ist die Überlegung, die jeder Solo-Entwickler durchgespielt hat, ich eingeschlossen. Eine Kundenseite hat 40 Seiten. Ich baue sie in drei Wochen. Eine Testsuite zu schreiben kostet vielleicht einen Tag. Der Kunde zahlt einen Festpreis. Dieser Tag geht von meiner Marge ab, und ich habe die ganze Seite vor dem Start ohnehin durchgeklickt. Also: keine Tests.
Diese Rechnung stimmt am Starttag. Sie ist an Tag 200 falsch.
Denn die Seite steht nicht still. Eine Inhaltsänderung, ein Abhängigkeitsupdate, ein Wechsel der Node-Version beim Hoster, ein Formularanbieter, der still seine API ändert, ein CMS-Feld, das jemand umbenennt. Ich deploye über Docker und Coolify hinter Cloudflare, und all das ist mir Monate nach der Übergabe auf einer Seite gelandet. Und nach einer Änderung nach dem Start klicke ich mich nicht durch 40 Seiten. Ich prüfe die eine Seite, die ich angefasst habe.
Der Bruch in der Überlegung ist nicht "Tests fangen Bugs". Es ist, dass die Kosten eines Fehlers damit skalieren, wie lange er unsichtbar bleibt. Ein kaputtes Layout wird binnen eines Tages gemeldet, weil es jemand sieht. Eine kaputte Formularübermittlung wird in sechs Wochen gemeldet, und dann weißt du nicht mehr, welches Deploy es war, und der Kunde hat echte Anfragen verloren. Über die Seite des langsamen Verfalls habe ich in was auf Websites kaputtgeht, die niemand wartet geschrieben, und ein stilles Formular ist der teuerste Punkt auf dieser Liste.
Die Frage lautet also nicht "soll ich testen". Sie lautet "was ist die kleinste Menge an Prüfungen, die aus einem stillen Sechs-Wochen-Ausfall morgen früh ein rotes X in meinem Postfach macht".
Die sieben Prüfungen, die sich für eine Kundenseite lohnen
Sieben. Das ist die ganze Liste, die ich für eine Marketing-Seite oder einen kleinen Shop mit 20 bis 60 Seiten nutze. Jede Seite gibt 200 zurück und rendert etwas. Das Kontaktformular sendet ab, und die Einsendung kommt an. Die kritische Navigation funktioniert. Die 404-Seite verhält sich richtig. Die SEO-Grundlagen sind da. Keine Konsolenfehler auf Schlüsselseiten. Die mobile Ansicht rendert ohne horizontalen Überlauf.
Das ist der gesamte Umfang von Playwright-Testing für kleine Agenturen, so wie ich es praktiziere. Es passt in eine Datei pro Seite und dauert beim ersten Mal einen Nachmittag, danach weniger, sobald du eine Vorlage hast.
Mit großem Abstand am wertvollsten ist der Seitendurchlauf, weil er fast kostenlos ist. Du hast schon eine Sitemap. Lies sie und prüfe, dass jede URL 200 zurückgibt und eine h1 hat.
import { test, expect } from '@playwright/test';
import { readFileSync } from 'fs';
const urls = [...readFileSync('dist/sitemap-0.xml', 'utf8')
.matchAll(/<loc>(.*?)<\/loc>/g)].map(m => new URL(m[1]).pathname);
for (const path of urls) {
test(`Seite rendert: ${path}`, async ({ page }) => {
const response = await page.goto(path);
expect(response?.status()).toBe(200);
await expect(page.locator('h1')).toBeVisible();
await expect(page.locator('main')).not.toBeEmpty();
});
}
Vierzig Seiten, null Wartung, und es fängt die Fehlerklasse, in der eine CMS-Änderung ein Template zerlegt oder eine Route nicht mehr gebaut wird. Weil es den Build-Output liest, wächst die Testliste mit der Seite.
Zweitens: der SEO-Block. Titel und Canonicals sind das, was bei jeder Migration still zurückfällt, und anders als ein kaputter Button fällt es niemandem beim Hinschauen auf.
test('SEO-Grundlagen auf der Startseite', async ({ page }) => {
await page.goto('/');
await expect(page).toHaveTitle(/.{20,60}/);
const head = page.locator('head');
await expect(head.locator('meta[name="description"]'))
.toHaveAttribute('content', /.{50,160}/);
await expect(head.locator('link[rel="canonical"]'))
.toHaveAttribute('href', /^https:\/\//);
await expect(head.locator('meta[property="og:image"]')).toHaveCount(1);
});
Lass das über eine Handvoll repräsentativer Templates laufen, nicht über alle 40 Seiten. Wenn du mitten in einem Plattformwechsel steckst, ist das die billigste Versicherung gegen die Fehlermodi in der SEO-Checkliste für Website-Migrationen.
Drittens: das Formular. Das ist die Prüfung, die die ganze Suite rechtfertigt.
test('Kontaktformular sendet und bestätigt', async ({ page }) => {
await page.goto('/contact');
await page.getByLabel('Name').fill('Playwright Smoke Test');
await page.getByLabel('Email').fill('smoke+test@wunderlandmedia.com');
await page.getByLabel('Message').fill('Automatische Prüfung, bitte ignorieren.');
const submission = page.waitForResponse(r =>
r.url().includes('/api/contact') && r.status() === 200);
await page.getByRole('button', { name: /send/i }).click();
await submission;
await expect(page.getByText(/thanks|received/i)).toBeVisible();
});
Achte darauf, was geprüft wird: Der Netzwerkaufruf war erfolgreich und der Nutzer sieht eine Bestätigung. Nur die Danke-Nachricht zu prüfen ist der Weg zu einem Formular, das ein grünes Häkchen zeigt und die Daten wegwirft. Wenn du eine statische Astro-Seite mit einer Serverless Function hinter dem Formular betreibst, ist das Setup, das ich in ein Kontaktformular auf einer statischen Astro-Seite beschrieben habe, genau die Form, die dieser Test abdeckt.
Was ich in dieser Größe nicht teste
Ich schreibe für eine Kunden-Marketing-Seite keine Komponententests. Nicht einen. Die Komponenten sind überwiegend präsentational, sie ändern sich, wenn sich das Design ändert, und die Tests müssten jedes Mal neu geschrieben werden. Die Kosten sind real, und die Zahl gefangener Fehler ist nahe null.
Ich mache keine visuelle Regression auf jeder Seite. Screenshot-Vergleiche über 40 Seiten heißen, dass jede legitime Textänderung 40 rote Tests produziert, und beim dritten Mal genehmigst du Diffs, ohne hinzuschauen. Das ist schlechter als kein Test, denn jetzt hast du ein Ritual, das falsches Vertrauen produziert. Wenn bei einer Seite das Layout wirklich zählt, mach von der einen einen Snapshot.
Ich teste keine Drittanbieter-Widgets. Das Cookie-Banner, die eingebettete Karte, das Bewertungs-Karussell: Wenn die kaputtgehen, gehen sie im Code des Anbieters kaputt, und mein fehlschlagender Test sagt mir nichts, worauf ich reagieren kann. Playwrights eigene Best Practices sagen dasselbe, nämlich Drittanbieter-APIs zu mocken statt Abhängigkeiten zu testen, die du nicht kontrollierst. Was ich teste, ist, ob die Seite noch korrekt rendert, wenn das Widget nicht lädt, denn das ist die Aufgabe meines Codes.
Und ich jage keinen Abdeckungszahlen hinterher. Coverage ist eine sinnvolle Kennzahl für eine Bibliothek. Für eine Kundenwebsite ist es eine Zahl, die steigt, wenn du Tests schreibst, die niemand gebraucht hat.
Flaky Tests sind der Grund, warum Leute mit E2E aufhören
Alle, die End-to-End-Testing aufgegeben haben, haben es aus demselben Grund aufgegeben: Die Suite fing an, aus Gründen zu scheitern, die nichts mit dem Code zu tun hatten, und sie haben ihr nicht mehr geglaubt. Eine Testsuite, der du nicht glaubst, ist schlechter als keine Suite, denn sie kostet Zeit und produziert nichts.
Vier Gewohnheiten erledigen fast alles davon.
Nimm Rollen- und Label-Locator, kein CSS. page.getByRole('button', { name: /send/i }) überlebt ein Redesign. page.locator('.btn-primary.mt-4') überlebt eine Umsortierung der Tailwind-Klassen nicht. Playwrights Empfehlung ist, nutzersichtbare Attribute gegenüber CSS oder XPath zu bevorzugen, und der praktische Grund ist, dass deine Locator dann nur brechen, wenn sich das nutzersichtbare Verhalten tatsächlich geändert hat, und genau dann sollen sie brechen.
Schreib nie ein festes Timeout. await page.waitForTimeout(2000) ist die größte einzelne Flake-Quelle in jeder Suite, die ich angeschaut habe. Es ist gleichzeitig zu kurz auf einem langsamen CI-Runner und überall sonst zu langsam. Playwright-Locator warten automatisch: Sie prüfen vor dem Handeln, dass das Element sichtbar und aktiv ist, und Web-First-Assertions wie toBeVisible() wiederholen, bis die Bedingung hält. Nimm die und lösch jedes fest verdrahtete Warten.
Prüfe auf Zustand, nicht auf isVisible(). expect(locator).toBeVisible() wiederholt. if (await locator.isVisible()) kehrt sofort zurück und läuft der Seite davon. Fast identisch zu lesen, komplett anders im Verhalten.
Halte Tests isoliert. Jeder Test bekommt seinen eigenen Browserkontext, eigene Cookies und eigenen Speicher. Das ist Playwrights Standard, und Leute brechen ihn, indem sie einen Test schreiben, der davon abhängt, dass ein früherer sich eingeloggt oder einen Datensatz angelegt hat. Dann kaskadiert ein Fehlschlag in sechs, und du debuggst eine Stunde lang den falschen Test.
Wenn ein Test danach immer noch flakt, lösche ich ihn. Eine Prüfung, der ich nicht trauen kann, gehört nicht in eine Suite, deren ganzer Zweck ist, dass ein rotes Ergebnis etwas bedeutet.
In der CI laufen lassen, ohne deine Minuten zu verbrennen
Playwrights dokumentierter GitHub-Actions-Workflow ist kurz genug, dass es keine Ausrede gibt, keinen zu haben:
name: Playwright Tests
on:
push:
branches: [main]
pull_request:
branches: [main]
jobs:
test:
timeout-minutes: 60
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: lts/*
- run: npm ci
- run: npx playwright install --with-deps chromium
- run: npx playwright test
- uses: actions/upload-artifact@v4
if: ${{ !cancelled() }}
with:
name: playwright-report
path: playwright-report/
retention-days: 30
Eine Änderung gegenüber der Doku-Version: install --with-deps chromium statt aller drei Browser. Für eine Kunden-Marketing-Seite sind browserübergreifende Rendering-Unterschiede nicht der Fehlermodus, den du jagst, und einen Browser statt dreier zu installieren kürzt den langsamsten Schritt des Jobs erheblich. Ich habe das nicht präzise gemessen, behandle es also als Größenordnung und nicht als Zahl.
Zum Budget: GitHub enthält bei privaten Repos für kostenlose Konten 2.000 Actions-Minuten im Monat, 3.000 bei Pro und Team, und Standard-Runner sind bei öffentlichen Repos kostenlos. Eine Smoke-Suite dieser Größe ist nicht das, was das aufbraucht. Die volle Suite bei jedem Push in jeden Branch laufen zu lassen schon. Lass sie bei Pull Requests nach main laufen, und noch einmal nach dem Deploy gegen die Live-URL.
Der Lauf nach dem Deploy ist der, der sich wirklich lohnt. Ein grüner PR sagt dir, dass der Code in Ordnung ist. Ein grüner Lauf gegen die deployte Seite sagt dir, dass Build, Umgebungsvariablen, CDN und Hoster alle mitgespielt haben, und das ist eine andere und größere Menge von Dingen, die schiefgehen können.
Nächtlich gegen die Produktion hängt vom Kunden ab. Broschürenseite: nein. Alles mit Checkout oder Buchungsstrecke: ja, denn dieser Ausfall kostet Geld pro Stunde.
Gegen die Produktion testen, ohne deinem Kunden 40 Mails zu schicken
Und jetzt der Teil, über den niemand schreibt und über den die meisten Versuche von Playwright-Testing für kleine Agenturen stolpern. In dem Moment, in dem du nächtlich einen Formulartest gegen die Live-Seite fährst, sendest du ein echtes Kontaktformular ab. Dein Kunde bekommt um 3 Uhr nachts eine Lead-Benachrichtigung. Sein CRM füllt sich mit Müll. Sein Analytics protokolliert eine Sitzung mit Conversion, und seine Conversion-Rate ist jetzt eine Lüge.
Drei Dinge lösen das, und du willst alle drei.
Markier den Traffic auf Browserebene. Setz in storageState ein Flag, das dein Analytics-Snippet liest, und lade kein Analytics, wenn es da ist. Ein Muster, das gut funktioniert, ist ein ga_debug_mode-Wert im localStorage, den du an GA4s eigenen Filter für Entwickler-Traffic hängen oder benutzen kannst, um das Tag ganz kurzzuschließen:
// playwright.config.ts
use: {
storageState: {
cookies: [],
origins: [{
origin: process.env.BASE_URL!,
localStorage: [{ name: 'ga_debug_mode', value: 'true' }],
}],
},
}
Gib dem Backend einen Weg, den Test zu erkennen. Ich schicke einen Header mit einem gemeinsamen Geheimnis, und der Formular-Handler behandelt einen passenden Request als Trockenlauf: alles validieren, denselben Codepfad fahren, dieselbe 200 zurückgeben, die Mail nicht senden und den Datensatz nicht schreiben.
extraHTTPHeaders: { 'x-smoke-test': process.env.SMOKE_SECRET! }
Der Trockenlauf muss durch die echte Validierung gehen, sonst testest du einen Codepfad, der nur in Tests existiert, und das beweist nichts.
Nimm für alles, was doch entwischt, eine Adresse, die dir gehört. smoke+test@deinedomain.de, niemals das Postfach des Kunden, und serverseitig filtern.
Mach das vor dem ersten nächtlichen Lauf, nicht danach. Zu erklären, warum im CRM eines Kunden 200 Einträge namens "Playwright Smoke Test" liegen, kostet mehr Wohlwollen, als die Tests wert sind, und die Absprache gehört in deine Übergabedokumentation, neben alles andere aus der Checkliste zur Projektübergabe.
Astro und Next.js brauchen eine unterschiedliche Zeile
Der Großteil meiner Kundenarbeit ist Astro oder Next.js, und die Suite ist über beide identisch. Das Einzige, was sich ändert, ist, wie der Server startet.
Für Next.js fährt Playwrights webServer-Konfiguration den Produktions-Build:
webServer: {
command: 'npm run build && npm run start',
url: 'http://localhost:3000',
reuseExistingServer: !process.env.CI,
},
Bei einer statischen Astro-Seite gibt es eine schöne Eigenschaft: Der Build-Output sind einfach Dateien. Du kannst einen statischen Server auf dist/ richten und genau das Artefakt testen, das hochgeladen wird, ganz ohne Framework-Laufzeit.
webServer: {
command: 'npm run build && npx serve dist -l 4321',
url: 'http://localhost:4321',
reuseExistingServer: !process.env.CI,
},
Das ist näher an der Produktion als ein Dev-Server, der HMR, unminifizierten Output und anderes Routing-Verhalten hat. Viele der "läuft lokal, kaputt in Produktion"-Fehler, die ich in acht Jahren gejagt habe, waren Dev-Server-Illusionen, aus derselben Familie wie die unsichtbaren Probleme, die dich still Kunden kosten.
Playwright 2026: Die Agenten sind echt, der Nutzen ist gemischt
Playwright 1.63 kam am 4. September 2026. Jüngere Releases haben Test-Locks für Tests ergänzt, die sich eine Ressource teilen, Cross-Frame-Locator, damit du nicht mehr von Hand in iframes greifst, AbortSignal-Unterstützung und virtuelle WebAuthn-Passkey-Authenticator. Die Docker-Images sind auf Node 24 LTS gewechselt, Ubuntu 26.04 wird unterstützt. Solider, unspektakulärer Fortschritt.
Das Aushängeschild sind Test Agents, eingeführt in 1.56 im Oktober 2025. Drei davon: ein Planner, der die Anwendung erkundet und einen Testplan in Markdown schreibt, ein Generator, der diesen Plan in ausführbare Tests verwandelt und die Selektoren live prüft, und ein Healer, der scheiternde Schritte wiederholt und einen Patch vorschlägt. Du installierst sie mit npx playwright init-agents --loop=claude (oder vscode, codex, opencode). Playwright bringt inzwischen auch seinen MCP-Server mit, ausführbar über npx playwright mcp.
Ich nutze Claude Code täglich, ich wollte also, dass das großartig ist. Ehrliches Urteil: Der Generator ist nützlich, und der Healer macht mich nervös.
Der Generator ist gut im langweiligen Teil. Richte ihn auf eine Seite, und er produziert einen ersten Entwurf mit echten rollenbasierten Locatoren, weil er sie gegen das lebende DOM prüft statt aus dem Quellcode zu raten. Das spart die mühsame halbe Stunde Selektorschreiben. Die Doku selbst merkt an, dass generierte Tests Fehler enthalten können, die den Healer brauchen, und das deckt sich mit dem, was ich gesehen habe.
Beim Healer steige ich aus. Ein Werkzeug, das einen scheiternden Test automatisch umschreibt, bis er besteht, ist strukturell ein Werkzeug, das eine echte Regression verschwinden lassen kann. Das ist das genaue Gegenteil von dem, was ich von einer Suite will, deren einzige Aufgabe ist, mir zu sagen, wann etwas kaputtgegangen ist. Ich fahre die Vorschläge des Healers als Vorschläge und lese jeden. Wenn sich ein Locator geändert hat, weil ich einen Button umbenannt habe, super, übernehmen. Wenn er sich geändert hat, weil der Button verschwunden ist, ist das der Fehler, und ein grüner Test ist das denkbar schlechteste Ergebnis.
Also: Nutz den Generator, um den ersten Entwurf schneller zu schreiben. Lass den Healer den Kreis nicht unbeaufsichtigt schließen.
Was ich tatsächlich ausliefere
Sieben Prüfungen. Eine Konfigurationsdatei. Ein GitHub-Actions-Workflow. Ein Tag Arbeit, wenn eine Seite es zum ersten Mal bekommt, danach rund eine Stunde pro Seite, denn es ist Kopieren und Einfügen mit einer anderen baseURL.
Ich sage dir nicht, dass das die meisten Fehler fängt. Tut es nicht. Es fängt eine Kategorie: die Änderung, die still etwas kaputtmacht, das niemand anschaut. Eng, und die einzige Behauptung, die ich ehrlich machen kann. Das meiste, was auf Kundenseiten schiefgeht, findet immer noch ein Mensch, dem etwas auffällt.
Aber allein der Formulartest hat verändert, wie ich mich beim Deployen auf eine Seite fühle, die ich vier Monate nicht angefasst habe, und das ist einen Nachmittag wert.
Wenn du Kundenseiten ohne QA und ohne Netz betreibst und lieber ein zweites Augenpaar auf dem Setup hättest, statt die Lücken allein zu suchen: Dieses Gespräch führe ich gern.
Wenn dir diese Beiträge helfen: Markiere Wunderlandmedia bei Google als bevorzugte Quelle — dann tauchen meine Artikel häufiger in deinen Suchergebnissen, AI Overviews und im AI-Modus auf.
Als bevorzugte Quelle festlegenÜber den Autor
Kemal Esensoy
Kemal Esensoy, Gründer von Wunderlandmedia, begann seine Karriere als freiberuflicher Webentwickler und Designer. Er führte Web-Design-Kurse mit über 3.000 Studenten durch. Heute leitet er eine preisgekrönte Full-Stack-Agentur, die sich auf Webentwicklung, SEO und digitales Marketing spezialisiert hat.