Testing automatisé avec Selenium et Python
Selenium : présentation de l'outil
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.
Créer un script Selenium
Enregistrement via Selenium IDE
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 :
- Se rendre sur la page d'accueil
- Saisir des données de recherche de formation en renseignant :
- Un domaine de formation
- Une localisation
- Valider la recherche
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>
Réaliser des tests automatisés : l'API Selenium
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.
Présentation du use case et des test cases
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 :

Une version simplifiée sous la forme de user-story pourrait être spécifiée de la sorte :
- EN TANT QUE
- Internaute
- 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.
Remarque 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 :
- ÉTANT DONNÉ QUE
- Je navigue sur la page d'accueil du site CESI
- 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 :
- ÉTANT DONNÉ QUE
- Je navigue sur la page d'accueil du site CESI
- 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.
Installation de Selenium et de l'API Python
Pour pouvoir exécuter nos tests, nous devons :
- Installer le serveur Selenium
- Installer l'API Python
La procédure d'installation est décrite sur la documentation de l'API Selenium Python.
Écriture du code de test
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.pyimport 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# -*- coding: UTF-8 -*- 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.pydef 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 :
- Modifier le code de l'application si c'est bien le résultat attendu qui doit être affiché
- Modifier le code de test s'il s'agit une erreur de test (faux négatif)
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…
Conclusion
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.
Très important La distinction testing fonctionnel vs. testing unitaire/d'intégration n'a finalement pas de sens : les tests unitaires et d'intégration sont en réalité des tests fonctionnels, car tout est fonction dans un système informatique ! Selenium et ses API permettent de mettre en œuvre ce principe fondamental.
Testez vos connaissances
- On peut automatiser des actions dans le navigateur dans réaliser de tests !
- Il n'y a pas de différence, ces concepts sont équivalents.
- L'automatisation de navigateur permet seulement d'éviter de réaliser des actions manuellement dans le navigateur, automatisation de tests va plus loins en confrontant les résultats observés à des résultats attendus.
- Oui
- Non
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).
- Il permet de simplifier l'écriture des scripts Selenium.
- Selenium est la seule manière d'écrire des scripts Selenium.
- L'enregistrement de macros y est simplifié dans le navigateur.
- Selenium IDE n'a aucun intérêt.
- C'est la seule manière de pouvoir utiliser l'API Selenium sous Python.
- Cela permet de mêler tests unitaires, tests d'intégration et tests d'interface.
- Cela permet de disposer des assertions et de l'une implémentation de xUnit pour exécuter des tests Selenium.
Que permet de réaliser la commande Selenium suivante (API Python) ?
self.driver.find_element_by_id('find_element_by_id')
- Cet énoncé est faux : on retrouve deux fois l'élément « find_element_by_id ».
- Cette instruction permet de récupérer l'élément du DOM ayant pour identifiant
find_element_by_id
. - Cette instruction permet de récupérer l'élément du DOM ayant pour classe CSS
find_element_by_id
.