Hinweis:
Dieser Blogartikel ist älter als 5 Jahre – die genannten Inhalte sind eventuell überholt.
BackstopJS ist ein Testing Tool, das bei Web-Frontends zum Einsatz kommt. Mit seiner Hilfe können schnell und einfach visuelle Regressionstests zu einer bereits bestehenden Anwendung hinzugefügt werden. Dazu werden als erstes Screenshots aller Seiten erstellt, gegen die vor einem neuen Release geprüft wird. Erkennt Backstop dabei ungewollte Unterschiede, schlagen die entsprechenden Tests fehl. Dadurch lassen sich solche Änderungen schnell erkennen und wiederkehrende Fehler in bereits getesteten Bereichen einer Webanwendung oder Website vermeiden. Gerade beim Refactoring können sich Fehler einschleichen, die so direkt Auswirkungen auf die visuellen Verhaltensweisen haben.
In der aktuellsten Version werden die Browser Safari, Chrome, Edge und Firefox unterstützt.
BackstopJS wurde unter anderem von Garris Shipon gegründet, der als Software-Entwickler bei LinkedIxn arbeitet. Das Projekt steht als OpenSource Projekt unter der MIT License zur Verfügung.
In diesem Blog Post gebe ich einen Überblick, wie erste Tests für BackstopJS geschrieben werden. Darüber hinaus erkläre ich, wie man BackstopJS in Docker ausführt, welche Befehle zur Verfügung stehen und wie visuelle Tests in einer Gitlab-Pipeline ausgeführt werden können.
Backstop-Projekt aufsetzen
Das Aufsetzen eines Projekts ist einfach. Eine globale Installation ist über den Befehl
1 |
$ npm install -g backstopjs |
auszuführen, damit man die Befehle über die Kommandozeile ausführen kann. Alternativ ist eine lokale Einbindung in ein Projekt möglich. Hier ein einfaches Beispiel:
1 |
const backstop = require('backstopjs'); |
Damit kann man über die API in JavaScript noch mehr Möglichkeiten nutzen. Mit dem Befehl
1 |
$ backstop init |
initialisiert man das Projekt. Achtung: Dieser Workflow überschreibt alle existierenden Files! Im Projekt findet man nun eine Datei mit dem Namen backstop.json.
Bevor die Installation von Backstop final ausgeführt werden kann, benötigt man, falls nicht vorhanden, noch Puppeteer, sonst schlägt die Installation gegebenenfalls fehl. Der Befehl
1 |
$ yarn add puppeteer |
sollte in den meisten Fällen reichen.
Workflows
Diese vier Workflows sind wichtig: der zum Erstellen von Referenzbildern, der zum ausführen der Tests und der Workflow, der die Referenzbilder durch die Testbilder ersetzt. Ein weiterer Workflow ist wichtig, um das Projekt aufzusetzen – später braucht man ihn nicht mehr.
Mit dem Befehl
1 |
$ backstop reference |
erstellt man Referenzbilder, deren Links man hinterlegen kann. Mehr dazu unten.
Der Befehl
1 |
$ backstop test |
erstellt Testbilder und vergleicht sie mit den Referenzbildern. Auch hier kann man Links hinterlegen.
Mit dem Befehl
1 |
$ backstop approve |
kann man die Referenzbilder durch die Testbilder ersetzen.
Mit
1 |
$ backstop init |
initialisiert man das Projekt wie bereits beschrieben.
Die Konfigurationsdatei
Die backstop.json ist sehr einfach zu verstehen und besteht wie jede Konfigurationsdatei aus festen Objekten.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
{ "id": "backstop_default", "viewports": [ { "label": "phone", "width": 320, "height": 480 }, { "label": "tablet", "width": 1024, "height": 768 } ], "onBeforeScript": "puppet/onBefore.js", "onReadyScript": "puppet/onReady.js", "scenarios": [ { "label": "BackstopJS Homepage", "cookiePath": "backstop_data/engine_scripts/cookies.json", "url": "https://garris.github.io/BackstopJS/", "referenceUrl": "", "readyEvent": "", "readySelector": "", "delay": 0, "hideSelectors": [], "removeSelectors": [], "hoverSelector": "", "clickSelector": "", "postInteractionWait": 0, "selectors": [], "selectorExpansion": true, "expect": 0, "misMatchThreshold" : 0.1, "requireSameDimensions": true } ], "paths": { "bitmaps_reference": "backstop_data/bitmaps_reference", "bitmaps_test": "backstop_data/bitmaps_test", "engine_scripts": "backstop_data/engine_scripts", "html_report": "backstop_data/html_report", "ci_report": "backstop_data/ci_report" }, "report": ["browser"], "engine": "puppeteer", "engineOptions": { "args": ["--no-sandbox"] }, "asyncCaptureLimit": 5, "asyncCompareLimit": 50, "debug": false, "debugWindow": false } |
Mit den viewports legt man fest, unter welchen Voraussetzungen abhängig von der Größe man die Anwendung testen möchte. Für jede abgelegte Größe wird jeder Test einmal durchlaufen. Zum Start sind zwei Größen gegeben, die modifiziert werden dürfen. Unter den scenarios kann man die Tests ablegen. Hierbei ist die URL wichtig, in der die zu testende Anwendung abgelegt ist. Die Referenz-URL ist optional; ist diese leer, wird die URL als Referenz-URL genutzt. Alles andere wird in den folgenden Kapiteln näher mit Beispielen erklärt.
BackstopJS mit Docker
1 |
$ backstop test --docker |
Mit der Flag --docker delegiert BackstopJS den Befehl an Docker, um ihn dort auszuführen; das funktioniert bei allen Workflows.
Test Workflow
Mit
1 |
$ backstop test |
erzeugt man die Testbilder und startet automatisch die Auswertung. Das folgende Bild zeigt eine Browserauswertung, die sich automatisch öffnet. Eine Einbindung in die CI wird unten erklärt.
Beispiel einer Browserauswertung mit erfolgreichen Tests
Beispiel einer Browserauswertung eines fehlerhaften Tests
Seit der Version 3.7.0 ist eine Filterung möglich, für welche die obere Leiste zuständig ist. Hier kann man sich beispielsweise nur erfolgreiche oder fehlgeschlagene Tests anzeigen lassen. Der Freitextfilter ist für individuell benannte Tests gedacht.
BackstopJS punktet hier mit seiner visuellen Darstellung der unterschiedlichen Pixel. In der Browser-Auswertung werden sie pink markiert.
Eigene Interaktionen
Wählt man die Option, alles über die Backstop-Datei zu testen, kann man ganz einfach Selektoren hinzufügen. Hierbei gibt das Standardszenario schon Selektoren vor, etwa Click oder Hover. So kann man in die Komponente auch hineingreifen, um zusätzliche Tests zu schreiben. Die Selektoren zu definieren ist sehr einfach gehalten: Selektoren nach IDs fangen mit einem Hashtag an und Klassen mit einem Punkt. Einem Button mit der ID id=’myButton’ bekommt den Click-Selektor #myButton.
Optional kann man auch eine Verzögerung einbauen, die der Applikation Zeit gibt, wenn man etwas zur Laufzeit testen möchte. Die misMatchTreshold gibt den Teil in Prozent an, wie viele Pixel maximal verschieden sein dürfen, damit der Test nicht fehlschlägt.
ClickSelector im Beispiel:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
<!DOCTYPE HTML> <html> <body> <h1 id="myHeader">Hello World!</h1> button id="myButton" onClick="displayResult()">Change text</button> <script> function displayResult() { document.getElementById("myHeader").innerHTML = "Have a nice day!"; } </script> </body> </html> |
Beispiel-HTML
HTML-Beispiel im Browser
Nimmt man ein einfaches Beispiel, das einen Button anzeigt, der durch Klicken den Text ändert, kann man über Backstop mit einem ClickSelector den Button auswählen. Das Resultat sollte der Erwartung entsprechen.
1 |
"clickSelector": "#myButton", |
Ergebnis der Auswertung
Einbindung in Gitlab CI
Die größten Vorteile bietet BackstopJS bei der Ausführung automatisierter Tests. Dazu sind ein paar Änderungen notwendig, die jedoch keine weiteren Probleme bereiten:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 |
stages: - test cache: paths: - node_modules before_script: - npm install test: image: name: backstopjs/backstopjs:v3.7.0 entrypoint: [""] stage: test tags: - shared script: - backstop reference - backstop test |
Eine sehr triviale Implementierung einer yml-Datei
Das Image muss implementiert sein, idealerweise mit der aktuellsten Version. Der entrypoint ist wichtig, wenn man einen einfachen Shared Runner nutzt.
Die Skripte erklären sich von selbst. Man sollte jedoch beachten, ob man immer Referenzbilder erstellt.
1 |
"report": ["CI"] |
In der Datei backstop.json muss der report auf CI gesetzt sein. Das Report-Format ist bereits im Projekt hinterlegt.
1 2 3 4 5 6 7 8 9 |
"ci": { "format" : "junit" , "testReportFileName": "myproject-xunit", "testSuiteName" : "backstopJS" }, |
Optional kann man das ci-Objekt hinzufügen. Dieses überschreibt die vorgegebene Konfiguration von Backstop. Hier kann man Pfad, Format und den Namen überschreiben, wie und wo die Ergebnisse der CI-Auswertung hinterlegt werden.
v3.7.0
BackstopJS ist bereits bei Version 3.7.0 angekommen und bietet inzwischen erweiterte Features, wie zum Beispiel das anpassbare User Interface des In-Browser Reporters. Ebenfalls mit der dritten Version von BackstopJS wurde der Diff-Inspector eingeführt, inklusive Scenario-Filter. Seit der dritten Version werden die Tests mit Chrome Headless, Phantom und Slimer gerendert. User können Interaktionen mit Puppeteer, ChromeJS und CasperJS simulieren. BackstopJS ist als Standalone Package App global oder lokal runnable und unterstützt integriertes Docker Rendering, um plattformübergreifendes schlechtes Rendering zu vermeiden.
Die Anbindung zur CI ist in 3.7.0 verbessert und vereinfacht worden, außerdem sind die Workflows einfacher gehalten, es existieren nur noch drei notwendige Befehle, um alles zu steuern.
Zusammenfassung und Fazit
BackstopJS bietet eine einfache Methode für visuelle Regressionstest. Von der Einrichtung bis zur Nutzung und Auswertung wurde die aktuellste Version maßgeblich vereinfacht und verringert dadurch auch den Zeitaufwand. Im praktischen Bereich lässt sich alles vom User selber einstellen und steuern. Für ein kostenlos bereitgestelltes Testframework funktioniert es schnell und zuverlässig.
Vergleichsweise gibt es nur wenige Nachteile: Lästig wird es nur, wenn sich die Tests häufen und man die Konfigurationsdatei manipulieren muss. Hier es sich an, stattdessen die API in JavaScript zu nutzen.
3 Kommentare