Unser Interesse, ganz offen gesagt: Wir sind Geonode und verkaufen Proxys, und Screenshots über einen Proxy gehören zu den sauberen Anwendungsfällen für unser Produkt – zu überprüfen, wie eine Seite aus einem anderen Land tatsächlich aussieht, ist etwas, was keine API verrät. Der ehrliche Vorbehalt betrifft die Kosten: Ein Browser lädt jedes Bild, jede Schriftart, jedes Skript und jedes Video vorab, sodass die Erstellung von Screenshots die bandbreitenintensivste Aktivität ist, die man bei datenvolumenbegrenztem Internet durchführen kann. Weiter unten gibt es einen Abschnitt dazu, wie man das reduzieren kann, und die darin beschriebene Technik wird Ihnen mehr Geld sparen, als die Wahl eines günstigeren Anbieters es tun würde.
Die drei Arten von Screenshots
Viewport – das, was gerade sichtbar ist, die Standardeinstellung:
await page.screenshot({ path: 'viewport.png' });
Ganze Seite – in der Dokumentation wird dies als „ein Screenshot einer vollständigen, scrollbaren Seite, so als hättest du einen sehr hohen Bildschirm und die Seite würde vollständig darauf passen“ beschrieben:
await page.screenshot({ path: 'full.png', fullPage: true });
Element – „Manchmal ist es nützlich, einen Screenshot eines einzelnen Elements zu erstellen“:
await page.getByRole('article').screenshot({ path: 'element.png' });
Die Wahl zwischen diesen Optionen hängt hauptsächlich davon ab, was Sie mit dem Ergebnis vorhaben. Viewport-Screenshots beantworten die Frage „Was sieht der Nutzer als Erstes?“. Ganzseitige Screenshots beantworten die Frage „Was befindet sich auf dieser Seite?“. Element-Screenshots beantworten die Frage „Sieht diese Komponente richtig aus?“, und sie sind die stabilste der drei Varianten für Vergleiche, da sie alles ausschließen, wonach Sie nicht gefragt haben.
Ganzseitige Screenshots und wo sie versagen
fullPage: true ist die Option, auf die viele zurückgreifen, die aber auch die meisten Einschränkungen mit sich bringt.
Lazy-geladene Inhalte sind möglicherweise nicht vorhanden. Playwright scrollt, um den Screenshot aufzunehmen, aber Bilder und Komponenten, die beim Erreichen des Bildbereichs geladen werden, sind möglicherweise noch nicht vollständig geladen, wenn die Aufnahme abgeschlossen ist. Die zuverlässige Lösung besteht darin, bewusst zu scrollen und auf die erwarteten Inhalte zu warten:
await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight));
await expect(page.getByRole('img').last()).toBeVisible();
await page.evaluate(() => window.scrollTo(0, 0));
await page.screenshot({ path: 'full.png', fullPage: true });
Sticky-Header wiederholen sich oder werden seltsam angezeigt. Elemente mit „position: fixed“ oder „sticky“ verhalten sich in einer zusammengesetzten Aufnahme unvorhersehbar. Die Option „style“ – eine „CSS-Zeichenkette, die zur Formatierung während der Aufnahme in die Seite eingefügt wird“ – ist die saubere Lösung:
await page.screenshot({
path: 'full.png',
fullPage: true,
style: '.sticky-header { position: absolute !important; }',
});
Sehr lange Seiten erzeugen sehr große Dateien. Ein Feed mit unendlichem Bildlauf hat kein natürliches Ende. Verwenden Sie stattdessen „clip“, um einen definierten Bereich zu erfassen; dabei wird „ein Objekt verwendet, das den Ausschnitt des resultierenden Bildes festlegt“.
Overlays werden ebenfalls erfasst. Cookie-Banner, Chat-Widgets und Modalfenster erscheinen im Screenshot genau so, wie sie für den Nutzer sichtbar sind. Wenn Sie diese nicht möchten, schließen Sie sie zunächst – und falls dies nicht möglich ist, ist „mask“ das richtige Werkzeug.
Element-Screenshots und Puffer
Element-Screenshots scrollen das Element in den sichtbaren Bereich und erfassen nur dessen Begrenzungsrahmen, was sie zur richtigen Standardeinstellung für Überprüfungen auf Komponentenebene macht.
const card = page.getByTestId('product-card').first();
await card.screenshot({ path: 'card.png' });
Zwei Dinge tun sie nicht: Sie erfassen keine Inhalte, die durch overflow: hidden
abgeschnitten werden, und sie erfassen nichts außerhalb des Begrenzungsrahmens des Elements, selbst wenn es sich visuell überschneidet.
Puffer statt Dateien. In der Dokumentation heißt es: „Anstatt in eine Datei zu schreiben, können Sie einen Puffer mit dem Bild abrufen und diesen nachbearbeiten oder an eine Pixelvergleichsfunktion eines Drittanbieters weiterleiten.“ Lassen Sie „path
“ weg, und Sie erhalten die Bytes zurück:
const buffer = await page.screenshot();
const base64 = buffer.toString('base64');
Diese Form ist immer dann sinnvoll, wenn der Screenshot an einen anderen Ort als die lokale Festplatte gesendet wird – beispielsweise in einen Objektspeicher, an eine API, in einen Bericht oder an einen Diff-Dienst. Außerdem wird das Dateisystem vollständig umgangen, was bei containerisierten Runners wichtig ist, bei denen die Festplatte nur vorübergehend verfügbar ist.
Die Optionen, die Screenshots reproduzierbar machen
Wenn Sie Screenshots miteinander vergleichen, sind diese Optionen unverzichtbar. Wenn Sie einen Screenshot nur flüchtig betrachten, können Sie sie ignorieren.
| Option | Werte | Funktion |
|---|---|---|
animations |
| disabled
, allow
| „Wenn auf ‚disabled‘ gesetzt, werden CSS-Animationen während der Aufnahme angehalten“ |
| caret
| hide
, initial
| „Wenn auf ‚hide‘ gesetzt, wird das Textcursor-Symbol während des Screenshots ausgeblendet“ |
| mask
| Locator[]
| „Gibt die Elemente an, die beim Erstellen des Screenshots maskiert werden sollen“ |
| maskColor
| CSS-Farbe, Standardwert #F0F
| „Legt die Farbe fest, die für maskierte Bereiche verwendet werden soll“ |
| scale
| css
, device
| „Der Maßstab der Webseiten-Darstellung“ |
| omitBackground
| Boolescher Wert, Standardwert false
| „Blendet den standardmäßigen weißen Hintergrund aus und ermöglicht die Erstellung transparenter Screenshots“ |
| type
| png
, jpeg
, Standard png
| „Legt das Dateiformat des Screenshots fest“ |
| quality
| 0–100 | „Die Bildqualität für das JPEG-Format“ – nur JPEG |
| style
| CSS-Zeichenkette | Wird für die Dauer der Aufnahme in die Seite eingefügt |
Die vier Einstellungen, die die meisten Reproduzierbarkeitsprobleme lösen:
**animations: 'disabled'
** beseitigt die größte Einzelursache für Abweichungen zwischen zwei Aufnahmen derselben Seite. Jeder während der Aufnahme stattfindende CSS-Übergang führt bei jedem Durchlauf zu einem anderen Pixelergebnis.
**mask
** ersetzt Bereiche durch eine einheitliche Farbe. Auf diese Weise schließen Sie tatsächlich variierende Inhalte – Zeitstempel, Sitzungskennungen, personalisierte Empfehlungen, Werbung – aus, ohne den Vergleich gänzlich aufzugeben:
await page.screenshot({
path: 'page.png',
mask: [page.getByTestId('timestamp'), page.locator('.ad-slot')],
maskColor: '#000000',
});
**scale: 'css'
** erfasst die Abbilder in CSS-Pixelabmessungen statt im Geräte-Pixelverhältnis, sodass ein Gerät mit hoher DPI und ein CI-Runner Bilder vergleichbarer Größe erzeugen. Legen Sie diese Einstellung explizit fest, anstatt sich auf den Standardwert zu verlassen, da dies die Option ist, bei der die größten Unterschiede zwischen Ihrem Laptop und dem Build-Server auftreten können.
**caret: 'hide'
** entfernt den blinkenden Textcursor, der sonst in etwa der Hälfte Ihrer Screenshots von Seiten mit einem fokussierten Eingabefeld erscheint.
Drei weitere Punkte, die Sie für die Reproduzierbarkeit festlegen sollten – wobei es sich bei keinem davon um Screenshot-Optionen handelt: Legen Sie die Viewport-Größe in Ihrer Konfiguration fest, legen Sie die Ländereinstellung und die Zeitzone fest und legen Sie die Schriftarten fest – die Verfügbarkeit von Schriftarten unterscheidet sich zwischen einem Entwicklerrechner und einem Container, und unterschiedliche Schriftarten bedeuten ein unterschiedliches Layout.
Automatische Screenshots bei Testfehlern
Die wertvollste Screenshot-Konfiguration in Playwright, für die nur eine Zeile erforderlich ist:
export default defineConfig({
use: {
screenshot: 'only-on-failure',
trace: 'retain-on-failure',
video: 'retain-on-failure',
},
});
screenshot: 'only-on-failure'
erfasst die Seite in dem Moment, in dem ein Test fehlschlägt, und fügt sie dem Bericht bei. Optionen sind off
, on
und only-on-failure
; on
erstellt bei jedem Test einen Screenshot und erzeugt dadurch eine große Anzahl von Artefakten.
Die Kombination mit „trace
“ ist besonders wichtig, da ein Trace DOM-Snapshots, Netzwerkaktivitäten und jede Aktion mit Zeitangaben enthält. Ein Screenshot zeigt Ihnen, dass die Seite falsch aussah; ein Trace erklärt Ihnen, warum. Bei allen unbeaufsichtigt ablaufenden Prozessen sollten beide Optionen aktiviert sein.
Diese Konfiguration ist zudem der schnellste Weg, um die verwirrende Fehlerkategorie zu diagnostizieren, bei der eine Seite zwar gerendert wurde – nur eben nicht die Seite, die man erwartet hat. Eine Challenge-Seite, eine Weiterleitung nach der Anmeldung oder eine regionale Variante führen alle zu Timeouts, die wie Elementprobleme aussehen, bis man den Screenshot sieht.
Visueller Vergleich mit „toHaveScreenshot
“ Für echte visuelle Regressionstests statt Ad-hoc-Screenshots:
await expect(page).toHaveScreenshot('homepage.png');
await expect(page.getByRole('navigation')).toHaveScreenshot('nav.png');
Beim ersten Durchlauf wird eine Basislinie erstellt; bei nachfolgenden Durchläufen wird ein Vergleich durchgeführt und bei Abweichungen ein Fehler gemeldet. Aktualisieren Sie die Basislinien gezielt mit --update-snapshots
.
Drei praktische Hinweise.
Basislinien sind plattformspezifisch. Die Darstellung von Schriftarten unterscheidet sich je nach Betriebssystem, sodass eine unter macOS erstellte Basislinie nicht mit einer übereinstimmt, die in einem Linux-Container erstellt wurde. Erstellen Sie Basislinien in derselben Umgebung, in der Ihre Tests ausgeführt werden – in der Regel in der CI, meist über einen Container, den Sie auch lokal ausführen können.
Legen Sie eine Toleranz fest. Eine exakte Pixelübereinstimmung führt zu Fehlern aufgrund von Antialiasing-Unterschieden, die kein Mensch bemerken würde. „maxDiffPixels
“ oder „maxDiffPixelRatio
“ in Ihrer Konfiguration machen die Testsuite erst nutzbar.
Maskieren Sie alle variablen Elemente, bevor Sie beginnen. Ein visueller Test, der bei jeder Änderung des Zeitstempels fehlschlägt, wird innerhalb einer Woche deaktiviert – was schlimmer ist, als gar keinen zu haben.
Screenshots über einen Proxy
Das ist ein Fall, bei dem wir uns wirklich auskennen – und zwar ein guter.
Konfigurieren Sie den Proxy in Ihrem Playwright-Setup:
const context = await browser.newContext({
proxy: { server: 'http://proxy.example.com:9000', username: 'u', password: 'p' },
locale: 'de-DE',
timezoneId: 'Europe/Berlin',
});
Beachten Sie die Adressen locale
und timezoneId
neben dem Proxy. Eine deutsche Ausgangsadresse mit der Länderkennung „en-US
“ und der Zeitzone London ist eine Kombination, die kein echter Besucher verwendet, und viele Websites nutzen die Länderkennung unabhängig von der Adresse, um zu entscheiden, welche Inhalte angezeigt werden. Legen Sie alle drei gemeinsam fest, sonst testen Sie etwas anderes als beabsichtigt.
Was dies tatsächlich beantwortet: Was ein echter Besucher in diesem Land sieht. Regionale Preisgestaltung, Währungsanzeige, Verfügbarkeit, Werbebanner, ob Ihre Werbung dort geschaltet wird, wofür Sie bezahlt haben, und was neben Ihren Inhalten erscheint. Keine API liefert Ihnen diese Informationen, da die Antwort eine gerenderte Seite ist.
Was es kostet und wie man die Kosten senken kann. Ein Browser ruft alles ab. Bei privatem Datenverkehr mit Datenvolumenbegrenzung – bei uns beginnt dieser bei 0,79 $/GB, Stand September 2026 gemäß unserer Preisseite – summieren sich die Kosten für einen Screenshot-Lauf über zwanzig Märkte schnell. Das Blockieren von Ressourcentypen, die Sie nicht benötigen, ist der wichtigste Hebel:
await page.route('**/*.{woff,woff2,mp4,webm}', route => route.abort());
Achten Sie darauf, was nicht in dieser Liste steht. Wenn der Screenshot das Endergebnis ist, können Sie keine Bilder blockieren – das würde den Sinn verfehlen. Blockieren Sie Schriftarten und Medien, behalten Sie Bilder bei und akzeptieren Sie, dass die visuelle Überprüfung von Natur aus die teure Art der Proxy-Arbeit ist. Wenn Sie lediglich den Textinhalt und nicht das Erscheinungsbild bestätigen müssen, blockieren Sie auch Bilder und verzichten Sie ganz auf den Screenshot.
Und überprüfen Sie, ob die Geolokalisierung tatsächlich funktioniert hat. Machen Sie den Screenshot und sehen Sie ihn sich an. Wenn eine Seite, die über einen brasilianischen Exit-Server erfasst wurde, dieselben Preise anzeigt wie Ihr Arbeitsplatz, funktioniert das Targeting nicht, unabhängig davon, was eine IP-Abfrage meldet. Das ist genau der „stille Fehler“, den wir in Warum das Testen von Proxys wichtig ist beschrieben haben – und Screenshots sind ungewöhnlich gut darin, ihn aufzudecken, da ein Mensch ihn auf einen Blick erkennen kann.
Screenshots in großem Umfang
Sobald Sie mehr als nur eine Handvoll Screenshots erstellen, gibt es einige Vorgehensweisen, die verhindern, dass die Aufgabe unüberschaubar wird.
Verwenden Sie den Browser wieder, nicht den Kontext. Das Starten eines Browsers ist ressourcenintensiv; das Erstellen eines Kontexts ist ressourcenschonend. Für einen Durchlauf über viele Seiten oder viele Regionen: Starten Sie den Browser einmal und erstellen Sie pro Arbeitseinheit einen neuen Kontext – so erhalten Sie isolierte Cookies und Speicherplatz, ohne die Startkosten wiederholt zahlen zu müssen:
const browser = await chromium.launch();
for (const country of countries) {
const ctx = await browser.newContext({ proxy: { server: proxyFor(country) } });
const page = await ctx.newPage();
await page.goto(url, { waitUntil: 'domcontentloaded' });
await page.screenshot({ path: `shots/${country}.png`, fullPage: true });
await ctx.close();
}
await browser.close();
Verwenden Sie „networkidle“ nicht als Wartebedingung. Seiten mit Analytics-Beacons, WebSockets oder Polling werden nie inaktiv, und die Wartezeit läuft ab. Warten Sie auf das Element, das Ihnen anzeigt, dass die Seite bereit ist:
await page.goto(url);
await expect(page.getByRole('heading', { level: 1 })).toBeVisible();
await page.screenshot({ path: 'shot.png' });
Begrenzen Sie die Parallelität bewusst. Jeder Browserkontext verbraucht echten Arbeitsspeicher – einige hundert Megabyte sind normal, sobald eine Seite geladen ist. Wenn Sie dreißig Instanzen parallel auf einem kleinen Runner ausführen, kommt es zu Fehlern, die wie Timeouts aussehen, aber tatsächlich darauf zurückzuführen sind, dass dem Rechner der Speicherplatz ausgeht. Beginnen Sie mit vier oder fünf und erhöhen Sie die Anzahl, während Sie den Speicherverbrauch im Auge behalten.
Benennen Sie Dateien so, dass Sie sie wiederfinden können. Ein Verzeichnis mit Namen wie „screenshot-1.png“ bis „screenshot-400.png“ ist unbrauchbar. Nehmen Sie das Ziel, die Region und einen Zeitstempel in den Dateinamen auf und speichern Sie die URL zusammen mit dem Bild.
Komprimieren Sie die Dateien vor der Archivierung. PNG ist verlustfrei, aber speicherintensiv. Wenn die Bilder eher zur menschlichen Überprüfung als zum Pixelvergleich dienen, ist JPEG (quality: 80) in der Regel nur einen Bruchteil so groß und optisch nicht zu unterscheiden – und bei einem planmäßigen Durchlauf über zwanzig Märkte macht dieser Unterschied die Speicherkosten aus.
Behandeln Sie Fehler, ohne den Durchlauf zu unterbrechen. Eine Seite, die sich nicht laden lässt, sollte nicht dazu führen, dass die anderen neunzehn aufgegeben werden. Schließen Sie jede Erfassung ab, protokollieren Sie den Fehler und fahren Sie fort – berichten Sie dann, welche Ziele fehlgeschlagen sind, anstatt erst festzustellen, dass der gesamte Auftrag bereits bei Ziel drei abgebrochen ist.
Wann ein Screenshot das falsche Werkzeug ist
Wenn Sie die Daten benötigen. Wenn Sie den Preis benötigen, extrahieren Sie den Preis. Ein Screenshot einer Zahl ist eine Zahl, die Sie anschließend aus einem Bild ablesen müssen. Screenshots dienen der Darstellung; Selektoren dienen dem Inhalt.
Wenn Sie wissen möchten, warum ein Test fehlgeschlagen ist. Ein Trace ist deutlich aussagekräftiger und enthält ohnehin den Screenshot.
Wenn die Seite riesig ist. Ganzseitige Aufnahmen von Seiten mit unendlichem Bildlauf erzeugen riesige Dateien, die niemand öffnen wird. Beschränken Sie sich auf den relevanten Bereich.
Wenn Sie Text überprüfen. Führen Sie eine Prüfung des Textes durch. „expect(locator).toHaveText()“ liefert eine lesbare Fehlermeldung; ein Pixel-Diff liefert Ihnen lediglich ein Bild davon.
Wenn Sie eine Archivierung in großem Umfang benötigen. Screenshots sind groß, und Tausende davon über viele Märkte hinweg belasten sowohl den Speicherplatz als auch die Bandbreite. Speichern Sie Hashes oder Diffs und behalten Sie vollständige Bilder nur dort, wo sich etwas geändert hat.
Häufig gestellte Fragen
Wie mache ich einen Screenshot in Playwright?
await page.screenshot({ path: 'shot.png' }) für den Viewport, { fullPage: true } für die gesamte scrollbare Seite und locator.screenshot() für ein einzelnes Element. Lassen Sie path weg, um einen Puffer zu erhalten, anstatt eine Datei zu schreiben.
Wie erstelle ich einen ganzseitigen Screenshot?
Übergeben Sie fullPage: true. Beachten Sie, dass verzögert geladene Inhalte möglicherweise noch nicht angekommen sind und dass „sticky“ oder feststehende Elemente im zusammengesetzten Ergebnis seltsam wirken können – scrollen Sie daher zunächst bewusst und verwenden Sie die Option „style“, um die „sticky“-Positionierung während der Aufnahme zu neutralisieren.
Wie erstelle ich einen Screenshot eines einzelnen Elements?
Rufen Sie „screenshot()“ für einen Locator statt für die Seite auf: await page.getByTestId('card').screenshot({ path: 'card.png' }). Playwright scrollt das Element in den sichtbaren Bereich und erfasst dessen Begrenzungsrahmen. Inhalte, die durch „overflow: hidden“ abgeschnitten werden, werden nicht berücksichtigt.
Wie stelle ich sicher, dass Playwright-Screenshots über mehrere Durchläufe hinweg konsistent sind?
Setzen Sie „animations: 'disabled'“ und „caret: 'hide'“, maskieren Sie variable Bereiche mit „mask“ und legen Sie „scale“ explizit fest. Legen Sie anschließend die Größe des Ansichtsbereichs, die Ländereinstellung, die Zeitzone und die verfügbaren Schriftarten fest, da alle vier Faktoren das Layout beeinflussen und keine davon zu den Screenshot-Optionen gehört.
Wie kann ich automatisch einen Screenshot erstellen, wenn ein Test fehlschlägt?
Setzen Sie „screenshot: 'only-on-failure'“ im Block „use“ Ihrer Playwright-Konfiguration. Kombinieren Sie dies mit „trace: 'retain-on-failure'“ – ein Trace enthält DOM-Snapshots, Netzwerkaktivitäten und Aktionszeiten, wodurch der Fehler erklärt wird, anstatt ihn nur anzuzeigen.
Kann ich einen Screenshot als Base64 statt als Datei erhalten?
Ja. Lassen Sie die Option „path“ weg, dann gibt „screenshot()“ einen Puffer zurück, den Sie mit „buffer.toString('base64')“ konvertieren können. Die Dokumentation empfiehlt dies für die Nachbearbeitung oder die Weitergabe an einen Pixel-Diff-Dienst; außerdem wird dadurch das Dateisystem in kurzlebigen CI-Runners umgangen.
Wie blende ich dynamische Inhalte in einem Screenshot aus?
Verwenden Sie die Option „mask“ mit einem Array von Locators, wodurch diese Bereiche durch eine einheitliche Farbe ersetzt werden – „maskColor“ ist standardmäßig auf „#F0F“ gesetzt und kann geändert werden. Auf diese Weise bleibt der visuelle Vergleich auch auf Seiten mit Zeitstempeln, Sitzungsdaten oder Werbung aussagekräftig.
Kann ich Screenshots über einen Proxy erstellen, um regionale Seiten anzuzeigen?
Ja, und das ist eine der besseren Anwendungsmöglichkeiten dafür. Richten Sie den Proxy im Browserkontext ein und passen Sie locale und timezoneId an das jeweilige Land an – viele Websites verwenden die Ländereinstellung unabhängig von der Adresse. Sehen Sie sich dann das resultierende Bild an, um zu bestätigen, dass sich die regionalen Inhalte tatsächlich unterscheiden, anstatt sich auf eine IP-Abfrage zu verlassen.
Fazit
Ein Screenshot in Playwright ist mit einer einzigen Zeile erledigt. Einen Screenshot zu erstellen, der aussagekräftig ist, erfordert etwas mehr Aufwand.
Wenn der Screenshot dazu dient, dass ein Mensch ihn sich einmal ansieht – etwa bei einem Fehlerbild, einem Fehlerbericht oder um zu überprüfen, wie eine Seite von Brasilien aus aussieht –, sind die Standardeinstellungen ausreichend, und screenshot: 'only-on-failure' in Ihrer Konfiguration ist die wertvollste Zeile, die Sie hinzufügen können. Kombinieren Sie dies mit einem Trace, denn ein Trace erklärt, was ein Screenshot nur zeigt.
Wenn der Screenshot mit einem anderen Screenshot verglichen werden soll, ändert sich alles. Deaktivieren Sie Animationen, blenden Sie den Cursor aus, maskieren Sie die variablen Bereiche, fixieren Sie den Maßstab und legen Sie Viewport, Ländereinstellung, Zeitzone und Schriftarten fest. Erstellen Sie dann Baseline-Daten in derselben Umgebung, in der die Tests laufen, da sich die Schriftdarstellung plattformübergreifend unterscheidet und eine Baseline von Ihrem Laptop niemals mit einem Container übereinstimmen wird.
Und für die geografische Überprüfung – bei der eine gerenderte Seite strukturierten Daten tatsächlich überlegen ist – stellen Sie den Proxy, die Ländereinstellung und die Zeitzone gemeinsam ein und überprüfen Sie dann anhand des Bildes, ob die Zielausrichtung funktioniert hat. Bandbreite ist der Kostenfaktor, Bilder sind die einzige Ressourcenart, die Sie nicht blockieren können, und genau das sind die Kosten der visuellen Überprüfung.
