Selenium est un outil d'automatisation de navigateur web. Il permet donc d'écrire, de manière plus ou moins assistée, des scripts dont l'exécution réalisera automatiquement des actions dans un navigateur web : visiter une page, cliquer sur un lien, remplir un formulaire, etc. et de récupérer les résultats de ces actions.
La manière la plus simple de créer des scripts Selenium est de les enregistrer à la manière de macros, via l'outil Selenium IDE, un plugin pour Firefox qui permet d'enregistrer les actions que vous réalisez dans le navigateurs.
Nous allons utiliser Selenium IDE pour réaliser quelques actions de navigation sur le site cesi.fr :
Au fur et à mesure de notre navigation sur le site, Selenium IDE enregistre les actions réalisées et les consigne dans son interface :
Notre macro est enregistrée ! Nous pourrons à présent la rejouer à l'infini. Du reste, elle est stockée en interne sour la forme d'une source XHTML :
Source XHTML
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">
<head profile="http://selenium-ide.openqa.org/profiles/test-case">
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
<link rel="selenium.base" href="http://cesi.fr/" />
<title>New Test</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">New Test</td></tr>
</thead><tbody>
<tr>
<td>open</td>
<td>/</td>
<td></td>
</tr>
<tr>
<td>select</td>
<td>id=form_1579</td>
<td>label=Informatique et systèmes d'information</td>
</tr>
<tr>
<td>select</td>
<td>id=form_1582</td>
<td>label=Arras</td>
</tr>
<tr>
<td>clickAndWait</td>
<td>css=input[type="submit"]</td>
<td></td>
</tr>
</tbody></table>
</body>
</html>
D'aucuns se seront dit à la lecture du début de ce tutoriel : « OK, c'est sympa, mais tout ceci ne permet pas d'automatiser des tests : même si le navigateur réalise des actions par lui même, il faut quelqu’un derrière l'écran pour vérifier que les résultats obtenus sont conformes aux attentes ». Certes !
C'est à ce moment qu'intervient l'API Selenium : nous allons, en écrivant du code de test, automatiser le navigateur et les vérifications associées.
Note Selenium peut être automatisé dans beaucoup de langage différents grâce à des API dédiées : Java, PHP, Python… Pour cette démonstration, nous allons travailler en Python.
Nous allons ici nous attarder sur le use case « Rechercher une formation ». Ce use case peut être formalisé de la manière suivante en UML :
Le cas d'utilisation « Rechercher une formation du CESI »
Une version simplifiée sous la forme de user-story pourrait être spécifiée de la sorte :
JE SOUHAITE Pouvoir recherche une formation du CESI sur des critères de domaine de formation, durée, localisation et nature
AFIN DE Être en mesure d'optimiser mon parcours utilisateur sur le site CESI en trouvant plus rapidement une formation répondant à mes attentes.
Notez la puissance d'expression plus importante d'UML, qui nous permet de préciser le caractère obligatoire (stéréotype include
) ou facultatif (extend
) des use cases associés.
De ce use case découlent un nombre important de test cases (nombre de domaines de formation × nombre de durées+1 × nombre de localisations+1 × 2, soit en l'espèce 4466 tests !). Nous n'allons bien entendu pas tester tous ces cas… Nous allons plutôt commencer par tester le cas général.
Un exemple de cas de test est le suivant, exprimé ici en syntaxe Gherkin :
QUAND Je saisis une formation dans le cadre de recherche « Former ses équipes »
ET QUE Je clique sur le bouton « Valider »
ALORS Une page de résultats de recherche p
apparait
ET Cette page p
porte le titre « Votre Résultat, votre recherche »
Un autre exemple de cas de test consiste à vérifier que si un pré-requis n'est pas satisfait, la fonctionnalité réagit correctement :
ET QUE Je n'ai sélectionné aucun critère dans le cadre de recherche « Former ses équipes »
QUAND Je clique sur le bouton « Valider »
ALORS Un message d'erreur « Vous devez sélectionner au moins un critère de recherche » est affiché
Nous allons à présent implémenter les test automatisés permettant d'exécuter ces test cases.
Pour pouvoir exécuter nos tests, nous devons :
La procédure d'installation est décrite sur la documentation de l'API Selenium Python.
La grande force de la solution que nous allons mettre en place est que nos tests fonctionnels vont s'intégrer complètement dans la solution de testing unitaire et d'intégration de l'application. Autrement dit, nous allons utiliser la même structure de tests que pour notre tests techniques (unitaires et d'intégration).
Nous commençons donc par créer notre classe de test :
tests.py
import unittest
from selenium import webdriver
class TestCesiSearch(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
def tearDown(self):
self.driver.close()
if __name__ == '__main__':
unittest.main()
Nous avons pris soin d'importer le framework unittest
de Python afin de disposer de la classe TestCase
et du test runner. Nous avons également importé le webdriver
fourni par Selenium, qui va nous permettre d'automatiser le navigateur. Les méthodes setUp()
et tearDown()
d'xUnit sont implémentées pour initialiser notre driver avant de lancer les tests et le fermer une fois terminé.
OK, nous sommes à présent fin prêts pour écrire notre code de test.
Astuce Il est possible d'exporter un script directement depuis Selenium IDE, via le menu « Fichier > Exporter le test sous… ».
tests.py
import unittest
from selenium import webdriver
class TestCesiSearch(unittest.TestCase):
def setUp(self):
self.driver = webdriver.Firefox()
def test_results_page_shows(self):
self.driver.get("http://www.cesi.fr")
select = Select(self.driver.find_element_by_id('form_1579'))
select.select_by_value("Informatique et systèmes d'information")
btn = self.driver.find_element_by_css_selector("input[type="submit"]")
btn.click()
page_url = self.driver.current_url
page_title = self.driver.find_element_by_css_selector(".txtblancRechercheTitre").text
self.assertEqual(page_url, 'http://www.cesi.fr/recherche-formulaire-portail.asp')
self.assertEqual(page_title, u'VOTRE RÉSULTAT, VOTRE RECHERCHE')
def tearDown(self):
self.driver.close()
if __name__ == '__main__':
unittest.main()
Notre méthode de test test_results_page_shows()
a un contenu relativement simple à comprendre : nous chargeons la page désirée, ciblons les éléments qui nous intéressent, cliquons où il faut, puis nous terminons par deux assertions pour vérifier que nous sommes atterris sur la bonne page, et que le titre est celui attendu.
Lançons à présent nos tests :
$ python test.py
.
----------------------------------------------------------------------
Ran 1 test in 21.666s
OK
OK, tout va bien, notre test passe ! Passons au second test, qui testera qu'une recherche sans critère abouti bien à l'affichage d'un message d'erreur.
tests.py
def test_no_criteria_implies_error_message(self):
self.driver.get("http://www.cesi.fr")
btn = self.driver.find_element_by_css_selector("input[type="submit"]")
btn.click()
page_url = self.driver.current_url
error_message = self.driver.find_element_by_css_selector(".listeDomainesFormation li").text
self.assertEqual(page_url, 'http://www.cesi.fr/recherche-formulaire-portail.asp')
self.assertEqual(error_message, u'Vous devez selectionner au moins 1 critère de recherche')
Relançons nos tests :
$ python test.py
F.
======================================================================
FAIL: test\_no\_criteria\_implies\_error\_message (\_\_main\_\_.TestCesiSearch)
----------------------------------------------------------------------
Traceback (most recent call last):
File "test.py", line 33, in test\_no\_criteria\_implies\_error\_message
self.assertEqual(error\_message, u'Vous devez selectionner au moins 1 critère de recherche')
AssertionError: u'Vous devez selectionner au moins un crit\\xe8re de recherche' != u'Vous devez selectionner au moins 1 crit\\xe8re de recherche'
- Vous devez selectionner au moins un crit\\xe8re de recherche
? ^^
+ Vous devez selectionner au moins 1 crit\\xe8re de recherche
? ^
----------------------------------------------------------------------
Ran 2 tests in 19.767s
FAILED (failures=1)
Hum, le premier test est toujours OK, mais une erreur se produit sur le second test : le résultat obtenu n'est pas conforme au résultat attendu ! Deux solutions :
En l’occurrence, il s'agit d'une erreur de test : modifions notre code de test :
self.assertEqual(error_message, u'Vous devez selectionner au moins un critère de recherche')
Relançons nos tests :
$ python test.py
..
----------------------------------------------------------------------
Ran 2 tests in 19.846s
OK
Nos deux tests ont bien été exécutés, et à présent tout le monde est OK, tout va bien…
Nous avons vu dans ce tutoriel comment mettre en œuvre des tests fonctionnels automatisés grâce à Selenium et son API webdriver
, notamment en langage Python.
La grande force du testing fonctionnel automatisé couplé avec un framework de test technique (comme unittest en Python, PHPUnit en PHP, jUnit en Java…) est qu'il n'existe plus de frontière stricte entre tests fonctionnels et tests unitaires, tests d'intégration, etc.
Quelle est la différence entre l'automatisation de navigateur (browser automation) et l'automatisation de tests ?
L'industrialisation des tests passe-t-elle systématiquement par l'automatisation ?
Industrialiser ne peut pas systématiquement dire automatiser : un process peut être industrialisé (formalisé, normé, documenté, contrôlé, etc.) en restant manuel (exemple d'une usine d'assemblage manuel de pièces détachées).
Quel est l'intérêt de l'outil Selenium IDE ?