Réaliser des tests en navigateur avec Selenium sur Bash for Windows 10 [et sa démo en Ruby/Capybara]

Introduction

Windows 10 propose un Windows Subsystem for Linux (WSL) permettant d’obtenir un terminal Ubuntu sans émulation ni virtualisation. Il s’agit plutôt d’une couche de compatibilité permettant d’exécuter nativement des binaires Linux sous Windows, similaire à ce que fait Wine sous Linux.

En ces termes, vous comprenez rapidement l’intérêt du WSL : tous les outils de développement Unix s’ouvrent à vous sous Windows 10.

Nous allons utiliser Firefox pour exécuter nos scénarios. Récemment, Mozilla a conçu un “mode pilote automatique” pour Gecko (le moteur de rendu de Firefox) nommé Marionette.

Marionette se compose d’un serveur (intégré dans Firefox) et d’un client respectant la WebDriver API du W3C (geckodriver). Le client geckodriver est un exécutable distribué sur le net, indépendant de Firefox. Il s’utilise par API HTTP et est chargé d’envoyer au serveur Marionette, c’est à dire à Firefox, des actions à réaliser telles que cliquer sur des éléments de l’interface, lire des éléments du DOM etc. Marionette est utilisable dans Firefox 57+.

Voyons à présent comment développer des tests d’automation de navigateur Web à l’aide de Selenium, un framework disposant de bibliothèques en PHP, Python, Ruby, .NET, Perl et Java. Selenium 3 implémente un pilote pour geckodriver, nous n’aurons donc pas à utiliser l’API HTTP de Marionette manuellement.

Préparation de l’environnement

Première étape, suivez le guide d’installation du WSL sous Windows 10. À la fin du processus, vous disposerez d’un terminal Linux fonctionnel vous permettant de lancer tous les logiciels interagissant en ligne de commande.

Seconde étape optionnelle, installez le logiciel VcXsrv Windows X Server. C’est un serveur d’affichage X Window System (X11) pour exécuter des applications Linux graphiques sous Windows. À la fin du processus, vous serez en mesure d’ouvrir une application telle que Firefox. L’étape est optionnelle car Firefox dispose d’une option « headless » ne nécessitant pas d’affichage graphique pour fonctionner dans un terminal.

Troisième étape, installer Firefox : sudo apt-get install firefox. Il faut également ajouter export DISPLAY=:0 à la fin de ~/.bashrc ou ~/.zshrc afin que Firefox trouve le serveur d’affichage si vous envisagez d’utiliser VcXsrv.

Quatrième étape, rendez vous sur la page github du projet geckodriver de Mozilla et téléchargez la dernière version :

wget https://github.com/mozilla/geckodriver/releases/download/v0.21.0/geckodriver-v0.21.0-linux64.tar.gz
tar -xf geckodriver*
sudo mv geckodriver /usr/bin/

Exemple

Pour développer, Microsoft déconseille fortement d’aller modifier les fichiers sur le système de fichier Linux depuis un éditeur sous Windows, car il y a des risques de corruption du système de fichier.

À la place, il est recommandé de créer ses fichiers dans un sous dossier de /mnt/c/ (point de montage de la partition NTFS Windows). En clair depuis Windows, vous pouvez créer votre dossier projet sur le disque C: et l’ouvrir avec votre IDE.

Voici un exemple fonctionnel :

require 'selenium-webdriver' # Préalable : gem install selenium-webdriver

Selenium::WebDriver::Firefox.driver_path = "/usr/bin/geckodriver"
driver = Selenium::WebDriver.for :firefox

driver.navigate.to "http://www.google.com"
driver.quit

Placez ce code dans un fichier, appelez-le avec ruby et laissez la magie opérer. Vous êtes à présent en mesure de scripter la navigation d’un utilisateur sur le web.

Maintenant que le navigateur s’ouvre, nous allons utiliser un framework de tests automatiques dont le but est d’écrire des scénarios : Minitest. Nous allons aussi ajouter une surcouche à Selenium dont le but est de simplifier la navigation sur une page web : Capybara. Cerise sur la chantilly sur le gâteau, je vous rajoute de quoi prendre des captures d’écran, et en déclencher automatiquement en cas de failure. Vous allez voir, c’est amusant :

#!/usr/bin/env ruby
require 'selenium-webdriver'
require "minitest/autorun"
require 'capybara/dsl'
require 'capybara/minitest'
require 'capybara-screenshot/minitest'

puts "En cas d'erreur \"Selenium::WebDriver::Error::UnknownError: Process unexpectedly closed with status 1\", pensez à démarrer le serveur X.\n\r"

# Configuration de Selenium, si besoin
#Selenium::WebDriver::Firefox.driver_path = "/usr/bin/geckodriver" 
#Selenium::WebDriver::Firefox::Binary.path = "/chemin/si/vous/voulez/utiliser/une/version/portable" # Archives : https://ftp.mozilla.org/pub/firefox/releases/

# Configuration de Capybara
Capybara.register_driver :firefox do |app|
  Capybara::Selenium::Driver.new(
    app,
    browser: :firefox,
    desired_capabilities: Selenium::WebDriver::Remote::Capabilities.firefox(accept_insecure_certs: true),
    options: Selenium::WebDriver::Firefox::Options.new(args: ['-headless']) # À retirer pour voir Firefox s'animer sous vos yeux (nécessite un serveur graphique)
  )
end
Capybara.default_driver = :firefox
$folder_path = `pwd`.gsub("\n", "") + "/output"
Dir.mkdir $folder_path if not Dir.exist? $folder_path
Capybara.save_path = $folder_path
$launch_date = DateTime.now.strftime("%Y%m%d%H%M%S%10N")

# Configuration de Capybara Screenshot
Capybara::Screenshot.register_driver(:firefox) do |driver, path|
  driver.browser.save_screenshot(path)
end

class CapybaraTestCase < Minitest::Test
  include Capybara::DSL
  include Capybara::Minitest::Assertions
  include Capybara::Screenshot::MiniTestPlugin

  def setup
    Capybara.current_session.driver.browser.manage.window.resize_to(1024, 768)
    @file_name = "#{$launch_date}-#{self.name}"
    @file_path = "#{$folder_path}/#{@file_name}"
    Capybara::Screenshot.register_filename_prefix_formatter(:minitest) do |test_case|
      "#{@file_name}-failure"
    end
    Capybara::Screenshot.append_timestamp = false
  end

  def teardown
    Capybara.reset_sessions!
  end

  def test_google_fr
    visit('http://google.fr')
    save_screenshot("#{@file_path}.png") && save_page("#{@file_path}.html")
    assert_equal 1, 2
  end
end

J’ai mis beaucoup de configuration optionnelle afin de vous faire gagner du temps 🙂

Résultat :

Aller plus loin

Il existe un tas de possibilités avec ces outils. Voici quelques idées :

  • En prenant des captures d’écrans et le HTML sauvegardé lors de l’exécution des tests, il est possible de conserver les sorties d’une application web. En utilisant git pour les sauvegarder à un instant T, on peut alors s’assurer en lançant à nouveau les tests que les sorties sont les mêmes via la commande git diff (il faut pour cela retirer l’heure des noms de fichiers dans l’exemple ci-dessus). Cette technique est par exemple utile lors de montée de version de composants impactant une application web (BDD, serveur, version de framework, etc), en l’utilisant en mode « boite noire ».
  • Si les sorties changent, notamment le dump HTML, on peut avec git ajouter des scripts hooks pour nettoyer les balises affichant l’heure, un webservice externe, etc. afin de ne conserver que le contenu qui nous intéresse. On peut également faire la même chose à l’aide de Minitest.after_run en Ruby.
  • Sachant qu’on peut donc lancer les tests sans serveur X, on peut créer un cron faisant passer les tests chaque nuit dans un conteneur Docker ou une VM Linux, et ainsi prouver que l’application a fonctionné comme prévu chaque jour depuis la mise en place du cron.

Lorsque Firefox est lancé dans un environnement graphique virtuel (le mode headless), vous verrez que sa taille initiale est petite en réalisant des captures d’écran. J’attire votre attention car Capybara ne retournera aucun élément hors du champ de vision de l’utilisateur (éléments hidden ou uniquement visibles après un scroll).

Happy testing!

Laisser un commentaire