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

Leave a Reply

Your email address will not be published. Required fields are marked *