Blog de développement

Sélénium avec Webdriver: Tronc commun

Dernière Modification le :
2000-01-01

On finit par se lasser de tous ces sites qui exigent une authentification complexe pour faire une opération anodine: EDF, impôts, banque, URSAFF, etc. Selenium est un excellent outil pour automatiser ce qui est répétitif au point d'être lui même répétitif. Concu pour automatiser les test, il existe aussi des automates ou automatismes pour automatiser la création de scripts Selenium!

Ce qui est toujours un peu la même chose fait partie d'un 'tronc commun' et chaque projet Sélenium reprend le tronc commun et est l'occasion de l'enrichir

Ce qui est très original, c'est la programmation assistée par CHAT-GPT. Ensuite, on met une gestion des dossiers, paramètres, paramètres de compte, du mot de passe, un log enrichi, dynamique et illustré, un système d'alerte

Tous les sites avec un 'mur' d'authentification presentent un intérêt: Banques , impôts (TVA...), lettres professionnelles ou confidentielles, Contrat Telecom, Wise, WebMail, Cotisations Sociales, UPS, LaPoste, Syndic, mes économies, Ameli, EdF, sites partenaires professionels, ENSAP (retraite), domiciliation entreprise, Chorus PRO, Pharmacie, etc. En bref, faites la liste de tous vos mots de passe, cela donne des idées.

Programmation assistée par ... CHAT-GPT

L'automatisation est sensible aux modifications éventuelles de l'interface EFI (grand public). Or, CHAT-GPT s'avère être un excellent assistant de programmation. On lui donne les éléments à identifier/documenter et il construit ou corrige les éléments de script. A chaque étape on prend soin d'enregistrer le code source des pages HTML et des captures d'écran, pour pouvoir, le cas échéant, soumettre des changements éventuels à notre nouvel assistant. Cette programmation 'assistée' permet d'aller plus avant et probablement de façon plus perenne que le mode Wizard de Selenium IDE.

Il n'est pas nécessaire de programmer ni se plonger dans la documentation!

La méthode est assez simple, voire un peu répétitive... On identifie la zone à documenter/cliquer. Avec click-droit on appèle 'inspecter' dans Firefox. On copie le OuterHTML de l'élémént. On le soumet à CHAT-GPT en lui donnant comme instruction d'écrire le script qui identifie, documente et clique

On n'échappe pas à PYTHON. VBS ou VBA ? Forget it: le webdriver n'est pas mis à jour. Python est beaucoup plus riche de bibliothèques au point qu'on rèverait d'un asssistant stagiaire, frais émoulu de l'école. Chat-GPT fait cela très bien et j'ai une liste de prompts prèts à l'emploi. Lire →.

Les prérequis les outils et mise à jour des outils

Il faut disposer d'un fichier Key:Valeurs que le script puisse interroger pour documenter la saisie. Il faut aussi avoir Python pour lequel les outils comme Selenium et le driver de Navigateur sont régulièrement mis à jour.

Le navigateur peut avoir été mis à jour, ce qui peut nécessiter une mise à jour de Selenium ou du driver (pour Firefox, c'est Gecko). Ce problème est plus général et je dispose d'un script de mise à jour: Lire →.

Pour Python et pip, une approche manuelle est souvent recommandée, car ces installations peuvent dépendre de configurations spécifiques à votre système. Pour les drivers, vous pouvez télécharger automatiquement les dernières versions avec des outils comme webdriver-manager, Lire → et Télécharger →

Principalement il existe 3 webdriver: Firefox autour de la machinerie Gecko, Edge et Chrome construits autour de Chromium.

Chromium dispose de plus de flags, ce qui le rend plus performant face à la complexité croissante de la sécurité. Lire → https://www.selenium.dev/documentation/webdriver/browsers/firefox/

https://www.selenium.dev/documentation/selenium_manager/

Prérequis: - Avoir Python installé - Avoir Selenium installé (pip install selenium) - Télécharger le WebDriver pour Firefox (GeckoDriver) et l'ajouter au PATH de votre système

Pour maintenir Selenium à jour après l'avoir installé avec pip install selenium, utilisez régulièrement la commande pip install --upgrade selenium dans votre terminal ou invite de commande. Cela vérifiera la version la plus récente disponible et la mettra à jour si nécessaire. De plus, gardez vos drivers de navigateurs (comme ChromeDriver ou GeckoDriver) à jour en téléchargeant les dernières versions depuis leurs sites officiels respectifs. Ou vous pouvez utiliser webdriver-manager, un outil installable via pip qui simplifie la gestion des drivers.

pip install webdriver-manager

Pour moi même, j'utilise Firefox et le WebDriver Gecko (uniquement) avec satisfaction.

Logiciel Action Site Manuel Téléchargement
Sélénium Automate Site →
DriverManager manager Site →
Les drivers ...
Pour FireFox
aka. GeckoDriver
Web driver Site →
Pour Chrome Web driver Site →
Pour Edge Web driver Site →

Paramètres du script, du compte, etc.

Tous les scripts commencent par l'importation des modules python. Puis vient la détermination du répertoire de travail (dossier_racine). Les dossiers Automate, Log, Paramètres, Captures, Archives sont définis par le script et créés éventuellement.

# Extrait uniquement le nom du fichier à partir du chemin
script_path = os.path.realpath(__file__)
script_name = os.path.basename(script_path)

# Définition du dossier racine comme le dossier contenant le script
dossier_racine = os.path.dirname(script_path)

Log et captures d'écran

A chaque étape, avant un click et après un click, je conserve une copie de la source HTML et Une capture d'écran. On peut ainsi aisément rejouer le film pour déboggage.

Mon script python efface les captures antérieures, mais la date de création du fichier, elle, persistera... Ce n'est pas trop gênant , mais bon... Il est possible que la date de création ne soit pas mise à jour pour refléter la dernière opération de création du fichier. Cela est dû à la façon dont Windows gère les métadonnées des fichiers. FSO, lui, gère cela très bien donc un nettoyage préliminaire est fait avec FSO/VBS

# Définir les chemins des dossiers et initialiser le compteur d'étape
step = 0
sDossier_captures_as_text = "E:\\...\\TVA\\Text_Captures"
sDossier_captures_as_PNG = "E:\\...\\TVA\\PNG_Captures"

def sauvegarder_capture_et_source(driver):
    global step  # Indiquer qu'on va utiliser et modifier la variable globale `step`
    step += 1  # Incrémenter l'étape
 
    step_normalise = str(step).zfill(2)
    chemin_capture_png = os.path.join(sDossier_captures_as_PNG, f"{step_normalise}_capture.png")
    chemin_capture_text = os.path.join(sDossier_captures_as_text, f"{step_normalise}_source.html")
    # Supprimer le fichier de capture s'il existe déjà
    try:
        if os.path.exists(chemin_capture_png):
            os.remove(chemin_capture_png)
    except Exception as e:
        print(f"Erreur lors de la suppression du fichier : {e}")
    time.sleep(5)
    driver.save_screenshot(chemin_capture_png)
    time.sleep(5)
    code_source_complet = driver.page_source
    with open(chemin_capture_text, 'w', encoding='utf-8') as fichier:
        fichier.write(code_source_complet)
https://automationtesting.in/taking-full-page-screenshot-in-selenium/ So, while capturing the screenshot in selenium 3 it will capture only the visible area of the web page in all the browsers including firefox

Le LOG dynamique

Il faut pouvoir suivre en temps réel ce qui se passe et le log doit être très verbeux pour aider au débugagge. On fait un log texte mais surtout un LOG en HTML avec un script d'auto-rafraichissement. De plus on agremente le log de captures d'écran.

def sauvegarder_capture_et_source(driver):
    global step, dossier_captures_as_PNG, dossier_captures_as_TEXT, dossier_captures_as_PNG_Log, dossier_captures_as_TEXT_Log # Indiquer qu'on va utiliser et modifier la variable globale `step`
    step += 1  # Incrémenter l'étape
    step_normalise = str(step).zfill(2)

    chemin_capture_png = os.path.join(dossier_captures_as_PNG, f"{step_normalise}_capture_{datestamp}.png")
    chemin_capture_text = os.path.join(dossier_captures_as_TEXT, f"{step_normalise}_source_{datestamp}.html")
    chemin_capture_png_Log = os.path.join(dossier_captures_as_PNG_Log, f"{step_normalise}_capture_{datestamp}.png")
    chemin_capture_text_Log = os.path.join(dossier_captures_as_TEXT_Log, f"{step_normalise}_source_{datestamp}.html")

    driver.save_screenshot(chemin_capture_png)
    time.sleep(1)
    try:
        # Vérifier si le fichier source existe avant de tenter de le copier
        if os.path.exists(chemin_capture_png):
            shutil.copy(chemin_capture_png, chemin_capture_png_Log)
            Log(f"Fichier copié de {chemin_capture_png} vers {chemin_capture_png_Log}")
            # Conversion du chemin Windows en chemin compatible avec une URL
            nom_image = f"{step_normalise}_capture_{datestamp}.png"  # Nom de l'image
            dossier_images = "PNG_Captures"  # Nom du sous-dossier contenant l'image
            # Utilisation d'un chemin relatif dans l'attribut src
            Log(f'<img src="{dossier_images}/{nom_image}" width="250">')
        else:
            Log(f"Le fichier source {chemin_capture_png} n'existe pas, impossible de copier.")
    except Exception as e:
        Log(f"Erreur lors de la copie du fichier : {e}")
    # On traite maintenant le code source
    code_source_complet = driver.page_source
    with open(chemin_capture_text, 'w', encoding='utf-8') as fichier:
        fichier.write(code_source_complet)

Il existe plusieurs autres méthodes pour conserver une trace du travail effectué avec WebDriver et enrichir la session :
Logs de navigateur: Selenium peut accéder aux logs du navigateur, ce qui peut être utile.

logs = driver.get_log('browser')
for log in logs:
    print(log)

Vidéo de la session: des outils tiers Selenium Grid, BrowserStack ou Sauce Labs enregistrent des vidéos des sessions.

Données de performance:utiliser les API de performance du navigateur : collecter des données sur les temps de chargement des pages, l'utilisation des ressources, etc.
performance_data = driver.execute_script("return window.performance.getEntries();")
print(performance_data)

Harvesting: Har files (HTTP Archive format, via extensions), informations détaillées sur chaque requête.

État des éléments de la page: sauvegarder l'état de certains éléments de la page à un moment donné.
element = driver.find_element(By.ID, "myElement")
state = element.get_attribute('innerHTML')
print(state)

Cookies: Pour des tests, sauvegarder les cookies de la session.
cookies = driver.get_cookies()
print(cookies)

Cache, Monolithe, écran complet et PDF

Imprimer une page en PDF, ou manipuler un bouton d'impression, sauvegarder une page Web complète (Monolith ou SingleFile), y compris son cache, ou en PDF avec Selenium WebDriver est un peu complexe car Selenium ne fournit pas ces fonctionnalités. d'où un billet de blog specifique...

Lire les parametres de compte et de script

Le schéma INI plus simple que JSON... Il est composé de sections, introduites par un titre entre crochets [], et de paires clé-valeur.

[ParametresCompte]
username = johndoe
language = fr
[Preferences]
notifications = on

J' utilise le module configparser pour lire un fichier INI :

# Créer une instance de ConfigParser
import configparser
config = configparser.ConfigParser()
config.read(chemin_fichier_config)
# Accéder aux valeurs
nom_utilisateur = config.get('ParametresCompte', 'nom_utilisateur')
utilisateur_email = config.get('ParametresCompte', 'utilisateur_email')
utilisateur_mot_de_passe = config.get('ParametresCompte', 'utilisateur_mot_de_passe')
email_superviseur = config.get('ParametresCompte', 'email_superviseur')
email_superviseur_password = config.get('ParametresCompte', 'email_superviseur_password')
numero_siren = config.get('ParametresCompte', 'numero_siren')

L'automate peut avoir à modifier le mot de passee. On le laisse le faire puis on enregistre. Idéalament on lancera une alerte. Pour des données sensibles, envisagez des approches plus sécurisées.


def update_password(ini_path, new_password):
    # Charger le fichier INI
    config = configparser.ConfigParser()
    config.read(ini_path)    
    # Mettre à jour le mot de passe dans la section appropriée
    config['UserSettings']['password'] = new_password    
    # Sauvegarder les modifications dans le fichier INI
    with open(ini_path, 'w') as configfile:
        config.write(configfile)
# Par exemple
update_password('path/to/config.ini', 'nouveau_mot_de_passe')

Alternativement, on peut gérer le mot de passe dans un fichier à part. On peut intégrer un gestionnaire de mots de passe via son API (LastPass, 1Password, etc.), avec leurs SDK ou APIs pour récupérer les mots de passe et les injecter dans les champs de formulaire.

Les projets difficiles voire impossibles

Cet article décrit les projets d'automation 'infaisables' Lire →. 1. CAPTCHA https://www.guvi.in/blog/how-to-automate-captcha-in-selenium/ Two-factor authentication Another scenario that you shouldn't automate through UI is two-factor authentication (or 2FA). This is where a one-time password is generated using "authenticator" mobile apps such as Google Authenticator or Microsoft Authenticator, and sent by SMS or email. It's a significant challenge to automate it in Selenium, but that doesn't mean it can't be done. Device authentication 4. File downloads : L'URL du fichier à télécharger peut parfois être 'cachée' dans uu javascript ougénéré à la volée: le bouton à cliquer pour le download marche assez bien. Si l'URL est exposée 5. HTTP response codes 6. Gmail, email, and Facebook logins

Gérer la navigation

Gérer les interruptions

plusieurs types d'interactions ou d'interruptions peuvent survenir lorsqu'on effectue des actions comme des clics 1. Alertes JavaScript (Alerts) Boîtes de dialogue de confirmation Boîtes de dialogue de prompt Fenêtres modales ou Pop-ups Nouveaux onglets ou fenêtres Cliquer sur certains éléments peut entraîner l'ouverture d'une nouvelle fenêtre ou d'un nouvel onglet.
					    
XXX