Cucumber for Angular

Introduction

AngularCLI est une interface en ligne de commandes pour Angular (trouvé sur https://cli.angular.io/).

Cet outil comporte plusieurs fonctionnalités utiles qui nous font gagner beaucoup de temps. Il nous permet, par exemple, de générer un nouveau projet (Angular bien sûr) en une seule ligne de commande.

Dans cet article, nous n’allons pas aborder toutes les fonctionalités de AngularCLI, nous allons plutôt nous concentrer sur la partie test, plus précisément les tests e2e.

En effet, AngularCLI possède une commande ng e2e, qui nous permet de lancer les tests e2e. Cela est possible car au moment de la génération d’un nouveau projet Angular, AngularCLI va installer des paquets tiers qui nous permettent d’implémenter et exécuter les tests e2e. Il va installer (et configurer) pour nous Protractor (qui à son tour va installer webdriver et selenium) et Jasmine (utilisé aussi pour les tests unitaires). Ce n’est peut-être pas la bonne sélection d’outils si nous voulons faire du BDD ou si nous avons juste besoin d’exécuter nos tests sur Chrome Headless et que les autres navigateurs ne nous intéressent pas.

À travers cet article, nous allons voir comment supprimer Protractor et le remplacer par Cucumber (pour le BDD) et Puppeteer (pour communiquer avec Chrome Headless).

Supprimer Protractor

Avant de mettre en place Cucumber/Puppeteer, nous allons d’abord supprimer Protractor et les répertoires/fichiers associés qui ont été générés par AngularCLI.

Désinstaller Protractor

La désinstallation de Protractor se fait comme suit :

$ npm uninstall protractor --save-dev

Supprimer la partie e2e des fichiers de configuration

AngularCLI nous permet de lancer les tests e2e via la commande ng e2e, nous allons donc retirer l’appel à cette commande des scripts npm qui se trouvent dans le fichier package.json :

 {
   "scripts": {
-    "e2e": "ng e2e",
+    "e2e": "echo \"no e2e test yet!\"",
     ...
   }
 }

Vu que nous ne passons plus par AngularCLI pour nos tests e2e, nous allons supprimer du fichier angular.json le projet <project-name>-e2e (où <project-name> est le nom de votre projet) qui se trouve dans la partie projects :

 {
   "projects": {
     "my-project": {...},
-    "my-project-e2e": {...}
   }
 }

Le dernier fichier de configuration à modifier est src/tsconfig.e2e.json. Nous allons retirer jasmine et jasminewd2 de la propriété types des options du compilateur TypeScript (nous verrons un peu plus bas par quoi nous allons le remplacer) :

 {
   "extends": "../tsconfig.json",
   "compilerOptions": {
     "outDir": "../out-tsc/app",
     "module": "",
     "target": "es5",
     "types": [
-      "jasmine",
-      "jasminewd2",
       "node"
     ]
   }
 }

Supprimer les fichiers créés par AngularCLI

Ensuite, nous allons supprimer le contenu du répertoire e2e/src et le fichier e2e/protractor.conf.js :

$ rm e2e/src/* e2e/protractor.conf.js

Mettre en place Cucumber/Puppeteer

Maintenant que nous avons désinstallé Protractor et supprimé les fichiers de configuration correspondants, passons à l’étape suivante qui est la mise en place de Cucumber/Puppeteer et Chai pour pouvoir faire nos assertions.

Installation

Nous allons installer les paquets suivants :

  • Cucumber : c’est l’outil qui va nous permettre d’exécuter nos tests.
  • Puppeteer : pour communiquer avec l’API de Chrome/Chromium
  • Chai : une librairie d’assertion.
$ npm install cucumber puppeteer chai --save-dev

Afin que TypeScript puisse reconnaître les paquets que nous venons d’installer, nous allons aussi installer les Type Definition de ces derniers :

$ npm install @types/{cucumber,puppeteer,chai}

Structure des fichiers

La structure par défaut proposée par Cucumber est la suivante :

features/
  step_definitions/
    feautre1.steps.ts
    feautre2.steps.ts
    ...
  feature1.feature
  feature2.feature
  ...

Personnellement je préfère séparer les steps des features, nous allons donc les mettre au même niveau (si vous voulez opter pour la structure proposée par Cucumber, il faudra dans ce cas adapter les chemins dans les étapes qui viennent). Nous allons aussi utiliser le modèle Page Object Pattern afin de séparer l’appel à l’API de Chrome/Chromium de nos tests :

e2e/
  src/
    features/
      feature1.feature
      feature2.feature
      ...
    steps/
      feature1.steps.ts
      feature2.steps.ts
      ...
    po/
      app.po.ts
      ...
  tsconfig.e2e.json
  • features : contient nos user stories rédigées en Gherkin.
  • steps : contient l’implémentation des tests.
  • po : contient nos class PageObject

Configuration

Nous allons maintenant configurer notre application pour qu’elle puisse lancer les tests.

Commençons par le fichier src/tsconfig.e2e.json en ajoutant cucumber au tableau types que nous avons édité un peu plus haut :

 {
   "extends": "../tsconfig.json",
   "compilerOptions": {
     "outDir": "../out-tsc/app",
     "module": "commonjs",
     "target": "es5",
     "types": [
+      "cucumber",
       "node"
     ]
   }
 }

La dernière configuration à faire, est le script npm e2e. C’est à cet endroit que nous ferons appel à Cucumber pour exécuter nos tests.

La commande cucumber-js permet d’écrire les steps en TypeScript grâce à ts-node (d’autres transpilateurs sont supportés aussi, comme CoffeeScript), nous allons donc utiliser l’option --require-module afin d’utiliser ts-node.

Par défaut ts-node va charger le fichier tsconfig.json qui se trouve à la racine du projet. Mais comme cucumber-js ne permet pas le passage de paramètres au module ts-node, nous allons utiliser la variable d’environnement TS_NODE_PROJECT et lui assigner le chemin e2e/tsoncif.e2e.json que nous avons vu un peu plus haut.

 {
   "scripts": {
-    "e2e": "echo \"no e2e test yet\"",
+    "e2e": "TS_NODE_PROJECT=e2e/tsconfig.e2e.json cucumber-js --require-module ts-node/register -r e2e/steps/**/*.steps.ts e2e/features/**/*.feature",
     ...
   }
 }

Exemple de scénario

Nous allons à présent rédiger notre première feature que nous allons mettre dans le fichier e2e/src/features/welcome.feature :

Feature: Say hello to visitor
  Scenario: Display a welcome message
    Given a visitor visits our website
    When the home page is loaded
    Then he should see a message saying "Welcome Visitor !"

Passons maintenent au fichier e2e/src/steps/welcome.steps.ts :

import { AfterAll, BeforeAll, Given, Then, When }  from 'cucumber';
import { expect } from 'chai';

import { AppPage } from '../po/app.po';

let appPage: AppPage;

BeforeAll(async () => {
  appPage = new AppPage();
  await appPage.init();
});

Given('a visitor visits our website', async () => {
  await appPage.gotoPage('/');
});

When('the home page is loaded', async () => {
  await appPage.waitFor('h1');
});

Then('he should see a message saying {string}', async message => {
  expect(await appPage.getContent('h1')).to.equal(message);
});

AfterAll(() => {
  appPage.close();
});

Et dans le fichier e2e/src/po/app.po :

import * as puppeteer from 'puppeteer';

export class AppPage {
  browser: puppeteer.Browser;
  page: puppeteer.Page;
  baseUrl = 'http://localhost:4200';

  async init() {
    this.browser = await puppeteer.launch();
    this.page = await this.browser.newPage();
  }

  async gotoPage(url) {
    await this.page.goto(this.baseUrl + url);
  }

  async getContent(selector) {
    return await this.page
      .evaluate(select => document.querySelector(select).textContent, selector);
  }

  async waitFor(selector) {
    return this.page.waitFor(selector);
  }

  close() {
    this.browser.close();
  }
}

Exécution des tests

Avant de lancer Cucumber, nous devons exécuter npm start pour que Puppeteer puisse naviguer vers http://localhost:4200.

Une fois que notre serveur http en local est en marche, nous pouvons exécuter nos tests via la commande :

$ npm run e2e

Pour rappel, lors de la configuration de Cucumber nous avons remplacé le script npm e2e par la commande qui permet de lancer Cucumber.

Conclusion

AngularCLI nous fournit plein d’outils par défaut, afin d’accélerer et faciliter le développement de nos applications. Mais cela ne veut pas dire que nous ne pouvons pas remplacer ces outils par d’autres.

Dans notre cas, nous avons décidé de ne pas utiliser Protractor et d’utiliser à la place Cucumber/Puppeteer.

Nous avons donc commencé par voir comment supprimer Protractor. Ensuite, nous avons vu comment mettre en place Cucumber. Une fois tout en place, nous avons rédigé un exemple de scénario et implémenté les steps definitions de ce scénario. En dernier, nous avons vu comment exécuter nos tests.