#vuecember2020 #vuejs #webentwicklung

Visual-Regression-Tests mit Storyshots

Storybook.js ist eine wunderbare Umgebung um Komponenten unabhängig vom Projekt zu bauen und zu gestalten. Doch was wäre eine Umgebung, ohne Tests?

Bei sum.cumo, eine Tochter des israelischen Softwarekonzerns Sapiens, benutzen wir das offizielle Storybook Plugin „Storyshots“ als Visual Regression Test. Storyshots ist ein Jest Snapshot Addon, welches automatisch jede einzelne Story testet.

Klingt erstmal gut!

Doch was ist ein Visual Regression Test überhaupt?

Stellen wir uns vor, wir haben im laufenden Projekt einen visuellen Bug bei einem Button. An nur einer Stelle ist etwas verrutscht oder wird nicht richtig angezeigt. Diesen Button benutzen wir aber bereits dutzende Male im Code. Wie können wir nun sicherstellen, dass durch den vermeintlichen Fix an der einen Stelle, nicht etwas an einer vollkommen anderen Stelle kaputt gemacht wird?

Das ist der "Regression"-Gedanke. "Visual" heißt dann einfach, dass wir nicht nur die Funktionalität testen, sondern das Interface, bzw. das Aussehen der Komponente - und das lässt sich am besten anhand unseres gut gepflegten Storybooks umsetzen!

Wie funktioniert das dann genau?

Zunächst brauchen wir von jeder einzelnen Story einen Snapshot. Diese Snapshots sind die Grundlage für jeden Test und werden in unserem Repository hinterlegt.

Der Test hat eine eigenständige Stage in unserer Gitlab CI und läuft bei jedem Push zum Merge Request einmal durch.

Bei Durchlaufen des Tests erstellt Storyshots mit Hilfe von Puppeteer neue Snapshots von allen Stories - basierend auf dem neu gepushten Code - und vergleicht diese mit eben jenen, welche wir im Projekt als Grundlage hinterlegt haben.

Wenn alles gut läuft und die Snapshots übereinstimmen - wird die Pipeline grün.

Was uns aber mehr interessiert sind Fehler, wenn die Snapshots also nicht übereinstimmen. Nach Durchlauf des Tests werden drei Ordner in Gitlab erstellt, welche uns in Form von CI-Artefakten zum Download zu Verfügung stehen:

  • unsere Default-Snapshots
  • die im Test neu erstellten Snapshots
  • die Diff-Snapshots - falls ein Fehler aufgetreten ist

Die Diff-Snapshots zeigen uns anhand einer Art Heatmap pixel-genau was nicht übereinstimmt.

Base.png

Falls das ein Versehen war, können wir nun schauen, wie der Change entstanden ist und gegebenenfalls korrigieren.

Oder aber, wenn der Change bewusst war und wir diese Änderung übernehmen wollen, kann der neu generierte Snapshot aus Ordner 2 im Repository den alten Snapshot einfach ersetzen und beim nächsten Durchlauf der Pipeline als Default-Snapshot genutzt werden.

Was kann Storyshots noch so?

Um Storyshots optimal zu nutzen, braucht es natürlich noch die individuelle Konfiguration. In unserer storyshots.js können wir mit vielen Parametern spielen, um den Test bestmöglich zu nutzen. Einige nützliche Einstellungen wären z. B.:

Supported Devices:

Hier wird angegeben, ob „nur“ die Desktop-Version von den Stories getestet werden soll oder eben zusätzliche Devices:

supportedDevices = new Set(['iPad', 'iPhone X', 'Wide'])

Aber Achtung: Mit jedem zusätzlichen Device erhöht sich dementsprechend die Anzahl der zu generierenden Snapshots, was die Pipeline verlängert.

Failure Threshold:

Storyshots schlägt Alarm, sobald auch nur ein Pixel verrutscht ist. Für das menschliche Auge fast unmöglich nachzuvollziehen und in den meisten Fällen auch zu vernachlässigen. Daher empfiehlt es sich eine Fehlerschwelle einzubauen, damit man nicht unnötig nach Fehlern sucht, die, in den meisten Fällen, keine sind.

const failureThreshold = 0.001 // percent

Wait before Screenshot:

Hiermit wird sichergestellt, dass auch jede Story fertig geladen ist, bevor ein Snapshot generiert wird. Zum Beispiel müssen mögliche Bilder oder auch die Fonts erst von Storybook geladen werden.

const WAIT_BEFORE_SCREENSHOT = 500 // ms

Aber auch hier Achtung: Diese Zeit addiert sich natürlich in der Pipeline!

Visual Regression Tests sind bei uns eine nicht wegzudenkende Art des Testens, da nicht nur auf die Funktionalität der Komponente geachtet wird, sondern auch auf das, was der User später im Interface sieht.