CasperJS et les tests fonctionnels

CasperJS est l'outil open-source le plus utilisé pour mettre en place des tests fonctionnels sur des applications web.


Share on FacebookShare on Google+Share on LinkedInTweet about this on Twitter

Lors de mes récentes sessions de veille, je me suis intéressé à différents outils permettant de faciliter la mise en place de tests fonctionnels. À la différence des tests unitaires qui sont mis en place individuellement sur chaque projet, les tests fonctionnels sont encore peu implémentés. Et pourtant, ils demeurent tout aussi importants. La distinction pourrait se résumer en deux phrases bien choisies :

Unit testing makes sure you are using quality ingredients.
Functional testing makes sure your application doesn’t taste like crap.

Présentation

À l’heure actuelle, CasperJS est sans nul doute l’outil le plus utilisé et le projet le plus suivi pour mettre en place des tests fonctionnels sur des applications web. Il s’agit d’un utilitaire open-source écrit en JavaScript et basé principalement sur PhantomJS, un navigateur web headless, que l’on peut voir comme un navigateur sans interface graphique, qui intègre le moteur de rendu Blink (utilisé par Chrome, Chromium, Opera, Vivaldi). C’est ici la grande force de CasperJS : les tests ne sont pas effectués sur le code source retourné par le serveur mais directement comme si il s’agissait bel et bien d’un utilisateur derrière son navigateur.

Il est à noter qu’il est également multi-plateforme et qu’il peut s’utiliser depuis n’importe où sur un système, les projets ne sont donc nullement impactés par l’intégration des tests et nous aurons l’occasion de le constater lors de cette démonstration.

CasperJS est finalement une solution de scripting de scénario nous permettant de tester n’importe quelle fonctionnalité sur une application, et pas seulement. Entres autres, il nous permet de :

  • Naviguer sur une application et/ou entre plusieurs domaines,
  • Prendre une capture écran sous n’importe quelle résolution,
  • Saisir et valider des formulaires,
  • Attendre l’affichage d’un élément ou d’une ressource avant d’effectuer une action,
  • Vérifier l’existence des éléments attendus sur une page,
  • Intéragir avec n’importe quel élément du DOM,
  • Évaluer des portions de code executés directement sur la page, permettant d’interagir avec les plugins externes

A travers ce billet, nous verrons comment mettre en place rapidement et facilement tout un scénario de test pratique sur le site de GLOBALIS.

Getting started with PhantomJS

La première étape consiste à installer PhantomJS sur votre poste. Vous pourrez récupérer le binaire correspondant à votre système.

Ensuite, il suffit simplement d’installer CasperJS. Si vous avez npm : npm i -g casperjs Sinon, d’autres méthodes d’installations sont disponibles ici.

Voilà, CasperJS est installé, prêt à être utilisé !

Premiers pas avec la navigation

Création du scénario avec CasperJS

Pour appréhender le sujet, nous allons mettre en place un premier scénario de test qui va nous assurer que GLOBALIS est bien référencé chez Google, le cas échéant se diriger sur la page d’accueil de l’application et prendre une photo pour s’assurer que tout va bien (oui, rien que ça, en seulement quelques lignes de code).

Commençons par créer un fichier globalis.js n’importe où sur l’espace de travail comportant le code suivant :

casper.options.viewportSize = {width: 1024, height: 768};
var lastResourceLoaded = "scripts.min.js";
casper.test.begin("Testing Globalis", 1, function testGlobalis(test) {
   casper.start('http://google.fr/', function() {
      this.waitForSelector('form[action="/search"]');
   });
   casper.then(function() {
      this.fill('form[action="/search"]', {q: 'globalis'}, true);
   });
   casper.then(function() {
       this.click("h3.r a:first-of-type");
   });
   casper.then(function() {
       this.test.assertTitle("SSII Web - GLOBALIS, accélérateur de projets digitaux", "Globalis - Title is what we'd expect");
       this.waitForResource(lastResourceLoaded, function() {
           this.capture("images/globalis.png");
       });
   });
   casper.run(function() {
       test.done();
   });
});

Détail ligne par ligne

Voyons maintenant ligne par ligne la syntaxe et le fonctionnement de CasperJS.

Spécifie la taille du viewport à utiliser :

casper.options.viewportSize = {width: 1024, height: 768};

De nombreuses options sont paramétrables, on peut par exemple changer le userAgent utilisé ou spécifier un utilisateur et un mot de passe pour une authentification HTTP.

Spécifie le nom de la dernière ressource avant de considérer la page comme complètement chargée :

var lastResourceLoaded = "scripts.min.js";

Il s’agit ici d’un workaround afin de déterminer quand est-ce que la page est complètement chargée, ce qui est nécessaire pour faire une capture d’écran (CasperJS ne permettant pas actuellement de savoir si il reste des ressources à charger sur la page).

Commence un jeu de tests :

casper.test.begin("Testing Globalis", 1, function testGlobalis(test) {

« Testing Globalis » correspond au nom du test.

1 correspond ici au nombre de tests qui doivent normalement être éxecutés.

Charge la page http://google.fr :

casper.start('http://google.fr/', function() {

Attend que le formulaire de recherche de Google soit présent sur la page :

this.waitForSelector('form[action="/search"]');

Attend que l’action précédente soit terminée avant de continuer :

casper.then(function() {

Saisit la chaîne « globalis » dans le champ et soumet directement le formulaire :

this.fill('form[action="/search"]', {q: 'globalis'}, true);

Pour information, q correspond à l’attribut name du champ de recherche sur Google

Clique aveuglément sur le premier élément des résultats de la recherche :

this.click("h3.r a:first-of-type");

Teste si le titre de la nouvelle page correspond à celui attendu sur la homepage de GLOBALIS :

this.test.assertTitle("SSII Web - GLOBALIS, accélérateur de projets digitaux", "Globalis - Title is what we'd expect");

Les méthodes de l’objet test commençant par assert* permettent d’ajouter un test. De très nombreuses méthodes préfixées ainsi existent, ici nous effectuons un test sur le titre de la page afin de s’assurer que nous sommes arrivés sur la bonne page.
Le premier argument correspond au résultat attendu, le second correspond au libellé du test.

Attend que la ressource spécifiée soit chargée:

this.waitForResource(lastResourceLoaded, function() {

Effectue une capture d’écran et enregistre une copie au format PNG:

this.capture("images/globalis.png");

Lance les tests:

casper.run(function() {

Indique que les tests sont terminés:

test.done();

Pour résumer, le scénario se traduit de cette façon :

  • on commence par se diriger sur http://google.fr/ (comme un utilisateur qui taperait cette URL dans la barre d’adresse de son navigateur)
  • ensuite, on attend que le formulaire soit chargé avant de saisir “globalis” dans le champ de recherche, puis on valide
  • ensuite, on clique sur le premier lien des résultats
  • ensuite, on effectue un test sur le titre de la page puis on attend son chargement complet avant de faire une capture d’écran et d’enregistrer le résultat au format PNG

Lancement du script

Pour lancer le script, nous allons nous placer dans le dossier contenant le fichier globalis.js et exécuter CasperJS avec la commande suivante :

$ casperjs test globalis.js

Si tout se passe bien, nous devons obtenir le résultat suivant, nous indiquant que le test unique sur le titre de la page est passé :

Test file: globalis.js
# Testing Globalis
PASS Testing Globalis (1 test)
PASS Globalis - Title is what we'd expect
PASS 1 test executed in 5.223s, 1 passed, 0 failed, 0 dubious, 0 skipped.

À la fin du script, nous avons également lancé une capture d’écran. Un dossier images a été créé contenant un fichier globalis.png. Dans cet exemple, il est possible que le fond de l’image soit noir car aucun attribut background n’est spécifié sur l’élément <body> du site de GLOBALIS.

Les petits trucs à savoir

Deux points importants sont à noter sur le fonctionnement de CasperJS.

  • Lors du déroulement d’un jeu de test, le comportement par défaut est asynchrone. Cependant, il est souvent nécessaire d’attendre qu’une action soit terminée avant d’en lancer une autre. La méthode then() permet de séquencer nos parties de code.
  • Les tests sont bloquants : en cas d’échec, tout le script s’arrête et les résultats détaillés du test échoué s’affichent directement dans la console.

Aller plus loin avec la manipulation du DOM

Maintenant que nous avons pu nous assurer que le site de GLOBALIS était bien référencé sur Google, qu’on pouvait y accéder et s’assurer qu’il s’affichait correctement grâce à notre capture d’écran, nous allons pouvoir mettre en place des tests fonctionnels un peu plus poussés avec CasperJS.

Il serait par exemple intéressant de s’assurer qu’on affiche bien les offres d’emploi sur la page “Carrière” et que la modale permettant de postuler chez GLOBALIS fonctionne bien. Pour cela, nous allons rajouter ce bout de code juste avant l’appel au casper.run() :

casper.then(function() {
    this.test.assertExists("#menu a[href$='carriere/']", "Globalis - Career link exists");
    this.click("#menu a[href$='carriere/']");

    this.waitForUrl(/\/carriere\/$/, function() {
        test.assertElementCount(".block__recruitement-content", 4, "Globalis Career - 4 awesome jobs are displayed on the page");

        var modalOpensAndCloses = this.evaluate(function(){
            console.log("Now I'm in the DOM !");
            $("a[href='#cta_form_lightbox-904']").click()
            var modalVisibleAfterClick = $("#cta_form_lightbox-904").is(":visible");
            $(".mfp-bg").click();
            var modalClosedAfterClickOff = !$("#cta_form_lightbox-904").is(":visible");
            return (modalVisibleAfterClick && modalClosedAfterClickOff);
        });

        this.test.assert(modalOpensAndCloses, "Globalis Career - Apply modal opens and closes correctly");

        this.waitForResource(lastResourceLoaded, function() {
            this.capture("images/globalis-career.png");
        });
    });
});

Dans cette partie de code, CasperJS va tout d’abord vérifier s’il existe un lien vers la page “Carrière” dans le menu avec la méthode assertExists(), et le cas échéant, cliquer dessus. Ensuite, nous allons lui indiquer d’attendre que l’URL courante ait changé avec la méthode waitForUrl() comme on s’y attendrait en cliquant sur le lien. Arrivé sur la page “Carrière”, nous vérifions que nous avons bien le nombre voulu d’offres d’emplois affichés avec la méthode assertElementCount().

Une des fonctionnalités les plus intéressantes de CasperJS est la possibilité de faire évaluer du code directement par la page, en utilisant par exemple le plugin jQuery inclus sur le site de GLOBALIS. Pour ce faire, nous utilisons la méthode evaluate() dans lequel nous allons mettre un test nous permettant de nous assurer que la modale de candidature s’affiche puis se masque comme prévu. Il s’agit de la seule fonction qui s’exécute de façon synchrone, ce qui explique qu’on puisse directement tester le booléen retourné par evaluate() et déterminer si le test s’est bien déroulé. Pas besoin d’utiliser la méthode then() dans le cas présent.

Illustration article CasperJS

Arrivés là, vous devriez donc obtenir un résultat plutôt rassurant sur l’état de fonctionnement du WordPress GLOBALIS en exécutant la même commande que précédemment :

Test file: globalis.js
# Testing Globalis
PASS Testing Globalis (4 test)
PASS Globalis - Title is what we'd expect
PASS Globalis - Career link exists
PASS Globalis Career - 4 awesome jobs are displayed on the page
PASS Globalis Career - Apply modal opens and closes correctly
PASS 4 test executed in 9.223s, 4 passed, 0 failed, 0 dubious, 0 skipped.

En résumé

CasperJS est un outil réellement bien construit, qui impressionne par sa syntaxe rapidement assimilable et par ses nombreuses fonctionnalités. Il se déploie rapidement sur un poste de travail et permet de tester n’importe quel type d’application web. Personnellement, je n’ai pas encore atteint ses limites : tous les tests fonctionnels qui m’ont semblé pertinent à mettre en place sur le site de GLOBALIS ont pu être scénarisés. La seule problématique actuellement rencontrée est au niveau du chargement des pages : il n’est pas possible de savoir lorsqu’une est complètement chargée. Compte-tenu de l’engouement récent engendré pour ce projet, il n’est pas inenvisageable que ce défaut (et sans doute d’autres que je n’aurai pas encore remarqué) soit rapidement corrigé.

Ce premier billet avait pour but de présenter globalement et rapidement CasperJS à travers une mise en pratique autour du site de GLOBALIS, mais son utilisation pourrait être portée sur de nombreux projets développés en interne. Un prochain billet pourrait voir le jour, qui aura pour but d’apporter une solution plus globale, couvrant la majorité des tests fonctionnels communs à chaque projet et apportant un réel atout au développement front-end.

Quelques liens bien utiles

Publié le par Sylvain DUBUS

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *