Robot Framework, de v3 à v7, correction d’une obsolescence critique

Pourquoi l’obsolescence est-elle devenue critique ?

Robot Framework v3 est sorti le 31 décembre 2015, et est compatible avec les versions de Python qui existaient à ce moment-là, à savoir Python 2.6, 2.7 et 3.3. Il s’agissait de la première version de Robot Framework à être compatible avec Python 3.

Depuis, il y a eu beaucoup de nouvelles versions de Python et Python 2.x et 3.3 ne sont plus du tout supportées. Pour pouvoir mettre à jour Python afin que les tests puissent être lancés sur des machines plus récentes, il faut d’abord mettre Robot Framework à jour afin que la version du Framework soit compatible avec celle du langage.

Lors d’une récente expérience, Robot v3 était installé avec Python 3.5. nous avons décidé de passer Robot dans la dernière version disponible, la version 7.

Avec Python 3.5, il est déjà possible de passer en Robot v4, ce qui constitue notre première étape. Cependant, Python 3.5 n’est plus supporté non plus et il faut le mettre à jour également. La version 3.11 de Python étant supportée jusqu’à fin 2027, elle conviendra très bien. Robot Framework v7 est également compatible avec Python 3.11 et il s’agit de la version majeure la plus récente du Framework.

L’objectif est de montrer et d’expliquer la migration de v3 à v4, puis de v4 à v7.

Pourquoi choisir Robot Framework ?

Robot Framework est un framework Python open-source, destiné au développement de tests end-to-end automatisés. Contrairement à d’autres frameworks de test basés sur le code, Robot Framework utilise une syntaxe proche de l’anglais, ce qui le rend facilement lisible par les non-développeurs. Il promeut également une approche basée sur des mots-clés, pouvant être créés et nommés manuellement, permettant aux non-développeurs de maintenir les tests en utilisant uniquement les mots clés personnalisés. Il est aussi possible de créer des librairies Python pour étendre les fonctionnalités de Robot Framework. Le framework est maintenu par la communauté et mis à jour régulièrement.

Il est également possible d’enregistrer les rapports de tests sous Allure, Grafana ou encore TestLink, qui sont des solutions open-source permettant d’avoir des métriques sur le déroulement des tests en temps réel. Grâce à ces métriques, il est possible de montrer à n’importe qui (développeur, quality analyst, chef de projet, etc…) si les fonctionnalités demandées sont bien implémentées, sans avoir à parler de code.

Exemple de code Robot Framework basique (v3)

Afin d’avoir un exemple pour la mise à jour, voici un code Robot Framework développé en version 3

| *** Setting ***
| Documentation  | A test suite for valid login


| Library        | SeleniumLibrary | WITH NAME | Selenium


| Suite Setup    | Go To Login Page

| Suite Teardown | Log Out


| Force Tags     | login


| *** Variable ***

| ${ROOT_PAGE} =      | http://localhost:80

| ${LOGIN_PAGE} =     | ${ROOT_PAGE}/login

| ${LOGIN_BUTTON} =   | “xpath://span[text()=‘Login’]/parent::button”

| ${LOGOUT_BUTTON} =  | “xpath://span[text()=‘Logout’]/parent::button”

| ${USERNAME_FIELD} = | “xpath://span[id=’username’]”

| @{LOGIN_LIST} =     | testuser | testadmin

| @{PASSWORD_LIST} =  | testuserpassword | testadminpassword

| @{USER_LIST} =      | User Test | Admin Test

| *** Test Case ***
| Login With User Password For All Users

| | ${list_length} = | Get Length | @{LOGIN_LIST}

| | :FOR | ${login_index} | IN RANGE | 0 | ${list_length}
| | \ Login User | @{LOGIN_LIST}[${login_index}] | @{PASSWORD_LIST}[${login_index}]
| | \ Check Login | @{USER_LIST}[${login_index}]

| | \ Run Keyword If | “@{USER_LIST}[${login_index}]” == “Admin Test” | Log To Console | Admin was logged in | ELSE | Log to Console | Admin was not logged in

| | \ Log Out
| | [Teardown] | Log Out

| Denied Login With Wrong Password
| | [Tags] | loginFailed
| | Run Keyword And Expect Error | * | Login User | testuser | wrongpassword
| | Check Not Logged In
| | [Teardown] | Close Server Connection

| *** Keyword ***
| Go To Login Page
| | Go To | ${LOGIN_PAGE}

| Login User

| | [Arguments] | ${login} | ${password}

| | Click Button | ${LOGIN_BUTTON}


| Log Out
| | Click Button | ${LOGOUT_BUTTON}

| | Go To Login Page


| Get User Name

| | # Get User Name from navbar

| Check Login
| | [Arguments] | ${expected_username}
| | ${username} = | Get User Name
| | Should Be Equal | ${expected_username} | ${username}

| Check Not Logged In
| | Run Keyword And Expect Error | PermissionError* | Get Server Version

Une étape complexe mais nécessaire - Mise à niveau v4

Plusieurs choses ont changé en Robot v4, mais deux changements sont considérés comme des breaking changes.

  • Les boucles FOR ont changé de notation
  • L’accès aux valeurs d’une list se fait différemment

Avant de parler des breaking changes, parlons d’abord des autres changements, qui se sont malgré tout avérés complexes à modifier dans certains cas.

Tout d’abord, Robot Framework v4 a apporté un nouveau statut SKIP qui permet de ne pas exécuter un test case dans certains cas sans pour autant l’inscrire comme PASSED ou FAILED

Ensuite, le Run Keyword If est devenu IF, les deux notations sont encore valides mais Run Keyword If est indiqué comme bientôt obsolète, donc il a été décidé de prévoir la dépréciation dès cette version là.

Notre Run Keyword If finit par ressembler à ça :

| | \ IF | “@{USER_LIST}[${login_index}]” == “Admin Test” | Log To Console | Admin was logged in | ELSE | Log to Console | Admin was not logged in

Il est aussi possible d’avoir le IF sur plusieurs lignes pour avoir une syntaxe similaire à d’autres langages (on le considère comme hors de la boucle FOR ici pour des raisons de simplicité).

| | IF | “@{USER_LIST}[${login_index}]” == “Admin Test” | | | Log To Console | Admin was logged in | | ELSE | | | Log to Console | Admin was not logged in | | END

Il y avait beaucoup de modifications à faire pour cette nouvelle notation, donc il a été décidé de n’en faire qu’une partie et de faire le reste petit à petit.

Nouvelles Boucles FOR, plus lisibles mais impactant

Passons désormais aux breaking changes.

Premièrement, la notation des boucles FOR a changé, ce qui nous a imposé de vérifier toutes les boucles présentes dans le code. Si l’on se réfère à l’exemple présent au-dessus, la boucle devient :

| | FOR | ${login_index} | IN RANGE | 0 | ${list_length} | | | Login User | @{LOGIN_LIST}[${login_index}] | @{PASSWORD_LIST}[${login_index}] | | | Check Login | @{USER_LIST}[${login_index}] | | | IF | “@{USER_LIST}[${login_index}]” == “Admin Test” | Log To Console | Admin was logged in | ELSE | Log to Console | Admin was not logged in | | | Log Out | | END

Notez la suppression des anti-slash et la présence du END à la fin de la boucle, qui existait déjà mais, n’étant pas nécessaire avant cette version, n’était pas présent partout dans le code. L’intérieur de la boucle a également été indenté pour respecter cette nouvelle notation.

Accès aux listes, peu visible mais bloquant

Il y a aussi eu un autre breaking change beaucoup plus difficile à gérer, car très discret. Il s’agit de l’accès aux valeurs d’une liste, qui passe de @{USER_LIST}[...] à ${USER_LIST}[...]. Bien qu’il est précisé dans la Release de Robot v4 qu’il s’agit d’un breaking change, l’ancienne notation reste valide mais n’a plus tout à fait le même comportement. On se retrouve donc avec des tests qui se lancent bien, et arrivé à ce changement précis, Robot nous renvoie une exception “USER_LIST is not list or list-like”. En fait, la notation avec le @, depuis la mise à jour, ne retourne plus la liste mais l’objet en lui-même, ce qui ne permet pas d’accéder à son contenu. Il faut désormais utiliser le $. Beaucoup de tests se sont retrouvés avec ce problème et il a été difficile de savoir si l’on en avait fini ou non avec cette modification. Il est d’autant plus complexe de détecter cette erreur quand les tests concernés étaient déjà en statut FAILED pour d’autres raisons, ce qui ne montrait pas le problème tout de suite.

Une étape plus simple mais très utile - Mise à niveau v7

Le passage de la version 4 à la version 7 s’est passé sans encombre, mais a tout de même apporté un changement important afin de simplifier la lecture du code.

Grâce à la version 7 de Robot Framework, il est désormais possible de déclarer un variable (globale ou non) grâce au mot-clé VAR. Avant, pour déclarer une variable globale, il fallait écrire

| | ${variable_name} = | Set Variable | Text | | Set Global Variable | ${variable_name}

Maintenant, il suffit d’écrire

| | VAR | ${variable_name} | Text | scope=GLOBAL

Parmi les autres changements qui méritent un attention particulière, on note :

  • Python 2 n’est plus supporté depuis Robot v5
  • Force Tags devient Test Tags
  • WITH NAME devient AS
  • Les noms de sections comme *** Test Case *** doivent obligatoirement être mis au pluriel
  • Il est désormais possible d’écrire des boucles WHILE qui suivent la même structure que celles des autres langages
  • BREAK et CONTINUE apparaissent et sont aussi utilisables dans une boucle FOR pour, respectivement, sortir de la boucle, ou passer à l’itération suivante

Un autre changement intéressant bien que n’impactant aucunement le code, est la manière dont les rapports sont générés, avec des logs plus précis et un mode sombre inclus

Prochaine étape - le nouveau breaking change en version 7.3

Bien que la version 7.3 ne soit pas encore sortie, il est prévu de déprécier la notation séparée par des pipes lors de celle-ci, pour la supprimer en version 8 ou 9. Afin de pallier ce changement, il va falloir commencer dès à présent à changer le code et à utiliser la notation séparée par des espaces, ce qui implique d’avoir au moins deux espaces pour montrer la séparation entre les arguments. Le Style Guide officiel de Robot Framework conseille de mettre au moins 4 espaces pour augmenter la lisibilité. Par exemple, notre boucle FOR ressemblerait à ça (pour des raisons de lisibilité, la boucle FOR est considérée comme non-indentée)

FOR    ${login_index}    IN RANGE    0    ${list_length}     Login User    @{LOGIN_LIST}[${login_index}]    @{PASSWORD_LIST}[${login_index}] Check Login    @{USER_LIST}[${login_index}]     IF    “@{USER_LIST}[${login_index}]” == “Admin Test” Log To Console    Admin was logged in ELSE Log to Console    Admin was not logged in END     Log Out END

Code final

Voici le code final obtenu avec la version 7 de Robot Framework

| *** Settings *** | Documentation  | A test suite for valid login | Library        | SeleniumLibrary | AS | Selenium | Suite Setup    | Go To Login Page | Suite Teardown | Log Out | Test Tags      | login | *** Variables *** | ${ROOT_PAGE} =      | http://localhost:80 | ${LOGIN_PAGE} =     | ${ROOT_PAGE}/login | ${LOGIN_BUTTON} =   | “xpath://span[text()=‘Login’]/parent::button” | ${LOGOUT_BUTTON} =  | “xpath://span[text()=‘Logout’]/parent::button” | ${USERNAME_FIELD} = | “xpath://span[id=’username’]” | @{LOGIN_LIST} =     | testuser | testadmin | @{PASSWORD_LIST} =  | testuserpassword | testadminpassword | @{USER_LIST} =      | User Test | Admin Test | *** Test Cases *** | Login With User Password For All Users | | ${list_length} = | Get Length | ${LOGIN_LIST} | | FOR | ${login_index} | IN RANGE | 0 | ${list_length} | | | Login User | ${LOGIN_LIST}[${login_index}] | ${PASSWORD_LIST}[${login_index}] | | | Check Login | ${USER_LIST}[${login_index}] | | | IF | “${USER_LIST}[${login_index}]” == “Admin Test” | | | | Log To Console | Admin was logged in | | | ELSE | | | | Log to Console | Admin was not logged in | | | END | | | Log Out | | END | | [Teardown] | Log Out | Denied Login With Wrong Password | | [Tags] | loginFailed | | Run Keyword And Expect Error | * | Login User | testuser | wrongpassword | | Check Not Logged In | | [Teardown] | Close Server Connection | *** Keywords *** | Go To Login Page | | Go To | ${LOGIN_PAGE} | Login User | | [Arguments] | ${login} | ${password} | | Click Button | ${LOGIN_BUTTON} | Log Out | | Click Button | ${LOGOUT_BUTTON} | | Go To Login Page | Get User Name | | # Get User Name from navbar | Check Login | | [Arguments] | ${expected_username} | | ${username} = | Get User Name | | Should Be Equal | ${expected_username} | ${username} | Check Not Logged In | | Run Keyword And Expect Error | PermissionError* | Get Server Version

Conclusion

Bien qu’au premier abord, les changements puissent paraître minimes, il est bon de noter qu’ils ont tous permis d’améliorer la lisibilité et donc la maintenabilité du code de manière globale. Cela nous aura aussi permis de mettre à jour une architecture logicielle plus que vieillissante, notamment en se débarrassant de Python 2.7 et en passant en Python 3.11. Grâce à cette mise à niveau, il nous est également possible d’utiliser un linter pour harmoniser le code, linter qui n’est compatible avec Robot Framework qu’à partir de la version 4, et qui transmet les résultats à Sonar pour permettre de limiter les inconsistances au sein du code.