Visuelle Tests von Websites nach Updates und Änderungen

An der Uni Köln wird seit mehreren Jahren auf das Content-Managment-System (CMS) TYPO3 gesetzt. Immer mehr Institute und universitäre Einrichtungen greifen dafür auf die TYPO3-Angebote des RRZK zurück, sodass aktuell über 50.000 Seiten, auf über 960 Domains bereitgestellt werden. CMS und Webapplikationen erfordern regelmäßige Wartung und Bugfixes. Jedoch birgt jede Änderung oder Korrektur die Gefahr, dass Nebenwirkungen auftreten und Fehler vielleicht an einer unerwarteten Stelle im System verursacht werden. Regressionstests haben die Aufgabe dies vorzubeugen und Fehler zu finden. Das Ziel lautet: Das, was vorher funktioniert hat, soll auch nach einem Upgrade funktionieren.

Als im vergangenen Sommer die Upgrades unserer Systeme auf die Long Term Support Version v8 des CMS anstanden, haben wir deshalb nach einer Lösung gesucht. Ziel war es ein Programm zu finden, mit der die Front-Ends der Websites auf mögliche “Macken” nach den Upgrades getestet werden können.

Im Web-Bereich wurden in den letzten Jahren einige Libraries und Tools für visuelle Regressionstest entwickelt. Ein visueller Regressionstest führt Front-End- oder User-Interfacetests durch, indem es die Screenshots des User-Interface erfasst und mit den Originalbildern vergleicht. Wenn ein neuer Screenshot vom Referenzscreenshot abweicht, warnt das visuelle Regressionstool.

Visual Regression Testing mit Puppeteer und Resemble.js

“Puppeteer Logo” erstellt von Google, geteilt gemäß der CC BY 3.0-Lizenz

Seit 2017 stellt das EntwicklerInnen-Team des Chrome-Browsers mit Puppeteer eine Open-Source Node.js Library bereit, mit welcher ein Chrome Browser ohne eine grafische Oberfläche über eine API angesteuert und automatisiert werden kann. Mittels Puppeteer lassen sich z.B. Screenshots von Websites erstellen aber auch Interaktionen mit dem Front-End einer Website simulieren.

Die mit Puppeteer erstellten Screenshots, gilt es zu vergleichen. Hierfür bietet sich die gut gepflegte Library Resemble.js an. Diese ist darauf spezialisiert Bilddateien abzugleichen und Unterschiede auszugeben.

Teil 1: Screenshots erzeugen

Mit folgenden Tutorial will ich zeigen wie man mit Puppeteer Screenshots mehrerer Seiten erstellt. Ziel ist es am Ende einen Ordner mit Screenshots zu haben, welche anschließend als Ausgangsbilder für Tests dienen werden.

In einem weiteren Artikel werde ich darauf eingehen, wie diese Screenshots mit einem späteren Zustand verglichen werden können.

Der Code zu dieser Anleitung ist auch auf GitHub zu finden. Link zum Stand dieses Artikels

Node.js und Puppeteer installieren

Vorraussetzung: Zum Ausführen wird die JavaScript Runtime node samt der mitgelieferten Packetverwaltung npm benötigt.

Installationsanleitung für verschiedene Betriebssysteme gibt es auf nodejs.org. Für die Verwendung in Linux-Distributionen bietet NodeSource Repositories mit kompilierten Binaries an.

Nachdem nun Node.js installiert ist, muss noch ein Ordner erstellt und in das Verzeichnis gewechselt werden:

mkdir puppeteer-resemble-testing
cd puppeteer-resemble-testing

Als nächstes muss ein neues Projekt erstellt werden:

npm init

Für den Zweck dieser Anleitung, kann jede Frage mittels der Return-Taste bestätigt werden. Dabei wird eine neue package.json erstellt.

Nun muss Puppeteer installiert werden:

npm install puppeteer --save

Die Installation kann eine kurze Weile dauern. Denn es wird auch automatisch eine Version von Chromium heruntergeladen und innerhalb des Projekts abgespeichert.

Einzelnen Screenshot erstellen

Puppeteer steht jetzt bereit und kann verwendet werden. Unser erstes Skript erzeugt einen Screenshot der kompletten RRZK-Startseite. Das Ergebnis wird als screenshot.png abgelegt.

Inhalt von app.js:

const puppeteer = require('puppeteer')

const takeScreenshot = async () => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()

  await page.setViewport({ width: 1920, height: 1080 })
  await page.goto('https://rrzk.uni-koeln.de/')
  await page.screenshot({ path: './screenshot.png', fullPage: true })
  await page.close()
  await browser.close()
}

takeScreenshot()

Puppeteer hat eine umfassende API-Dokumentation, alle obigen Parameter sind dort ausführlich beschrieben.

Puppeteer ist eine Promise-basierte Bibliothek, in diesem Fall bedeutet dies, dass die Aufrufe an die Chrome-Instanz asynchron durchgeführt werden. Damit der Code des Skript einfach zu lesen ist, wird async/await verwendet. Unsere takeScreenshot() Pfeilfunktion muss deshalb als async definiert werden.

Das Skript wird ausgeführt mit dem Befehl:

node app.js

Im folgenden werden wir auf dieses Skript aufbauen und nach und nach mehr Features hinzufügen.

Mehrere Screenshots in Stapelverarbeitung

Ziel ist es, weiterhin Screenshots vieler Seiten zu erstellen. Um etwas Ordnung zu waren, erstellen wir deshalb zunächst das Unterverzeichnis “screenshots”:

mkdir screenshots

Danach legen wir in app.js ein Array aus Objekten an. In einem Objekt wird jeweils die URL und der Screenshot-Dateiname hinterlegt.

const websites = [
  { url: 'https://rrzk.uni-koeln.de/', filename: 'homepage' },
  { url: 'https://rrzk.uni-koeln.de/aktuelles.html', filename: 'news' },
  { url: 'https://typo3.uni-koeln.de/typo3-angebote.html', filename: 'typo3-offerings'},
  { url: 'https://typo3.uni-koeln.de/typo3-links-und-downloads.html', filename: 'typo3-links-and-downloads'}
]

Zum Test kann dieses Array nun durchlaufen werden:

for (const website of websites) {
  console.log(website.url)
  console.log(website.filename)
}

Beim erneuten Ausführen des Skripts werden nun die Inhalte des websites-Arrays ausgegeben.

Bisher ist in unserer takeScreenshot() Funktion die URL und der Dateiname hartkodiert. Die Funktion muss jetzt mit url und filename gefüttert werden. Daraus ergeben sich folgende Änderungen:

const takeScreenshot = async (url, filename) => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()

  await page.setViewport({ width: 1920, height: 1080 })
  await page.goto(url)
  await page.screenshot({ path: './screenshots/' + filename + '.png', fullPage: true })
    .then(console.log('Screenshot: ' + filename))
  await page.close()
  await browser.close()
}

Fast geschafft, nun noch beim durchlaufen des Arrays die takeScreenshot()-Funktion aufrufen und die Werte übergeben:

for (const website of websites) {
  // console.log(website.url)
  // console.log(website.filename)
  takeScreenshot(website.url, website.filename)
}

Unsere finale kompakte app.js sieht dann wie folgt aus:

const puppeteer = require('puppeteer')

const websites = [
  { url: 'https://rrzk.uni-koeln.de/', filename: 'homepage' },
  { url: 'https://rrzk.uni-koeln.de/aktuelles.html', filename: 'news' },
  { url: 'https://typo3.uni-koeln.de/typo3-angebote.html', filename: 'typo3-offerings'},
  { url: 'https://typo3.uni-koeln.de/typo3-links-und-downloads.html', filename: 'typo3-links-and-downlods'}
]

const takeScreenshot = async (url, filename) => {
  const browser = await puppeteer.launch()
  const page = await browser.newPage()

  await page.setViewport({ width: 1920, height: 1080 })
  await page.goto(url)
  await page.screenshot({ path: './screenshots/' + filename + '.png', fullPage: true })
    .then(console.log('Screenshot: ' + filename))
  await page.close()
  await browser.close()
}

for (const website of websites) {
  // console.log(website.url)
  // console.log(website.filename)
  takeScreenshot(website.url, website.filename)
}

Nach dem Ausführen liegen nun unter „/screenshots“ vier PNG-Dateien.

Durch den .then Handler wird nach einlösen des Promise screenshot kurz Rückmeldung gegeben. Hiermit zeigt sich auch schön die asynchrone Arbeitsweise von Puppeteer.

In meinem nächsten Artikel werde ich das Skript um einen Vorher-Nachher-Vergleich mit Resemble.js erweitern, sowie das ganze mittels Docker so abpacken, dass man es einfach auf einem Linux-Server ausführen kann.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert.

*