Cucumber version bash

Etat: en cours de rédaction

Introduction

Cette page donne une solution pour exploiter et traduire des scenarios au format Gherkin vers des commandes shell (selenium dans notre cas).

Nous prendrons comme exemple un fichier gherkin “.feature” et le traduirons en commande selenium.

Pour cela, nous aurons affaire à 3 scripts:
– un script “cucumber.sh” réutilisable à souhait;
– un script “seleneseBuildet.sh” pour construire des scenarios selenium au format HTML “selenese”;
– un script spécifique projet pour définir les règles de traduction souhaitées.

Ce qu’il faut retenir essentiellement, c’est que si vous souhaitez réutiliser le travail réalisé ici, il vous suffira d’écrire vos propres règles de traduction gherkin. Le reste est fourni sur un plateau.

Dans sa première version, le script “cucumber.sh” ne gère que les cas simples (pas de “scenario outline”, traduit par “scénario modèle”). Cependant, l’intégration complète de la syntaxe gherkin reste un objectif à atteindre au fil de mes besoins.

Code source

La parser cucumber

Il s’agit du script essentiel, qui va faire appel à vos règles de traduction de chaque clause (“step definitions”). C’est le moteur (engine), qui n’est qu’un outil de parcours (parsing) de vos scenarios gherkin.

Code source ici:

#!/bin/sh
stage=""

rules="./rules.sh"
output="./selenese.html"
logLevel="WARNING"
while getopts r:o:v o; do
  case "$o" in
  r) rules="$OPTARG";;
  o) output="$OPTARG";;
  v) logLevel="DEBUG";; 
  [?])	print >&2 "Usage: $0 [-v] [-o output] [-r rules] file ..."
    exit 1;;
  esac
done
shift "$((OPTIND - 1))"

. $rules

color()(set -o pipefail;"$@" 2>&1>&3|sed $'s,.*,\e[31m&\e[m,'>&2)3>&1
log()
{
  case $logLevel in
    "DEBUG")
      echo $@
      ;;
  esac
}
Log()
{
  color log $@
}

Start
while IFS='' read -r line || [[ -n "$line" ]]; do
    # echo "Text read from file: $line"
    firstWord=`echo $line | awk '{print $1;}'`
    case $firstWord in
      "Feature:")
        stage="_FEATURE_"
        # >&2 echo -en "\033[31m"
        Log "Feature :"
        feature="$(echo $line | cut -d " " -f 2-)"
        Log $feature
        Feature $feature
        # >&2 echo -en "\033[0m"
        ;;
      "Scenario:")
        stage="_SCENARIO_"  
        Log "Scenario:"
        scenario="$(echo $line | cut -d " " -f 2-)"
        Log $scenario
        Scenario $scenario
        ;;
      "Given")
        stage="_SCENARIO_GIVEN_"
        Log "Given:"
        given="$(echo $line | cut -d " " -f 2-)"
        Log $given
        Given $given
        ;;
      "When")
        stage="_SCENARIO_WHEN_"
        Log "When:"
        when="$(echo $line | cut -d " " -f 2-)"
        Log $when
        When $when
        ;;
      "Then")
        stage="_SCENARIO_THEN_"
        Log "Then:"
        then="$(echo $line | cut -d " " -f 2-)"
        Log $then
        Then $then
        ;;
      "And")
        case $stage in
          "_SCENARIO_GIVEN_")
            # echo "Given   : $line"
            given="$(echo $line | cut -d " " -f 2-)"
            Log $given
            Given $given
            ;;
          "_SCENARIO_WHEN_")
            when="$(echo $line | cut -d " " -f 2-)"
            Log $when
            When $when
            ;;
          "_SCENARIO_THEN_")
            then="$(echo $line | cut -d " " -f 2-)"
            Log $then
            Then $then
            ;;
        esac
        ;;
      *)
        case $stage in
          "_FEATURE_")
            feature="$(echo $line | cut -d " " -f 1-)"
            Log $feature
            Feature $feature
            ;;
        esac
        
        ;;
    esac
done < "$1"
Stop > $output

Le générateur de selenese

Il s’agit là d’une simple boîte à outil qui va générer du code HTML selenese, qui permet de définir un scénario selenium simplement en créant un tableau HTML.
NB: La documentation des commandes selenium au format selenese est très liée à celle de l’outil “selenium IDE”. Cependant, les commandes selenese peuvent être créées indépendemment de l’IDE et être ensuite exécutées via l’outil java en ligne de commande “selenese-runner”.

Code source ici:

#!/bin/sh

title="undefined title"

header=$(cat <<EOF
<?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://localhost/" />
<title>$title</title>
</head>
<body>
<table cellpadding="1" cellspacing="1" border="1">
<thead>
<tr><td rowspan="1" colspan="3">$title</td></tr>
</thead><tbody>
EOF
)

footer=$(cat <<EOF

</tbody></table>
</body>
</html>
EOF
)

Title() {
  title="$1"
}

Action()
{
  action=$(cat <<EOF

<tr>
	<td>$1</td>
	<td>$2</td>
	<td>$3</td>
</tr>
EOF
)
#   echo "$action"
  html=$(echo "$html$action")
}

SelenseInit()
{
  html=$(echo $header | sed "s/\$title/$title/g")
}

SeleneseClose()
{
  html=$(echo "$html$footer")
}

SeleneseDisplay()
{
  echo "$html"
}

Les commandes sont les suivantes:

  • initialisation du script selenese
  • paramétrage du titre
  • création d’une action
  • fermeture du script
  • récupération du script

Vos règles de traduction

Il s’agit là de définir en shell vos étapes (step definitions), de manière très comparable à ce qui peut être fait en ruby.
Exemple:
En ruby:

Given(/^We navigate to the homepage$/) do
  driver.navigate.to "http://mock.agiletrailblazers.com/"
end

When(/^We search for the word agile$/) do
  driver.find_element(:id, 's').send_keys("agile")
  driver.find_element(:id, 'submit-button').click
end

Then(/^The results for the search will be displayed$/) do
  wait = Selenium::WebDriver::Wait.new(:timeout => 5) # seconds
  begin
    element = wait.until { driver.find_element(:id => "search-title") }
    expect(element.text).to eq('Search Results for: agile')
  ensure
    driver.quit
  end
end

En shell:

#!/bin/sh

. ./seleneseBuilder.sh

Feature()
{
  : whatever
}

Scenario()
{
  : whatever
}
Given()
{
  : whatever
}

When()
{
  check="some action by \(.*\)"
  if echo $@ | grep -Gq "$check"; then
    user=$(echo $@ | sed "s/$check/\1/")
    Action open /basic/redirect "$user"
  fi
}

Then()
{
  if echo $@ | grep -Gq "some testable outcome is achieved"; then
    Action assert xxx "achieved"
  fi
}

Start()
{
  SelenseInit
}
Stop()
{
  SeleneseClose
  SeleneseDisplay
}

Exemple réel

Un cas réel consiste à intégrer le parser cucumber en bash dans une chaîne d’intégration continue.

  • Etape 1: Récuperation des “features” depuis un bug tracker via appel API REST.
  • Etape 2: Traduction des étapes (step definitions) en commande selenium (selenese)
    Exemple:

    cucumber.sh -v \
      -r ./rules.sh \
      -o ./selenese.html \
      ./my.feature
    
  • Etape 3: Execution via une grille (grid) selenium dans un environnement docker
    Architecture docker:

    • un front-end
    • un back-end
    • un hub selenium
    • un node chrome (headless)
    • un lanceur selenese-runner
  • Etape 4: Import des résultats dans le bug tracker
  • Etape 5: Monitoring des résultats des tests dans le bug tracker

Liens utiles

* Doc de référence de gherkin: https://docs.cucumber.io/gherkin/reference/
* Projet selenium IDE: https://github.com/SeleniumHQ/selenium-ide
* Doc selenese: https://www.seleniumhq.org/docs/02_selenium_ide.jsp
* Outil “selenese-runner”: https://github.com/vmi/selenese-runner-java
* grille selenium avec docker: https://github.com/SeleniumHQ/docker-selenium

Windows 10 servi sur un plateau

Introduction

Le but de cet article est de vous montrer que l’on peut installer un Windows prêt à l’emploi de manière complètement automatisée.

Il s’agit ici d’un Windows 10 en machine virtuelle (VM) fourni par MS pour tester leur navigateur. La licence permet d’utiliser cette VM pendant 90 jours sans interruption. Bien sûr, étant donné que l’on automatise tout, il vous suffira de tout jeter et de relancer l’installation pour revenir retrouver votre environnement.

Outils nécessaires

  • Une ligne de commande
  • Vagrant
  • VirtualBox

Parcours d’obstacles

Téléchargement

Tout d’abord, il faut récupérer la machine virtuelle fournie par MS: https://developer.microsoft.com/en-us/microsoft-edge/tools/vms/



Prenez un Windows 10, au format vagrant.

Il s’agit en fait d’une “box” vagrant, à la sauce “VirtualBox”. Si vous voulez utiliser un autre hyperviseur, il vous faudra passer par une conversion.

Décompression

Il vous faudra ensuite décompresser le fichier récupéré pour accéder à la box vagrant.

Configuration vagrant

C’est là que me sont apparues toutes les difficultés.

L’objectif est de pouvoir prendre la main sur cette VM avec winrm, pour ensuite passer le relai à ansible.

La VM fourni est doté d’un serveur OpenSSH qui embarque un shell minimaliste. Nous allons l’exploiter.

Ensuite, il faut permettre de basculer de ssh à winrm selon l’avancement de notre process.

Petite astuce importante, vagrant démarre “bash -l” comme shell par défaut, alors que c’est “sh -l” dont nous avons besoin ici.

Enfin, la seul chose à faire, c’est de basculer le réseau en mode “private” ou “work”, mais pas “public”. En effet, le service winrm est déjà en place, il faut seulement permettre de l’atteindre.

Vous pourrez alors passer le relai à ansible via winrm.

Voici le résultat:

# -*- mode: ruby -*-
# vi: set ft=ruby :

Vagrant.configure("2") do |config|
  config.vm.box = "msedgewin10"
  config.vm.box_url = "file://./MSEdge - Win10.box"
  config.vm.guest = :windows
  config.vm.synced_folder ".", "/vagrant", disabled: true
  config.vm.provider "virtualbox" do |vb|
    vb.gui = true
    vb.cpus = 1
    vb.memory = 2048
  end

  config.vm.network :forwarded_port, guest: 22, host: 2322, id: 'ssh'
  config.vm.network :forwarded_port, guest: 5985, host: 6085, id: 'winrm'
  config.vm.network :forwarded_port, guest: 3389, host: 3489, id: 'rdp'

  config.ssh.username = "IEUser"
  config.ssh.password = "Passw0rd!"
  config.ssh.insert_key = false
  config.ssh.sudo_command = ''
  config.ssh.shell = 'sh -l'
  config.winrm.username = "IEUser"
  config.winrm.password = "Passw0rd!"
  config.winrm.transport = :plaintext
  config.winrm.ssl_peer_verification = false

  config.vm.communicator = ENV['communicator'] || 'ssh'

  case config.vm.communicator
  when "ssh"
    puts('ssh communicator')
    config.vm.provision "shell",
      binary: true,
      privileged: false,
      inline: <<-SHELL
      set -x
      whoami
      # winrm - switch to private network
      /cygdrive/c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe \
        -InputFormat None \
        -NoProfile \
        -ExecutionPolicy Bypass \
        -Command 'Set-NetConnectionProfile -Name "Network" -NetworkCategory "Private"'
    SHELL
  else
    puts('winrm communicator')
    config.vm.provision "shell",
      privileged: false,
      inline: <<-SHELL
      echo hello world
    SHELL
    config.vm.provision "ansible" do |ansible|
      ansible.raw_arguments = ['-e', 'ansible_winrm_scheme=http']
      ansible.verbose = "vvv"
      ansible.playbook = "playbook.yml"
    end
  end
end

L’utilisation de ce Vagrantfile se passe en deux temps.

  • Initialisation de la VM, avec réglage de winrm:
    vagrant up
    
  • Passage de relai à ansible pour l’approvisionnement (provisioning) de la VM:
    communicator=winrm vagrant provision
    

A propos de la licence d’évaluation, Windows s’enregistre automatiquement pour une période de 90 jours.
Si toutefois vous voulez lancer explicitement l’opération, il s’agit de lancer la command VB suivante:

slmgr /ato

De VirtualBox vers Hyper-V

Introduction

Cet article montre comment convertir une VM de Virtualbox vers Hyper-V.
Pour exemple, on s’appuiera sur le cas d’un VM virtualbox “modern.ie” fournie par Microsoft pour la faire fonctionner sur Hyper-V.

Outils nécessaires

  • Une console
  • Virtualbox et sa commande VBoxManage
  • Hyper-V et commandes PowerShell associées

Conversion

Image originale

$ curl -LOk http://aka.ms/msedge.win10.vagrant
$ unzip *.zip

Import dans Virtualbox

vboxmanage import 'MSEdge - Win10.ova'
0%...10%...20%...30%...40%...50%...60%...70%...80%...90%...100%
Interpreting /vagrant/Projects/modern.ie_vagrant-win10-edge/MSEdge - Win10.ova...
OK.
Disks:
 vmdisk1 42949672960 -1 http://www.vmware.com/interfaces/specifications/vmdk.html#streamOptimized MSEdge - Win10-disk001.vmdk -1 -1

Virtual system 0:
 0: Suggested OS type: "Windows10_64"
 (change with "--vsys 0 --ostype <type>"; use "list ostypes" to list all possible values)
 1: Suggested VM name "MSEdge - Win10"
 (change with "--vsys 0 --vmname <name>")
 2: Number of CPUs: 1
 (change with "--vsys 0 --cpus <n>")
 3: Guest memory: 4096 MB
 (change with "--vsys 0 --memory <MB>")
 4: Sound card (appliance expects "", can change on import)
 (disable with "--vsys 0 --unit 4 --ignore")
 5: Network adapter: orig NAT, config 3, extra slot=0;type=NAT
 6: IDE controller, type PIIX4
 (disable with "--vsys 0 --unit 6 --ignore")
 7: IDE controller, type PIIX4
 (disable with "--vsys 0 --unit 7 --ignore")
 8: Hard disk image: source image=MSEdge - Win10-disk001.vmdk, target path=/home/vagrant/VirtualBox VMs/MSEdge - Win10/MSEdge - Win10-disk001.vmdk, controller=6;channel=0
 (change target path with "--vsys 0 --unit 8 --disk path";
 disable with "--vsys 0 --unit 8 --ignore")
0%...10%...

Conversion du format du disque dur

$ vboxmanage clonehd ~/VirtualBox\ VMs/MSEdge\ -\ Win10/MSEdge\ -\ Win10-disk001.vmdk MSEdge-Win10-disk.vhd -format vhd

Création de la VM Hyper-V

New-VM -Name Win10VM -MemoryStartupBytes 4GB -BootDevice VHD -VHDPath .\MSEdge-Win10-disk.vhd -Path .\VMData -Generation 1 -Switch DefaultSwitch

Export de la VM

Export-VM -Name Win10VM -Path .\

Préparation pour vagrant

Structure de répertoire

- VM/xxx.xml
- Hard drive/MSEdge-Win10-disk.vhd
- metadata.json

Contenu metadata

{
 "provider": "virtualbox"
}

Empaquetage

$ tar czvf VM msedgewin10.box

Utilisation avec vagrant

$ vagrant box add msedgewin10 msedgewin10.box

 

Liens utiles

  • https://docs.microsoft.com/en-us/windows-server/virtualization/hyper-v/get-started/create-a-virtual-machine-in-hyper-v
  • https://www.vagrantup.com/docs/boxes/format.html