Visuell regressiontestning i praktiken

När jag fick ansvaret för att refaktorera en större CSS-kodbas kände jag egentligen att jag hade tagit mig vatten över huvudet. Inte bara var min egen kompetens inom CSS på en helt rudimentär nivå, kodbasen var också i aktivt bruk i flera olika applikationer, i olika team inom SpareBank1-alliansen. Risken för regressioner var stor och det var fara för att team skulle bedöma refaktorering som för arbetsam att testa manuellt och välja att helt enkelt aldrig ta den i bruk i sin applikationer.

Lösningen blev att ta i bruk regressionstest-tankegången från TDD-metodiken och bara gå lös på refaktoreringen i skydd av en relevant enhetstest-suite.

Några existerande enhetstester fann inte, vilket fortsatt är det vanliga tillståndet när man jobbar med CSS.

Det naturliga (och enda?) infallsvinkeln för testning av CSS är rent visuell. Man tar screenshots av hur en browser renderar HTML-kod style:at av CSS:en som är under test. Baslinje-screenshots kan sedan jämföras vid framtida ändringar av CSS-koden. Det har funnits en del tooling baserat på denna princip, bl.a. med PhantomJS som browser, men de har lidit av false negatives orsakade av subtila skillnader i browserrendering mellan olika datorer. Vanliga problemkällor är t.ex. olika typer av font-antialiasing lokalt och på byggserver och dålig kontroll på vilka browsers och browserversioner som är installerade i olika miljöer.

Gemini är ett nyare testverktyg som baserar sig WebDriver-protokollet  (också känt som “Selenium”) för att integrera sig med reella browsers (snarare än pseudovarianter á la PhantomJs). Om man väljer att integrera Gemini mot Docker-images av t.ex. Firefox eller Chrome får man en testrigg som levererar konsistenta, pixel-perfekta screenshots på tvärs av maskiner før alltid och, i förlängningen, helt flakiness-fria tester.

Jag upprättade enkla statiska HTML-sidor som testfall för de olika komponenter i CSS-kodbasen, genererade baslinje-screenshot och satte upp kontinuerlig integrationstestning på byggservern. Med detta först på plats kände jag mig tillräckligt trygg för att ge mig i kast med själva huvuduppgiften. Vilken jag nu kunde genomföra i relativt trygg förvissning om att jag inte introducerade visuella regressioner.

Processen för att göra en ändring i CSS-kodbasen framøver blev efter inføringen av visuella regressionstester:

  1. Gör ändring i CSS-koden
  2. Kör testerna, observera att vilka som fallerar och att differensen är den förväntade.
  3. Generera nya baslinje-screenshots.
  4. Push:a kod-ändringar och screenshots-bildfiler och lägg upp Pull Request.
  5. Github och Atlassian Bitbucket har utmärkta funktioner för vissa diffar på bilder, vilket gør det enkelt att bedöma huruvida det uppstått oönskade ändringar.

Ibland kan det vara lite vanskligt att se vad som har ändrats genom att jämfør två olika bilder.

I pixel-diff modus är det betydligt enklare att få överblick.

I och med att PR:s nu har får ett visuellt element, där tydligt framgår vad som har ändrats, har det blivit möjligt (och lämpligt) att även inkludera personer som har en mer UX-inriktad än frontend-teknisk bakgrund i review-processen.

Visuell regressionstestning har varit en framgångshistoria i SpareBank 1 som har rullats ut inte bara i CSS-kodebaser av bibliotekstyp (CSS som delas på tvärs av applikationer) utan även i en del enskilda applikationer. Med visuelle tester på plats vågar utvecklare att refaktorera också CSS-koden löpande. Genom att PR:s blivit visuella har utvecklings- och peer review-processen blivit mer tillgänglig för testare, UX:are och andra fagpersoner som inte jobbar primärt som utvecklare.

Gemini-testrigg med Docker

Erfarenhetsmässigt är det helt essentiellt att ta total kontroll på browsern i alla miljøer som de visuelle regressiontester kommer exekeveras i. Detta låter sig enkelt göras med Docker och lite shell-plumbing:

#!/bin/bash -e

WEBSERVER=
WEBBROWSER=

run_gemini_command() {
  local -r command_and_args="$@"

  trap "_cleanup" INT TERM EXIT

  _start_webserver; _start_webbrowser;
  local -r wd_ip=$(docker inspect --format '{{ .NetworkSettings.IPAddress }}' ${WEBBROWSER})
  node_modules/.bin/gemini $command_and_args --grid-url=http://$wd_ip:4444/wd/hub visual-tests/
}

_cleanup() {
  testRes=$?
  trap - INT TERM EXIT
  docker rm -f ${WEBSERVER} > /dev/null || true
  docker rm -f ${WEBBROWSER} > /dev/null || true

  exit ${testRes}
}

_start_webserver() {
  local -r IMAGE=httpd:2.4

  WEBSERVER=$(docker run --detach \
    --volume $(pwd):/usr/local/apache2/htdocs/:ro,z \
    ${IMAGE})
}

_start_webbrowser() {
  WEBBROWSER=$(docker run --detach \
    --link ${WEBSERVER}:static.dev.sparebank1.no \
    sparebank1.no/firefox/main:2)
}

run_gemini_command test --reporter html --reporter flat

N.B:

  • byt sparebank1.no/firefox/main:2 mot image från docker-selenium.
  • det förutsätts att statiska HTML-sidor för testning är tillgängliga i aktuell arbetskatalog
  • browsercontainern som Gemini kørs mot kan accessera HTML-siderna genom ett fast domännamn (här static.dev.sparebank1.no) tack vara Dockers linking-funktionalitet.