18
.
7
.
2017

Angular 4 dockerized Unit-Tests mit Karma

Google Chrome 59 die widerspenstige Diva

EINLEITUNG

In so ziemlich jedem Angular Projekt - und mit Angular meine ich Version 4+ - verwende ich Karma für Unit Tests. Das tun viele Leute und sie setzen meist im CI-Server PhantomJS als Browser Ersatz ein. Aber warum nicht für meine dockerized NodeJS Builds auch Dockerized Google Chrome verwenden?

Chrome can be very demanding ...
‍Chrome can be very demanding ...

Wir legen uns dazu mit dem Angular CLI ein Testprojekt an und führen die Karma tests mit Chrome lokal auf unserer Workstation aus.

Zuerst installieren wir die Angular CLI global:

shell:npm install -g @angular/cli

Anschließend erzeugen wir unser Testprojekt mit dem Namen ‘my-angular-app’:

shell:ng new my-angular-app

Nun wechseln wir in das erzeugte Projektverzeichnis:

shell:cd my-angular-app

Wir installieren die Dependencies und führen die Karma Tests aus:

shell:npm install && npm run test

Das sollte dann in etwa so aussehen:

Karma Tests einer Angular App
‍Karma Tests einer Angular App

 

DOCKERIZED KARMA TESTS MIT CHROME

Was lokal so schön funktioniert möchte man doch auch im Jenkins oder in sonstiger CI Umgebung haben.

Damit der Wow-Effekt groß ist, hier schonmal der finale Befehl, der Chrome 59 nutzt und dockerized die Karma Tests ausführt.

shell:docker run -i -t -v $(pwd):/work --shm-size=128M codeclou/docker-nodejs-chrome-xvfb:node-8.1.3-chome-59 npm run test:singlerun
Dockerized Karma Tests einer Angular App
‍Dockerized Karma Tests einer Angular App

 

KARMA ANPASSEN

Damit der oben gezeigte Befehl funktioniert, müssen wir die Karma Config anpassen und dem Chrome, der von Karma gestartet wird, mitteilen, dass er ohne GPU und mit deaktivierter Sandbox Funktion starten soll. Die Sandbox Funktion setzt auf Namespace Kernelfeatures, die uns dockerized Probleme machen, daher schalten wir sie ab. Die GPU also der Grafikprozessor ist dockerized auch nicht sinnvoll, daher wird die Nutzung durch Chrome deaktiviert.

Das Ganze muss in der karma.conf.js wie folgt eingerichtet werden.

github:9c791e12aea58a1fc908ad85057a19dd

Da der normale ng test Befehl sich nicht beendet, sondern im Watch-Mode läuft, legen wir uns noch einen weiteren Befehl in der package.json an, damit wir die Karma Tests im single-run Modus starten können.

github:7e711d072120a3f2cb72f5ef88df53bd

 

DAS RICHTIGE DOCKER IMAGE

Auch wenn Karma nun korrekt konfiguriert ist, so brauchen wir doch einen passendes Docker Image. Normalerweise setze ich auf Alpine Linux, doch in diesem Fall gab es wegen der diversen Abhängigkeiten zu viele Komplikationen, weshalb ich für dieses Image Ubuntu empfehle.

Das gesamte Docker Image mit entry-points und config kann hier eingesehen werden:

https://github.com/codeclou/docker-nodejs-chrome-xvfb

Doch was musste nun geschehen, damit Chrome 59 dockerized läuft? Wie schon das einleitende Bild zeigt, erwartet Chrome eine ganze Menge. Ich gehe nun im Detail auf die verschiedenen Komponenten ein.

 

Xvfb - X Window Virtual Framebuffer

Will man eine grafische Anwendung in einem Docker Container laufen lassen, so wird diese sich fragen “Wo ist X?”. Also “Wo befindet sich die Grafische Oberfläche?”. Da wir in unserem Docker Image keine vollwertige grafische Oberfläche starten wollen, nutzen wir Xvfb und starten eine virtuelle Oberfläche, die uns einen virtuellen Screen zur Verfügung stellt. Wir starten Xvfb im Docker Entrypoint und stellen Display 99 zur Verfügung.

shell:Xvfb :99 -ac -screen 0 1280x1024x16 -nolisten tcp &

 

shell:xvfb=$!

 

shell:export DISPLAY=:99

Somit kann nun ein beliebiges grafisches Programm innerhalb dieser Umgebung laufen.

 

Dbus - Desktop-Bus

Startet man Chrome mit der Option ––headless dann möchte er auf Dbus zugreifen und beschwert sich lautstark, wenn das System nicht vorhanden ist. Anscheinend möchte Chrome gerne Nachrichten über das Desktop Bussystem verschicken. Auch wenn das in unserem Fall wenig Sinn macht, da der Desktop virtuell ist und die Nachrichten niemand lesen will. Aber ok. Was Chrome will bekomt Chrome.

Chrome zeigt in diesem Fall keine klare Fehlermeldung an, wenn Dbus nicht läuft.

Dbus fehlt
‍Dbus fehlt

Daher starten wir im Docker Entrypoint auch Dbus.

shell:eval `dbus-launch --sh-syntax --config-file=/work-bin/dbus-system.conf`

Die verwendete Config ist dabei sehr “lasch”, also erlaubt das Senden ohne großartige Restriktionen. Wer möchte kann und sollte sich diese config noch restriktiver gestalten. In neueren Versionen oder Patch-Updates kann es sein, dass dieses Problem behoben wird.

 

pulseaudio - Audio Manager

Chrome möchte mit der Option ––headless auch sehr gerne eine Soundkarte haben. Die hat er sich schon immer gewünscht und wir wollen mal nicht so sein. Dazu starten wir im Docker Entrypoint pulseaudio im Daemon Modus.

Somit sollte die Multiple instances of AudioManager Fehlermeldung verschwinden:

Multiple instances of AudioManager detected - Fehler
‍Multiple instances of AudioManager detected - Fehler

 

/dev/shm - Shared Memory Bereich

Zum Zeitpunkt des Schreibens war Chrome nicht zufrieden mit der Größe des Shared Memory Bereichs. Diese liegt bei Docker Containern per default bei 64MB. Wir können diesen mit der Option ––shm-size=128M auf 128MB erhöhen. Somit ist Chrome dann auch zufrieden.

Zu wenig Shared Memory
‍Zu wenig Shared Memory

 

FAZIT

Mit ein paar Kniffen kann man auf PhantomJS verzichten und auch dockerized Chrome verwenden. Die Option ––headless scheint irgendwie mehr Probleme zu machen als zu lösen, daher empfehle ich diese für Karma Tests nicht zu verwenden. Es reicht ––disable-gpu und ––no-sandbox zu verwenden. Das richtige Docker Image ist eine Kunst für sich, aber die Grundlage ist geschaffen, sich ein eigenes Docker Image zu schreiben. Viel Erfolg dabei :)