Automatisch visueel testen

De nieuwe website van de NS is geschikt voor elke schermgrootte. Bezoek je de site op je mobiele telefoon of tablet, dan hoef je niet meer te zoomen of heen en weer te scrollen. Om te garanderen dat de website op verschillende schermgroottes goed werkt, maken we gebruik van geautomatiseerde browsertests op basis van Selenium. Maar voor het testen van het design en de lay-out vertrouwen we nog volledig op onze eigen ogen. Dit kun je namelijk grondiger, sneller en goedkoper automatisch doen. In dit artikel leer je hoe je met het Galen Framework een responsive website automatisch visueel kunt testen.

Hello world

De software (~21 Mb) installeer je eenvoudig via NPM (NodeJs) of je gebruikt de installer voor OS X en Linux. Windows-gebruikers kunnen na het downloaden de software via een bat-script bedienen. Je kunt de software via Maven ook aan je projecten toevoegen, maar daarover later meer. Op de website van het project staat de installatie tot in detail beschreven.

Het Galen Framework is geschreven in Java, maar voor de tests (specs) gebruikt Galen zijn eigen taal. Hierdoor kun je ook met Galen aan de slag zonder kennis van Java. Galen is trouwens een off-side rule language, waarbij spaties en niet accolades worden gebruikt om je statements te groeperen.


@objects
  logo    css    .nslogo__image
 
= Header section =
  logo:
    height 21px

 Listing 1 – de testobjecten in homepage.spec staan gescheiden van de tests

Listing 1 is een eenvoudige test voor het NS-logo op de website. In woorden staat er: controleer of het element met de CSS-selector .nslogo__image 21 pixels hoog is. De schermelementen (@objects) staan gescheiden van de tests. Je kunt objecten daardoor eenvoudig hergebruiken. Vervolgens type je op de commandline: galen check homepage.spec –url http://ns.nl –size 1024×768 en even later staat het resultaat op je scherm (Listing 2).


========================================
Test: homepage.spec
========================================
check  homepage.spec --url http://ns.nl --size 1024x768
= Header section =
    logo:
->      height 21px
->      :   "logo" height is 20px instead of 21px
========================================
----------------------------------------
========================================
Failed tests:
    homepage.spec
 
Suite status: FAIL
Total tests: 1
Total failed tests: 1
Total failures: 1
There were failures in galen tests

Listing 2 – output van Galen: het logo is 1 pixel lager dan verwacht

De test faalt, omdat het logo op de website 20 pixels hoog is en niet 21 pixels. Oké, een typefoutje. Dat kan gebeuren. Als de hoogte van het element kan variëren, dan kun je ook schrijven: 19 to 21 px of ~ 20 px. Maar > 50 % of viewport/height kan ook. Galen kent naast viewport nog een aantal handige objecten, zoals parent en screen. Je kunt hier ook je eigen objecten gebruiken.

Afbeelding 1 – de reisplanner op de website van de NS

 

Iets geavanceerder: posities ten opzichte van elkaar

Stel nu dat het design van de website voorschrijft dat formulierelementen naast elkaar staan, zoals het geval is bij de velden van de reisplanner op een breder scherm (Afbeelding 1). Je kunt dan de y-positie van de elementen met elkaar vergelijken, maar met Galen kan het eenvoudiger (Listing 3).


@objects
  inputFrom      id   rp-reisopties-vertreklocatie
  inputTo        id   rp-reisopties-aankomstlocatie
  inputSubmit    id   rp-reisopties-submit
 
= Horizontal form elements =
  inputTo:
    right-of inputFrom
 
  inputSubmit:
    right-of inputTo

Listing 3. Controleer de positie van objecten ten opzichte van elkaar

Als het design daarbij voorschrijft dat de ruimte tussen de elementen 25 pixels is, dan schrijf je net zo makkelijk right-of <element> 25px. Andere dimensies dan px of % kun je overigens niet gebruiken.

 

Afbeelding 2 – reisplanner op smal scherm

 

Verschillende schermgroottes

Op smallere schermen zullen deze elementen onder elkaar moeten komen (Afbeelding 2). Anders kun je niet zien welk station of, later dit jaar, welk adres je invoert. Als je in Listing 3 right-of vervangt door below, dan ben je in principe klaar. Met behulp van tags kun je de tests vervolgens per schermgrootte groeperen (Listing 4).


@objects
  …
 
= Horizontal form elements =
  @on desktop
    inputTo:
      right-of inputFrom
 
    inputSubmit:
      right-of inputTo
 
= Vertical form elements =
  @on mobile
    inputTo:
      below inputFrom
 
    inputSubmit:
      below inputTo

Listing 4. Tests groeperen met behulp van tags

Tests die overal gedaan moeten worden, tag je met @on * of @on dekstop, mobile. Met behulp van een testsuite kun je de website vervolgens achter elkaar testen op de verschillende schermbreedtes (Listing 5). Als je Listing 5 opslaat als homepage.test kun je de suite vanaf de commandline starten met galen test homepage.test.


Homepage on Desktop
  http://ns.nl 1024x768
    check form-elements.spec --include “desktop”
 
Homepage on Mobile
  http://ns.nl 480x600
    check form-elements.spec --include “mobile

Listing 5. Testsuite voor verschillende schermgroottes (homepage.test)
 

Geavanceerdere testsuites

Op de vetgedrukte woorden na, staat er in de testsuite van Listing 5 eigenlijk tweemaal hetzelfde. Kan dat niet anders? Jawel, door te werken met tabellen en variabelen is het mogelijk om je testsuites clean te houden. Listing 6 doet bijvoorbeeld hetzelfde als Listing 5. Deze listing bevat nu iets meer code, maar je begrijpt dat die minder snel zal groeien als we er nog wat tests bij schrijven. Stel nu dat we deze tests ook in verschillende browsers willen uitvoeren.


@@ parameterized
  | name    | tags    | size     |
  | Desktop | desktop | 1024x768 |
  | Mobile  | mobile  | 480x600  |
 
Homepage on ${name}
  http://ns.nl ${size}
    check form-elements.spec --include “${tags}

Listing 6. Tests parameteriseren

Voor het aansturen van de webbrowser maakt het framework gebruik van Selenium en die gebruikt standaard Firefox als testbrowser. Maar je kunt tests in elke webbrowser uitvoeren. Om met andere browsers te testen, is het op Windows in elk geval nodig om extra browserdrivers te installeren (ChromeDriver, PhantomJs, WebDriver for MS Edge en dergelijke). Het voorbeeld van Listing 6 kunnen we dan eenvoudig uitbreiden met een extra tabel (Listing 7).


@@ parameterized
  | browser   |
  | Firefox   |
  | Chrome    |
  | PhantomJS |
 
@@ parameterized
  | name    | tags    | size     |
  | Desktop | desktop | 1024x768 |
  | Mobile  | mobile  | 480x600  |
 
Homepage on ${name} in ${browser}
  selenium ${browser} http://ns.nl ${size}
    check form-elements.spec --include “${tags}”

Listing 7. Testen in verschillende browsers

De twee tabellen uit Listing 7 worden op het moment van uitvoeren gecombineerd tot een enkele tabel, waardoor de test voor elke device en elke browser wordt uitgevoerd. In totaal wordt de test zes keer uitgevoerd. Als je gebruik maakt van Selenium Grid, dan kun je die tests parallel uitvoeren. Als meerdere testsuites dezelfde parameters of tabellen nodig hebben, dan kun je de tabellen opslaan in een eigen suite en die importeren met een import statement: @@ import base.test. Galen staat je dus niet in de weg om een gestructureerde verzameling tests te schrijven.

Nog iets waar Galen je niet bij in de weg staat is Test Driven Development. Voor de meeste Java-ontwikkelaars gaat dit misschien een brug te ver (en voor de meeste front-end ontwikkelaars waarschijnlijk ook), maar doordat Galen werkt met de schermelementen kun je al tests schrijven, zodra de requirements van het design er zijn. Met testframeworks, die uitsluitend screenshots met elkaar vergelijken, lukt dat niet.

 

Visuele regressie afvangen

Screenshots met elkaar vergelijken is een bekende manier om design regressie van een website tegen te gaan. Hele afbeeldingen met elkaar vergelijken werkt alleen niet altijd. Neem bijvoorbeeld de homepage van de NS. Daar is het gele vlak van de reisplanner semi-transparant, waardoor je subtiel nog een stukje van de afbeeldingencarrousel erachter ziet. Tijdens het testen kan de afbeelding veranderen en daarom kun je niet een bestaande en een nieuwe screenshot met elkaar vergelijken. Doe je dat wel, dan loop je grote kans dat je test faalt. Visuele checks uitvoeren met Galen werkt het makkelijkst als je –-htmlreport report als extra commandline parameter meegeeft. Het testresultaat wordt dan als HTML-rapport opgeslagen (in ./report) en daarin staat dan Afbeelding 3.

Afbeelding 3 – falende test door dynamische content

Wat je in zo’n geval kunt doen, is een tolerantie instellen. In Listing 8 wordt de reisplanner uit de pagina opgehaald met een ietwat onooglijke XPath-expressie.


@objects
  planner      xpath    /html/body/div/div[2]/div/div/div
 
= Visually check the planner on homepage =
  planner:
    image file images/homepage-planner.png, error 8%

Listing 8. Visueel checken, oftewel afbeeldingen vergelijken

Dit had eenvoudiger gekund met een CSS-selector, maar nu weet je tenminste dat Galen ook XPath ondersteunt. Vervolgens maakt Galen daar een afbeelding van en deze wordt met een tolerantie (error) van 8% vergeleken met een afbeelding uit de cache. Die cache moet je eerst zelf aanleggen, want Galen helpt je daar nog niet bij. Het is echter niet moeilijk om een falende test te schrijven en het resultaat daarvan (Actual in Afbeelding 3) op te slaan in de cache. Voordat je afbeeldingen met elkaar vergelijkt, kun je ze in Galen bewerken. Er zijn opties voor crop, stretch, offset, denoise, blur, contrast, saturation, enzovoort. De functionaliteit van de meesten spreekt voor zich.

 

Pagina-interactie

Een andere manier om lastige objecten te kunnen testen, werkt via Javascript-injectie. Je kunt daarmee elementen verwijderen of de achtergrond juist ondoorzichtig maken. Of zo’n manier van werken past binnen je teststrategie mag je zelf bepalen. Ook kun je met Javascript-injectie een cookiebanner op je site weghalen, maar dat kan ook met cookie-injectie. De injecties regel je in de testsuite en Listing 9 toont een suite waar Javascript en een cookie worden geïnjecteerd.


Homepage image check
  http://ns.nl 480x600
    inject scripts/removeCarrousel.js
    cookie “cookieBannerBeGone=true; path=/”
    check homepage-image.spec

Listing 9. Testsuite met Javascript- en cookie-injectie

Pagina-interactie is ook het klikken op elementen. Je wilt namelijk elke toestand van een webpagina kunnen testen. Dat is helaas niet triviaal in Galen. In Selenium voer je simpelweg een click() uit op een bepaald element. Je verwacht dat zo’n klik via Galen wel door te geven is, maar dat is (nog) niet zo. Voor nu zijn er drie manieren om dit te doen. De eenvoudigste is met Javascript-injectie. Daarnaast heeft Galen een uitgebreide Javascript API, waarmee je Selenium en dus de browser kunt aansturen. En tenslotte kun je hetzelfde ook doen in een Seleniumtest, die geschreven is in Java. Dat laatste voorbeeld bespreken we hierna wat uitgebreider, omdat het een mooi bruggetje is naar de integratie van Galen in je buildproces.

 

Maven- en buildserverintegratie

Er is een Maven-dependency voor Galen, waarmee je de software aan je project kunt toevoegen. In het project zitten abstract classes voor JUnit en TestNG, waardoor je lekker snel aan de slag kunt. Listing 10 is een voorbeeld van zo’n test, waarbij op de homepage van de NS een reis wordt gepland van Harlingen naar Vlissingen. Vervolgens wordt het menu met de meer-reisopties geopend en dan controleren we de layout met Galen.


public class HomePageTest extends GalenTestNgTestBase {
 
  @Test
  public void testLayoutOfExtraTravelOptions() throws Exception {
 
    // web page under test
    load("http://www.ns.nl", 1024, 768);
 
    // interaction
    WebDriver driver = getDriver();
    driver.findElement(By.id("rp-reisopties-vertreklocatie")).sendKeys("Harlingen\t");
    driver.findElement(By.id("rp-reisopties-aankomstlocatie")).sendKeys("Vlissingen\t");
    driver.findElement(By.id("rp-reisopties-meerreisopties")).click();
 
    // layout checking
    checkLayout("form-elements-extra.spec", Collections.singletonList("*"));
  }
 
  @Override
  public WebDriver createDriver(Object[] args) {
    FirefoxDriver driver = new FirefoxDriver();
    driver.manage().timeouts().implicitlyWait(500, TimeUnit.MILLISECONDS);
    return driver;
  }
}

Listing 10. Interactie met een webpagina voordat er wordt getest

Doordat de software netjes integreert met je testframework en gebouwd is op Selenium kun je de tests eenvoudig uitvoeren als onderdeel van je buildproces. Ook kun je de tests parallel naast elkaar uitvoeren in een eigen Selenium Grid en bij BrowserStack of Sauce Labs.

 

Conclusie

Automatisch testen doe je waarschijnlijk al een tijdje, maar test je het design en de lay-out van je website ook? Waarschijnlijk niet en dat is een risico. De designs die je tegenkomt worden steeds geavanceerder, doordat ze op alle schermgroottes moeten werken. Door regressie van de code kunnen op veel meer plaatsen subtiele visuele bugs ontstaan. Geautomatiseerd visueel testen kan helpen dat te voorkomen. De ontwikkelaars van Galen zijn zelf actief als tester, ontwikkelaar of architect en dat zie je terug in de structuur en mogelijkheden van het project. Het framework is volwassen qua mogelijkheden en sluit goed aan bij de meest gangbare buildprocessen. De documentatie en de code zijn dik in orde en de ontwikkelaars reageren snel op vragen en bugreports.

 

Referenties