Visual Regression Testing mit Micoo

05.01.2022 Ricky Elfner
Tech micoo javascript frontend foss testing handson tutorial howto

Diese Woche möchten wir uns mal wieder mit einem Tool zum Testen auseinandersetzen. Dabei werden wir uns das Open-Source Projekt Micoo anschauen. Dieses ist eine pixel-basierte Screenshot-Lösung für visuelle Regressionstests. Dabei stellt Micoo eine Webapplikation zur Verfügung, welche anhand von Screenshots zwischen einem Referenzbild und einem für den Regressionstest generiertem Bild abgleicht, und dies zudem in allen erdenkbaren Darstellungsformen anzeigt. Dabei lässt es sich dank der mitgelieferten Docker-Images ein schnelles Setup durchführen und sofort starten.

Visual Regression Testing

Um zu verstehen, was Micoo überhaupt macht, gucken wir uns erst einmal an, was Visual Regression Testing überhaupt bedeutet. Wenn Sie beispielsweise dabei sind, das Design eines Buttons auf der Startseite Ihrer Applikation anzupassen, möchten Sie sicher sein, dass diese Änderung nicht auf sonst irgendeinen anderen Button Auswirkungen hat. Hierfür gibt es das sogenannte Visual Regression Testing, welches sich darauf fokussiert, ob der angezeigte Inhalt dem entspricht, was sich der Entwickler auch wünscht. Die meisten Entwickler schauen vor allem darauf, dass der Code im Backend ausreichend getestet wird, um die meisten Logikfehler zu vermeiden und es nicht zu einem Fehler in der Applikation kommt. Doch man sollte beim Testen auch an die Darstellung denken, mit welcher der Benutzer interagiert.

Nimmt man als Element wieder einen Button, kann es hier zu unerwünschten Darstellungen oder Funktionseinschränkungen kommen, wenn dieser nicht korrekt dargestellt wird. Hier könnte es zum Beispiel passieren, dass der Text eines Buttons über den Button hinausgeht, je nach Grösse des Displays. Solche Dinge möchte man natürlich vermeiden. Dabei würde ein Funktionstest meistens noch als erfolgreich durchlaufen, da der Button sich noch klicken lässt, jedoch ist das nicht das gewünschte Design.

Genau solche Fehler lassen sich oft mit Visuals Regression Tests vermeiden.

Besonders heutzutage muss man als Entwickler beachten, dass die Applikationen, die man zur Verfügung stellt, meist auf den unterschiedlichsten Geräten und Browsern zur Verfügung gestellt werden. Hier zu gehören unterschiedliche Bildschirmgrössen, verschiedene Browser, verschiedene Betriebssysteme und auch unterschiedliche Bedienvarianten (Touch, Tastatur, Maus). Wenn man dies alles aufzählt, merkt man, dass man hier einen grossen Zeitaufwand hat, wenn man dies alles testet.

Puppeteer

Puppeteer ist eine Node Library welche von Google entwickelt wurde und genutzt wird, um mit Headless Chrome zu arbeiten.

Wie Puppeteer aufgebaut wird, lässt sich mit der Pyramide von Puppeteer beschreiben. Diese geht von unten nach oben:

  1. Headless Chrome

    1. Diese Schicht spiegelt den Chrome-Browser wider, mit dem normalerweise der Benutzer interagiert. Doch hierbei ist der Unterschied, dass es keine UI gibt. Google selbst beschreibt es als "Chrome without chrome".
  2. CDP - Chrome DevTools Protocol

    1. CDP wird dazu verwendet, damit mit der Seite innerhalb von Chrome kommuniziert werden kann. Jedoch um es herunterzubrechen, ist es eigentlich eine JSON basierte WebSocket API.
  3. Puppeteer

    1. Wird genutzt, um Headless Chrome und CDP wirklich zu nutzen. Dieses kann ganz einfach mittels npm installiert werden. Dazu wir ebenfalls Chromium installiert, damit Sie ohne grossen Aufwand Chrome in einem Headless Mode starten können.

Die typischen Verwendungszwecke sind:

  • erstellen von Screenshots
  • erstellen von PDF-Dateien
  • Crawling einer Single-Page Application und erstellen von vor-gerendertem Inhalt
  • Automatisierung von Formularen, UI Testing, etc.
  • Test Chrome Extensions
  • Service Worker testen, ob die Applikation auch wirklich offline funktioniert

Links:


How-To

Vorbereitend muss das Repository von GitHub geladen werden:

git clone https://github.com/Mikuu/Micoo.git

Zum Starten wechseln Sie in das env Verzeichnis:

cd Micoo/env

In diesem Verzeichnis befinden sich verschiedene yaml-Files, diese Sie mit Docker starten können.

.
├── README.MD
├── docker-compose.env.yaml
├── docker-compose.local.p.yaml
├── docker-compose.local.yaml
├── docker-compose.p.yaml
├── docker-compose.yaml
├── initializer

Innerhalb dieses Verzeichnisses können Sie Micoo nun mit docker-compose up starten. Sie können jederzeit das offizielle Docker Image von Docker Hub nutzen, solange Sie keine eigenen Änderungen benötigen. Innerhalb des Browsers können Sie die Startseite von Micoo unter http://localhost:8123 aufrufen, sobald Docker alle Container gestartet hat.

Auf der Startseite können Sie nun ein Passwort sehen, welches Sie unbedingt speichern müssen. Sollten Sie diesen Passwort verlieren, müssen die komplett von vorne beginnen. Sobald Sie auf den Start-Button klicken, werden Sie zur Login-Seite weitergeleitet. Dort können Sie sich nun mit dem zuvor gespeicherten Passwort anmelden.

Wenn das Login erfolgreich war, gelangen Sie auf das Dashboard, welches eine Übersicht über all Ihre Projekte präsentiert.

Über das Eingabe-Feld “New Project” innerhalb der ersten Kachel, können Sie ihrem Projekt einen Namen geben und anschliessend mit einem Klick auf das Pluszeichen die Eingabe bestätigen. Dies ist zunächst einmal alles, um ein neues Projekt zu starten.

Im nächsten Schritt benötigen Sie einen Ordner für Ihre Testdaten. Sobald Sie den ersten Ordner erstellt haben, wechseln Sie in diesen und erstellen noch einmal einen, um die eigentlichen Daten zu speichern. Bei Micoo handelt es sich dabei um Screenshots der zu testenden Applikation.

mkdir test_data
cd test_data
mkdir screenshots

Anschliessend können Sie ein Projekt innerhalb des test_data Ordners erstellen und puppeteer installieren. Dies ist notwendig, dass Sie automatisch Ihre Applikation bedienen können und davon anschliessend einen Screenshot erstellen können, mit dem Micoo den Vergleich durchführen kann.

npm init 
npm install puppeteer

Danach benötigen Sie noch ein Script, welches diese Aufgabe übernimmt und Bilder in den screenshot Ordner speichert. In unserem ersten Beispiel wird die Startseite unserer lokalen Applikation aufgerufen.

//ui-test.js

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setViewport({
    width: 1920,
    height: 1080,
  });

  await page.goto('http://localhost:4200/');
  await page.screenshot({ path: 'screenshots/local.png' });

  await browser.close();
})();

Mit dem Befehl node ui-test.js können Sie nun Screenshots erstellen bzw. Ihre Tests automatisch ausführen. Nun können finden Sie innerhalb des Ordners ein Bild mit dem Namen local.png.


Tipp

Wenn Sie sehen möchten, wie der Ablauf innerhalb Puppeteer ist, können Sie anstatt der Headless Chrome Variante auch den Vorgang mit dem Headful-Modus starten. Dadurch öffnet sich Chromium und Sie sehen quasi dasselbe wie Puppeteer. Dazu müssen Sie einfach den launch-Befehl anpassen:

  const browser = await puppeteer.launch({
    headless: false
  });

Dies sollten Sie jedoch nur lokal zu Testzwecken verwenden.


Damit die Tests mit Micoo nun auch funktionieren, müssen Sie die Bilder hochladen und einen Test Build starten. Dies alles soll jedes Mal automatisch passieren, wenn ein Upload stattfand. Hierfür wird in unserem Beispiel die Node Library micoo, mit dem Micoo Client, verwendet. Alternativ können Sie den Upload auch per Python oder Java Client Library machen.

npm install --save-dev micooc

Für den nächsten Schritt benötigen Sie die PID und den API Keys, welche Sie innerhalb der Einstellungen finden können. Diese benötigen Sie, damit Ihre Bilder in Ihr entsprechendes Projekt hochgeladen werden.

Mit den notwendigen Informationen können Sie die folgende Datei erstellen. Dieses Script ist dann dafür zuständig, dass beim Ausführen die Daten hochgeladen werden und ein neuer Build gestartet wird.

const { newBuild } = require("micooc");

async function testNewBuild() {
  const host = "http://localhost:8123/engine";
  const apiKey = "AKc623cf6bd0908092c8";
  const pid = "PID950e0e161df7441593afd23c2521d475";
  const buildVersion = "5fafc0478af24af2da45fa19ddd06c17dd5d0d45";
  const screenshotDirectory = "./screenshots";

  await newBuild(host, apiKey, pid, buildVersion, screenshotDirectory);
}

testNewBuild();

Sobald diese Datei gespeichert wurde, können Sie das JavaScript File ausführen. Sollte der Vorgang erfolgreich sein, bekommen Sie angezeigt, welche Dateien hochgeladen wurden. In unserem Beispiel ist dies nur local.png.

node visual-test.js                                                                                                                                                                                                                                          system  14:26:38
uploaded screenshot: local.png

Wenn Sie nun auf die Build Number 1 klicken, werden Sie zu der Detailansicht dieses Build weitergeleitet. Da es sich dabei um den ersten Build handelt, kann kein Unterschied festgestellt werden, da es kein weiteres Bild gibt, was als Vergleichsbild genutzt werden kann. Aus diesem Grund müssen Sie diesen Test als bestanden ("All Passed") definieren, wenn Sie dieses Bild als Referenz nutzen möchten. Zusätzlich müssen Sie noch den Rebase-Button klicken. Erst durch diese beiden Bedingungen zählt Ihre Build als Vergleichsbasis für die nächsten Tests.

Nun möchten wir den Fall nachstellen, dass es keine Änderung in der Ansicht für den Nutzer gibt. In unserem Fall reicht es hier ohne Code Change, die aktuellen Bilder zu löschen und noch einmal ui-test.js und visual-test.js auszuführen.

rm screenshots/*.png 
node ui-test.js 
node visual-test.js

Sobald Sie wieder die Bestätigung sehen, dass Ihre Bilder hochgeladen wurden, können Sie wieder in Ihre Micoo Projekt wechseln. Dort sollten Sie nun eine zweite Build Number sehen. Das Build Result kann, je nachdem wie viele Bilder verglichen werden, einen Moment dauern.

Wenn Sie nun auf die Build Number 2 klicken und ebenfalls auf das entsprechende Testbild, bekommen Sie ein Vergleich zwischen dem Base-Bild und dem neuen Screenshot.

Nun, im nächsten Schritt wollen wir einfach einen Fehler provozieren, in dem wir die Schriftfarbe unserer Filter-Buttons verändern. Sobald dies getan ist, können wieder die Screenshots gelöscht werden, die Tests und der Upload durchgeführt werden.

In der Projektübersicht bekommen wir dann die Anzeige, dass der Vergleich fehlgeschlagen ist.

Innerhalb des Builds (in der Detailansicht) sieht man welche Vergleiche fehlgeschlagen sind. In unserem Beispiel gibt es nur ein Bild, somit unterscheidet sich hier nur die Detail-Info Difference, welche in Prozent angibt wie stark die Abweichungen sind.

Klickt man nun das Bild bzw. den Testfall an, welcher fehlgeschlagen ist, bekommt man wieder ein Vergleichsbild inklusive eines Bildes, welches die Differenz anzeigt.

Eine weitere nützliche Funktion ist hier Image-Compare. Dabei haben Sie einen Schieberegler mit, dem Sie von links nach rechts gehen können, um die Unterschiede genauer zu vergleichen.

Weitere Testfälle

Im nächsten Schritt möchten wir Pupperteer noch ein wenig mehr nutzen, damit wir nicht nur die Startseite der Applikation testen, sondern verschiedene Testfälle abdecken können. Deshalb soll als Nächstes eine Eingabe in das Suchfeld getätigt werden, damit beispielsweise auch das Highlighting getestet wird. Zuerst schreiben wir eine Funktion, damit wir nicht alles in der Hauptfunktion stehen haben. Innerhalb dieser Methode suchen wir anhand der Id des Inputs-Feld (searchTermInputField) den Eingabebereich und fügen den Parameter searchTerm hinzu. Anschliessend fügen wir sicherheitshalber einen kurzen Timeout hinzu, da die Abfrage immer einen kurzen Moment benötigt und wir so sicher sein können, dass die Abfrage bei jedem Test auch vollständig durchgeführt werden konnte. Und zum Schluss wird wieder ein Screenshot gemacht, der zum Vergleichen genutzt werden soll.

async function  testInput(page, searchTerm){
    await page.type('#searchTermInputField', searchTerm);
    await page.waitForTimeout(2000)
    await page.screenshot({ path: 'screenshots/search_input.png' });
}

Damit dieser Test auch durchgeführt wird, benötigen wir den Aufruf noch in der Hauptfunktion. Hier können Sie auch sehen, dass wir die vorherige Logik für den ersten Test in die Funktion testMainPage ausgelagert haben.

const puppeteer = require('puppeteer');

(async () => {
  const browser = await puppeteer.launch();
  const page = await browser.newPage();
  await page.setViewport({
    width: 1920,
    height: 1080,
  });

  await testMainPage(page);
  await testInput(page, 'Angular');

  await browser.close();
})();

Nun führen wir wieder die Test-Klasse aus und laden es zu Micoo hoch. Doch auch hier löschen wir zuerst wieder alle vorhanden Screenshots.

rm screenshots/*.png 
node ui-test.js 
node visual-test.js

Innerhalb des Projekts auf Micoo sehen Sie nun, dass der vierte Test undetermined ist.

Deshalb müssen Sie zunächst einmal auf den ersten Test und dort ein Debase anklicken, damit Sie eine neue Vergleichsbasis bestimmen können, die als Vergleichsbasis wieder dienen soll.

Anschliessend können Sie wieder zurück in den vierten Test gehen und dort bestimmen, dass alle Tests in Ordnung sind und es mit Rebase als Vergleichsbasis festlegen. Auf der Startseite sollten Sie danach sehen, dass das lilafarbige Icon nun bei dem vierten Test zu sehen ist. Dies zeigt immer die Screenshots an, die zum Vergleich dienen sollen.

Und nun müssen Sie einfach nochmal den Testvorgang ausführen, damit Sie sicher sind, dass dieser funktioniert und auch automatisch erfolgreich durchläuft. Der fünfte Test hat nun zwei Bilder und auch beide sind in unserem Beispiel als bestanden markiert.

Nun haben Sie die einen weiteren Testfall abgedeckt und können nun auch die Ansicht sehen, wie diese aussieht. Des Weiteren wissen Sie nun auch, wie Sie die Vergleichsbasis festlegen und auch abändern.

Fazit

Dank des schnellen Setups lassen sich zu mindestens lokal sehr schnell Tests durchführen. Doch das Ziel ist hier eher alles zu automatisieren und dies nicht nur lokal zur Verfügung zu stellen. Darauf werden Sie auf der Webanwendung von Micoo auch hingewiesen. Dank der Docker Images kann man hier sehr schnell mit dem Vergleichen Ihrer Applikation beginnen. Möchte man Änderungen an diesen Images vornehmen, ist auch dies überhaupt kein Problem, da diese mit ausgeliefert werden und somit genutzt werden können.

Im Endeffekt ist dies ein sehr einfaches aber auch sehr nützliches Tool. Denn, wenn man einmal alle gewünschten Basis-Elemente die verglichen werden sollen eingerichtet hat, kann man immer sicher sein, dass Ihre Änderungen keinen unerwünschten Einfluss auf andere Elemente haben, die Sie sonst nicht so leicht bemerken würden. Was aus unserer Sicht hier noch wünschenswerte wäre, wären E-Mail-Benachrichtigungen out-of-the-box.

Ricky Elfner – Denker, Überlebenskünstler, Gadget-Sammler. Dabei ist er immer auf der Suche nach neuen Innovationen, sowie Tech News, um immer über aktuelle Themen schreiben zu können.