Blog
Piloter vos tests fonctionnels Selenium depuis NodeJS

Partager

Introduction

Souvent confronté au stress de la mise en ligne de nouvelles versions de nos sites web, nous avons choisi à BigInt d'intégrer dans nos process la validation d'une suite de tests fonctionnels de manière à qualifier nos développements.

Cet article décrit comment piloter et contrôler des tests Selenium, en javascript avec NodeJS. Le tout distribué sur de nombreuses plateformes, systèmes d'exploitation et explorateurs.

Mise en situation

Dans cet article, nous n’apprendrons pas comment développer en javascript ni comment créer des tests fonctionnels avec Selenium. Cet article décrit la plateforme que nous avons mis en place, son fonctionnement, ses atouts et ses limites.

Le but de cet outil est de centraliser le pilotage des tests devant être effectués sur l’environnement de recettage. Ces tests pourront être joués en boucle, vérifierons la compatibilité sur différents explorateurs, ceci sans devoir passer par Selenium IDE ni mettre en place un environnement chez soi. Il est possible de lancer vos tests à distance, depuis une simple interface web, fluidifiant ainsi le process de mise en production et de détection des problèmes en amont.

Au terme de cet article, vous pourrez alors avoir une interface de pilotage, gérée par Node JS qui contrôle vos tests et présente les résultats.

Schéma utilisateur

Le serveur Selenium, supporté par tout une communauté très active, en fin de chaîne sur le schéma, peut effectuer les tests sur différentes explorateurs. Celui-ci n'est qu'un plugin porté sur de nombreuses plateformes Windows, Mac et désormais mobiles : Selendroid).

Les outils nécessaires

Cette plateforme repose sur une suite d'outils de technologies et langages divers, que nous avons connectés entre eux. Nous allons voir ici les éléments nécessaires à la mise en place de cette plateforme.

NodeJS

Logo Node JS

Le cœur de la plateforme de pilotage des tests à été développée sous NodeJS. C'est un serveur web en javascript supportant le protocole Websocket. Sa particularité est d’être très rapide et asynchrone. Le développement de connecteurs est donc facilité puisque ce langage est adaptés pour faire des requêtes asynchrones.

Selenium Server

Logo Selenium

Les tests sont exécutés sur les browser grace aux plugins Selenium disponible pour les browser Firefox et Chrome. Coté développeur, un IDE permet de créer facilement des tests fonctionnels d'application web, mais comme dit dans la présentation, cela ne fait pas partie de cet article.

Nous allons donc nous intéresser à Selenium Server qui est l'application JAVA qui permet d'ouvrir, de faire naviguer puis de fermer un browser. Malgré son nom ambiguë, il permet de piloter un explorateur client, mais surtout il est en ce sens un serveur car il attend sur le réseau qu'une requête lui dise quoi faire.

Parser des tests Selenium

Les fichiers de description des tests produit par Selenium IDE sont au format HTML. Mais voyons les plutôt comme un fichier XML "ready for presentation".

Les commandes sont contenues dans un tableau <table> dans lequel chaque ligne est une étape séparée la plupart du temps en trois colonnes :

  1. La première contient le nom de la commande
  2. La deuxième le selecteur CSS de l'élément cible sur laquelle la commande porte
  3. La troisième l’attribut de la commande
<table>
    <tr>
        <td>Commande</td>
        <td>Cible</td>
        <td>Attribut</td>
    </tr>
</table>

Afin de récupérer ces commandes, nous allons ouvrir notre fichier avec les fonctions de fichier de NodeJS puis en extraire la donnée souhaitée, c'est à dire les lignes du tableau.

Pour la lecture du fichier, nous allons utiliser readFile présent dans la librairie fs de NodeJS. La documentation de l'API fs est touffue mais plutôt bien écrite.

var fs = require("fs");
fs.readFile("test_selenium.html", "utf-8", function(err, data) { });

Dans ce fichier (qui est encodé en UTF-8), nous allons récupérer uniquement les éléments dont nous avons besoin, c'est à dire le contenu du tableau puis à l'intérieur chaque ligne que nous allons mettre dans un tableau d'objets en Javascript. Ce sera plus simple pour exécuter les tests par la suite.

Dans NodeJS, la librairie dom-JS permet de lire du XML puis de le transformer en arbre DOM. Il faut ensuite parcourir cet arbre depuis la balise racine (root) jusqu'à la balise <table> puis <tbody> pour récupérer son contenu.

var domJs = require("dom-js").DomJS;
var domjs = new domJs();
domjs.parse(data, function(err, dom) { var root = dom.children; });

Une simple boucle crée alors un tableau natif Javascript, la suite du traitement ne dépend alors plus du format / parseur.

var commands;
for (var line; (line = tbody_children.shift()); ) {
    commands.push({
        cmd: line.children[1].children[0].text,
        target: line.children[3].children[0]? line.children[3].children[0].text : null,
        attribute: line.children[5].children[0]? line.children[5].children[0].text : null,
    });
}
Note: on extrait les nœuds 1, 3, 5 car il y a des nœuds textes entre chaque balise.

La cible et l'argument sont définis par des ternaires car ils peuvent être null si la commande n'a pas d'arguments comme un refresh par exemple.

La liste de nos commandes extraites est alors contenue dans le tableau javascript nommé "commands". L'exemple ci-dessous, bien que simplifié, montre le contenu que nous avons extrait :

var commands = [
	{
		cmd: "click",
		target: "css=#button_submit",
		attribute: null
	},
	{
		cmd: "verifyElementPresent",
		target: "css=#notifaction",
		attribute: null
	}
];

Pour exécuter notre test, nous allons boucler sur le tableau et exécuter les commandes une par une. Chaque commande est envoyée avec la cible et l'argument puis on attends la réponse pour passer à la suivante.

Exécution d'un test

Lancer le serveur Selenium

La plateforme de pilotage des tests va se connecter aux serveurs Selenium qui vont réceptionner les commandes puis les exécuter dans le browser. Cette commande peut être de type "navigation" (avec une URL), test de présence (<div>) ou de texte (String.match(/.../)).

Le serveur Selenium est lancé au démarrage de la machine (Windows, Linux, Mac). C'est un programme Java donc identique sur chaque plateforme. Il nécessite juste une machine virtuelle compatible. Pour le lancer, rien de bien compliqué, c'est un fichier .jar :

java -jar selenium_server.jar -singleWindow

L'attribut singleWindow permet d’exécuter la fenêtre de contrôle et la fenêtre de test dans le même navigateur, pour mieux comprendre, je vous invite à effectuer un test avec et sans ce paramètre.

Envoyer des commandes à Selenium depuis NodeJS

Selenium Server implémente le protocole HTTP ce qui permet de le piloter par de simples requêtes web. Tous les environnement de programmation proposent une API haut niveau pour cela. Dans NodeJS, nous allons forger les requêtes grâce aux plugins "http" et "request".

Lors de l'envoi de la première requête HTTP au serveur, celui-ci va nous envoyer l'ID de la session de l'explorateur, cet ID permet de continuer à envoyer d'autres requêtes dans la même suite logique (cookie d'authentification) de pages. Cet ID permet à plusieurs personnes d'effectuer des tests en même temps.

var request = require('request');
var http = require('http');

function send_request(post_data, success) {
	var post_options = {
		host: "adresse du serveur selenium",
		port: '4444',
		path: '/selenium-server/driver/',
		method: 'POST',
		headers: {
			'Content-Type': 'application/x-www-form-urlencoded; charset=utf-8',
			'Content-Length': post_data.length
		}
	};

	var post_req = http.request(post_options, function(res) {
		res.setEncoding('utf-8');
		res.on('data', function (chunk) {
			success && success(chunk);
		});
	});

	post_req.write(post_data);
	post_req.end();
}

Nous commençons chaque test par une première commande spéciale qui n'est pas décrite dans nos fichiers de tests : "getNewBrowserSession". Le serveur va ouvrir une nouvelle session vide (sans historique, cookie, etc...) donc une nouvelle fenêtre puis nous renverra l'id_session. Dans les requêtes suivantes, comme dans l'exemple ci-dessous "open" qui navigue vers la page "/", l'id_session est ajouté à la commande afin que le serveur sache quelle fenêtre on pilote.

function getNewSession(callback) {
	var post_data = querystring.stringify({
		"cmd": "getNewBrowserSession",
		"1": "*firefox",
		"2": "adresse du serveur sur lequel le test doit être fait",
		"3": ""
	});

	send_request(post_data, function(result) {
		sessionId = result.split(',')[1];

		post_data = querystring.stringify({
			"cmd": "open",
			"1": "/",
			"2": "True",
			"3": "",
			"sessionId": sessionId
		});

		send_request(post_data, function(result) {
			/* result contient OK si tout s'est bien passé */
		});
	});

	post_req.write(post_data);
	post_req.end();
}

Dans le corps de la réponse, si tout s'est bien passé, le serveur nous retourne OK, suivi de l'ID de la session :

OK,e358efa489f58062f10dd7316b65649e

Après il suffit d'envoyer une par une nos étapes avec un callback pour passer à l'étape d'après :

var post_data = querystring.stringify({
	"cmd": cmd,
	"1": attribute || "",
	"2": value || "",
	"sessionId": sessionId
});

send_request(post_data, function(result) { });

A la fin de toutes les étapes, le test se termine. En fonction du résultat, l'ensemble du test est considéré concluant et l'affichage sur la page de monitoring indique qu'on peut pousser en prod.

Au passage, nous pouvons calculer la durée du test (et donc la rapidité du serveur), basculer sur une procédure alternative en fonction du résultat ou envoyer un mail d'alerte avec screenshot en cas d'erreur critique.

comments powered by Disqus