Présentation de Protractor pour réaliser des tests fonctionnels pour AngularJS

Publié le 20 janvier 2016 par Théo Delaune | outils

Cet article est publié sous licence CC BY-NC-SA

Nous avons découvert dans un précédent article — intitulé Préparation aux tests unitaires d’une application AngularJS — la mise en place de tests unitaires au sein d’une application AngularJS.

Aujourd’hui nous allons aborder la partie tests fonctionnels, plus communément appelés tests E2E (End to End).

Le but des tests E2E est de lancer l’application dans un navigateur web pour obtenir les mêmes comportements qu’un utilisateur lambda. Ces tests sont utiles pour vérifier les interactions côté client, ainsi que le bon retour lors d’une action.

Pourquoi du E2E

L’avantage des tests E2E est de pouvoir tester son application comme elle apparaîtra aux yeux de l’utilisateur.

Ils sont ceux qui collent le mieux pour répondre parfaitement à une demande client. Ex: Lors du click sur le bouton 'acheter' afficher une image 'panier'.

Mais ils permettent également de maintenir cohérente l’application. Car tout changement au sein de l’application permet de vérifier la conformité de l’application lors des évolutions de versions.

Protractor permet d’exécuter ces tests E2E directement dans le ou les navigateurs physiques de votre choix. Il interagit avec le navigateur naturellement comme tout utilisateur le ferait.

Mise en place de Protractor

Nous partons du principe que vous avez déjà une application AngularJS mise en place.

Il nous faut avant tout installer le package protractor.

npm install -g protractor

Nous utiliserons ici Mocha, qui est un framework de tests JavaScript. C’est un choix personnel car ce framework a une syntaxe proche de celle de Rspec (Ruby), donc un style d’écriture des tests orienté BDD.

npm install mocha --save-dev

Pour la librairie d’assertions JS, mon choix s’est porté sur ChaiJS qui a une syntaxe que j’affectionne et qui en plus est suggéré lors de l’utilisation de Mocha.

npm install chai --save-dev

L’utilisation de Mocha avec Protractor requiert l’utilisation du package chai-as-promised pour que nos tests puissent se jouer.

npm install chai-as-promised --save-dev

Il nous reste maintenant le principal, Selenium, c’est une application qui va faire le lien entre nos tests et le navigateur sur lequel nos tests sont joués.

Nous pouvons télécharger le binaire avec cette commande :

webdriver-manager update

Il se peut que vous ne trouviez pas le binaire webdriver-manager, dans ce cas il est disponible dans les node_modules de Protractor (./node_modules/protractor/bin/webdriver-manager).

Fichier de configuration de Protractor

Ce fichier de configuration n’est pas obligatoire, mais très largement conseillé, car nous allons pouvoir spécifier à Protractor les variables de configuration suivantes :

  • Où se trouve Selenium ?
  • Quel framework de test utilisons-nous ?
  • Sur quel navigateur jouer les tests ?
  • Où se trouvent nos jeux de tests ?

Ce fichier de configuration a un nommage libre, personnellement je préfère le nommer protractor-config.js pour lever toute ambiguïté.

Où se trouve Selenium ?

Deux cas de figure ici, car il y a deux façons d’appeler Selenium dans notre fichier de configuration. Tout va dépendre de la manière de lancer nos tests.

Dans le premier cas, nous partons du principe que nous lançons Selenium en arrière plan avant d’exécuter nos tests. Il faut alors préciser dans le fichier de configuration, quelle est l’URL correspondante.

exports.config = {
  seleniumAddress: 'http://localhost:4444/wd/hub',
  
};

Dans l’autre cas, nous demandons à Protractor de lancer Selenium via le pilote selenium-webdriver avant d’exécuter les tests.

exports.config = {
  seleniumServerJar: "./node_modules/protractor/selenium/selenium-server-standalone-2.48.2.jar",
  
};

À vous de choisir la solution qui s’adaptera le mieux à votre besoin.

Quel framework de test ?

Protractor se lance par défaut avec le framework Jasmine, or nous utilisons dans ce cas-ci Mocha. À nous de lui préciser que c’est ce framework qui lance nos tests.

exports.config = {
  framework: "mocha",
  
};

Sur quel navigateur jouer les tests

La configuration de Protractor permet de les jouer sur un ou plusieurs navigateurs.

Dans le cas où vous souhaitez les lancer sur un seul navigateur, vous pouvez utiliser cette configuration.

exports.config = {
  capabilities: {
    'browserName': 'chrome'
  },
  
};

Mais si vous souhaitez pouvoir les exécuter sur Chrome et Firefox, la configuration suivante permet de spécifier autant de navigateurs que vous le souhaitez.

exports.config = {
  multiCapabilities: [{
    browserName: 'firefox'
  }, {
    browserName: 'chrome'
  }],
  
};

Où se trouvent nos jeux de tests ?

Reste la dernière étape de configuration pour que tout soit valide, il nous faut maintenant spécifier où se trouvent nos fichiers de tests.

exports.config = {
  specs: ["test/e2e/**/*Spec.js"],
  
};

Cette configuration va nous permettre de récupérer tous les fichiers se trouvant dans le dossier ainsi que les sous-dossiers de test/e2e/ et se terminant par *Spec.js.

Si vous souhaitez lui spécifier un seul fichier vous le pouvez :

exports.config = {
  specs: ["test/e2e/WelcomeSpec.js"],
  
};

Passons à l’écriture de nos tests

Comme cité plus haut, nous utilisons Mocha et ChaiJS pour écrire nos tests, cela nécessite une configuration en plus sur nos fichiers.

var chai = require('chai');
var chaiAsPromised = require('chai-as-promised');
chai.use(chaiAsPromised);
var expect = chai.expect;

describe('Welcome page', function() {

});

Ici, ChaiJS va remplacer le expect fourni par Protractor par celui issu de chai-as-promised.

La suite de l’article contient les points qui m’ont semblé important lorsque j’ai débuté avec Protractor. Pour accéder à la référence de toutes les interactions possibles avec Protractor, voici son API.

Interagir avec le navigateur

Pour que notre test soit fonctionnel, il faut exécuter les actions comme un utilisateur le ferait.

Ici nous utilisons browser.get() pour accéder à la page de notre application par le biais du navigateur.

eventually est utilisé car l’assertion est issue d’une promise.



describe('Welcome page', function() {
  it('has a correct title', function() {
    browser.get("http://locahost:2222");
    expect(browser.getTitle()).to.eventually.equal("My welcome page");
  });
});

Dans cet exemple nous voulons vérifier que le titre de notre application est correct.

browser.getTitle() nous retourne le contenu de la balise <title></title>.

Mocker un appel HTTP

L’un des points sur lequel j’ai bloqué lors de mes débuts avec Protractor est le mock d’appels HTTP.

C’est l’une des choses primordiales pour lancer nos tests dans un environnement cloisonné, il serait absurde de devoir lancer une application tierce pour qu’ils puissent tourner correctement.

Nous surchargeons l’application AngularJS lors de nos tests pour lui spécifier que plusieurs routes doivent nous retourner un contenu prédéfini.

describe('Welcome page', function() {

  beforeEach(function() {
    browser.addMockModule('httpMocker', function() {
      angular.module('httpMocker', ['welcomeApp', 'ngMockE2E'])
        .run(function($httpBackend) {
          $httpBackend.whenGET(/api\/cars).respond({cars: ["renault"]});
          $httpBackend.whenGET(/.*/).passThrough();
      });
    });
  });

  
});

Cet exemple est placé volontairement à l’intérieur d’un beforeEach, cela nous permet de mocker nos requêtes, sur tous les tests à l’intérieur du block Welcome page.

Un point important ici est l’utilisation du $httpBackend.whenGET(/.*/).passThrough();, cela précise à notre application que tous les appels en dehors de ceux spécifiés se déroulent normalement. Si vous omettez ce passThrough(), plus aucune de vos routes ne sera accessible.

Il est conseillé d’utiliser une regex pour spécifier la route mockée, sur notre exemple notre regex correspond à la route http://monapp/api/cars.

Vérifier le contenu d’un élément

La récupération d’un élément s’opère par l’intermédiaire de la fonction element.

Nous utilisons ici le by.css() pour lui spécifier que nous souhaitons trouver l’élément par l’intermédiaire d’une requête sur le DOM.

it('has submit button', function() {
  browser.get("http://locahost:2222");
  var submitButton = element(by.css("form input[type=submit]"));
  expect(submitButton.getAttribute('value')).to.eventually.equal("Add to cart");
});

Il existe évidemment bon nombre de façon de récupérer un ou des éléments, je vous laisse le soin de les découvrir sur la documentation de référence Protractor By.

Récupération de plusieurs éléments

Imaginons que notre application contienne plusieurs span avec chacune du texte brut. Nous voulons savoir sans distinction des span si un texte est bien présent à l’intérieur de l’une d’elles.

Rien de plus simple, il nous suffit d’utiliser element.all() en lieu et place de element().

it('has cart text', function() {
  browser.get("http://locahost:2222");
  var spanElements = element.all(by.css("span"));
  expect(span.getText()).to.eventually.include("My cart is empty");
});

Effectuer un click sur un élément

Nous souhaitons vérifier si lors d’un click sur un bouton, son texte change. il nous suffit d’appeler click() sur l’élément récupéré.

it('has submit button', function() {
  browser.get("http://locahost:2222");
  var submitButton = element(by.css("form input[type=submit]"));
  submitButton.click();

  expect(submitButton.getAttribute('value')).to.eventually.equal("Added !");
});

Ajout d’un email dans un input

C’est l’une des choses que l’on est quasiment sûr d’utiliser dans un de nos tests, nous utilisons dans ce cas la fonction sendKeys() sur l’élément sélectionné.

it('returns error when email is invalid', function() {
  var errorMessage = "Wrong email";
  var submitButton = element(by.css("form input[type=submit]"));
  var inputEmail = element(by.css("form input.email"));

  inputEmail.sendKeys("test@synbioz.com");
  submitButton.click();

  expect(element(by.css("span.error-message")).getText()).to.eventually.equal(errorMessage);
});

sendKeys() permet également d’envoyer un signal d’appui sur une touche. Si nous prenons pour exemple la touche entrée : .sendKeys(protractor.Key.ENTER).

Lancement de nos tests

Il ne nous reste plus qu’à lancer nos tests. Nous utilisons Protractor et son fichier de configuration pour les lancer.

protractor protractor-config.js

Si vous avez tout configuré correctement, vous verrez votre navigateur choisi dans la configuration s’ouvrir et exécuter vos tests.

Conclusion

Protractor est donc l’un des outils primordiaux à avoir dans votre stack de développement d’application AngularJS. Cela apporte une réelle vision par les tests du rendu vu par l’utilisateur.

N’hésitez pas à creuser dans l’API Protractor pour y découvrir tous les matchers utiles à vos développements.

Vous pouvez également vous intéresser à Grunt/Gulp pour exécuter Protractor en tant que tâche, comme par exemple gulp matache-e2e.


L’équipe Synbioz. Libres d’être ensemble.