Blog ENI : Toute la veille numérique !
Offre estivale️ ☀️ : de -20% à -30% sur les livres en ligne et vidéos, avec le code PLAGE Cliquez ici !
Accès illimité 24h/24 à tous nos livres & vidéos ! 
Découvrez la Bibliothèque Numérique ENI. Cliquez ici

Les waits pour une interaction optimale

Latences d’une application Web

Comme nous avons pu le remarquer au chapitre Les captures et validations des données, les traitements sur une page HTML ne sont pas toujours instantanés. La latence entre le lancement de la modification et la modification réelle de la page, si petite soit-elle, ne permet pas à Selenium d’enchaîner les actions sur une page comme le ferait un être humain.

Jusqu’à présent, nous avons utilisé la librairie time de Python pour gérer cette latence. Nous mettons en pause le script avec la méthode sleep pendant quelques secondes. Cela fonctionne, mais il s’agit d’une des pires pratiques possibles pour développer des tests fonctionnels.

Effectivement, sommes-nous sûrs que notre attente n’est pas trop courte ? ou trop longue ? L’exécution des tests fonctionnels d’une application Web prend déjà suffisamment de temps, nous ne devons donc pas le rallonger avec des pauses trop longues dans les tests.

Pour résoudre ce problème de latence sans avoir à calculer une pause exacte nous-mêmes, Selenium propose plusieurs solutions dont la première est les implicits waits.

Implicit waits

Pour vulgariser, un implicit wait revient à appeler la méthode sleep de Selenium. Il indique au driver Web d’attendre un certain laps de temps, en secondes, avant de continuer ses émulations sur l’instance du navigateur lancée. 

La syntaxe pour créer un implicit wait est la suivante :

browser.implicity_wait(dureeEnSeconde) 

Modifions notre test sur la table dynamique du chapitre Les captures et validations de données, en remplaçant notre pause par un implicit wait.

import pytest  
from selenium import webdriver  
from selenium.webdriver.common.by import By  
from selenium.webdriver.common.keys import Keys  
from selenium.webdriver.common.action_chains import ActionChains  
   
@pytest.fixture  
def browser() :  
    from selenium import webdriver  
    browser = webdriver.Chrome()  
    browser.get("http://localhost:8888/chapitre8.html")  
    def finalizer():  
          browser.close()  
          browser.quit()  
    return browser  
   
def testFilterByID(browser) :  
    filtreInput  = browser.find_element(By.XPATH, 
"/html/body/div[1]/div[1]/div/input")  
    actions...

Excepted conditions

Il nous paraît logique de pouvoir transcrire la phrase suivante avec Selenium : « Attends que la table ait fini de charger les données pour continuer » . Il s’agit bien d’une condition : la table doit avoir chargé les données.

De telles conditions en Selenium sont appelées les excepted conditions. Selenium nous permet de ne pas avoir à redéfinir les conditions d’attente courantes : est-ce que l’élément Web existe, est-il obsolète, est-il visible, le texte d’un élément Web contient-il xxx, etc. ?

Pour les utiliser, nous devons commencer par les importer :

from  selenium.webdriver.support import expected_conditions as EC 

Voici une liste non exhaustive des expected conditions fournies par Selenium :

  • alert_is_present() : une attente pour vérifier si une alerte est affichée sur la page courante ;

  • element_to_be_clickable(elementWeb) : une attente pour vérifier que l’élément Web passé en paramètre est actif et cliquable ;

  • element_to_be_selected(elementWeb) : une attente pour vérifier que l’élément Web passé en paramètre est bien sélectionné ;

  • invisibility_of_element(elementWeb) : une attente pour vérifier que l’élément Web passé...

Explicit waits

Les explicit waits ne définissent pas une attente avec un laps de temps donné, comme les implicit waits, mais demandent au driver Web d’attendre une certaine condition pour continuer les tests.

Leur import est nécessaire pour les utiliser :

from selenium.webdriver.support.wait import WebDriverWait 

Un explicit wait se déclare de la manière suivante, le paramètre timeout étant optionnel :

wait = WebDriverWait(driverWeb, timeout=2) 

Le paramètre timeout est une sécurité des explicit waits : si la durée timeout est dépassée, Selenium estime que l’attente ne se finira pas et soulève une exception.

Une fois un explicit wait déclaré, la mise en attente des instructions se fait grâce à l’appel de la méthode until().

1. Avec les excepted conditions

Créons une page HTML avec un script JavaScript qui va retarder l’affichage d’un titre sur la page.

<!DOCTYPE html>  
<html>  
<head>  
    <meta charset="utf-8">  
    <meta name="viewport" content="width=device-width, 
initial-scale=1">  
    <title>Chapitre 9 : Waits pour une interaction optimale</title> 
</head>  
<body id="body">  
    <script type="text/javascript">  
          function create() {  
                 var h1 = document.createElement("h1");  
                 var content = document.createTextNode("Chapitre 9 : 
Waits pour une interaction  optimale");  
                 h1.appendChild(content);  
                 document.getElementById("body").appendChild(h1);  
          }  
          setTimeout(create, 2000);  
    </script>  
</html> 

Nous intégrons à...

Fluent waits

Un fluent wait permet de créer notre propre explicit wait en lui indiquant les exceptions à ignorer (paramètre ignored_exceptions), l’intervalle de temps entre deux appels de la méthode __call__ ou de la lambda (paramètre poll_frequency), en plus du paramètre timeout.

Avec Python, rien de plus simple, tout se passe dans le constructeur du wait :

erreursAIgnorer = [NoSuchElementException, 
ElementNotInteractableException]  
wait = WebDriverWait(browser, timeout=2, poll_frequency=.2, 
ignored_exceptions= erreursAIgnorer) 

Synthèse sur les waits

Nous vous conseillons d’utiliser :

  • l’implicit wait lorsqu’un délai fixe en secondes est nécessaire pour une manipulation du DOM et même dans ce cas, vous devez pouvoir utiliser un explicit wait à la place pour obtenir une attente optimale et ne plus dépendre du timer de la page ;

  • l’explicit wait avec une lambda pour vérifier une attente sur un élément HTML qui existe déjà dans la page au chargement ;

  • l’explicit wait avec les excepted conditions dans tous les autres cas.

Stratégie de chargement de la page

Une option du driver Web de Selenium peut jouer les latences du chargement des éléments Web de la page HTML visitée : on parle de stratégie de chargement de la page.

Il existe trois stratégies de chargement de page :

  • Normal (par défaut) : le driver Web attend que la page HTML soit entièrement chargée, c’est-à-dire la fin du chargement du document DOM ;

  • Eager : le driver Web attend que la page HTML soit entièrement chargée et analysée, c’est-à-dire jusqu’à l’événement DOMContentLoaded ;

  • None : le driver Web n’attend pas que le téléchargement de la page HTML soit fini, aucun événement DOM sur cette page n’est écouté.

Si nous souhaitons modifier cette stratégie, il nous suffit de définir l’option page_load_strategy du driver Web.

from selenium import Webdriver  
from selenium.webdriver.common.by import By  
from selenium.webdriver.common.action_chains import ActionChains  
   
options = webdriver.ChromeOptions()  
options.page_load_strategy = 'normal'  
options.page_load_strategy = 'eager'  
options.page_load_strategy = 'none'  
   
browser = webdriver.Chrome(options=options)  
   
browser.close() 

Nous...

Passons aux vrais tests fonctionnels

Maintenant que nous connaissons les principales directives et instructions de Selenium ainsi que le fonctionnement du chargement des pages et des éléments Web, nous pouvons enfin dépasser le stade des tests fonctionnels pédagogiques !

1. Application multipage

a. Description de l’application

L’application à tester est assez simple : il s’agit d’un formulaire de connexion, initialement présenté en français. En cliquant sur le bouton English, l’utilisateur peut traduire les labels du formulaire en anglais et le texte du bouton change en Français pour faire une traduction en français.

Voici le code HTML de notre page d’accueil :

<html>  
<head>  
    <title>Chapitre 9 - Application multi-pages</title>  
</head>  
<body>  
    <h1>Chapitre 9 - Application multi-pages</h1>  
    <h2>Traduction et envoi d'un formulaire HTML</h2>  
    <button id="lang">English</button>  
    <form id="formulaire" action="connexion.html" 
onsubmit="return false;">  
          <label id="maillabel">Email</label><input id="email" 
name="email" type="email"/><br/>   
          <label id="passlabel">Mot de passe</label><input id="pass" 
name="pass" type="password" required/><br/>  
          <label id="mrlabel">Monsieur</label><input type="checkbox" 
id="mr" name="mr"  
          value="monsieur" checked>  
         <label id="mmelabel">Madame</label><input type="checkbox" 
id="mme" name="mme" value="madame">  
         <label id="namelabel">Nom</label><input type="text" 
id="name" name="name"><br/>...

Exercices

Les exercices suivants sont la suite de l’exercice du chapitre Les interactions utilisateur. Pour rappel, il s’agit de tester le comportement d’un formulaire d’enregistrement d’un compétiteur. Vous devez reprendre les tests de cet exercice pour obtenir des tests fonctionnels complets.

1. Exercice 1 

Notre première extension de cette page HTML est l’ajout d’une page affichant la liste des compétiteurs. Cette page est automatiquement affichée après la validation du formulaire.

Voici le code de cette page :

<!DOCTYPE html>  
<html>  
<head>  
    <meta charset="utf-8">  
    <meta name="viewport" content="width=device-width, 
initial-scale=1">  
    <title>Liste des compétiteurs</title>  
</head>  
<body>  
    <h1>Liste des compétiteurs</h1>  
    <table id="table">  
          <tbody>  
                 <tr>  
                     <td>Eric</td><td>Dupied</td><td>30</td> 
<td>50</td>  
                 </tr>  
                 <tr>  
                     <td>Manon</td><td>Sources</td><td>25</td> 
<td>42</td>  
                 </tr>  
                 <tr>  
                     <td>Gilles</td><td>Trouvetout</td><td>82</td> 
<td>110</td>  
                ...