Articles

Publié par

Il y a 1 semaine -

Temps de lecture 9 minutes

Angular : Tests UI

Dans cet article, nous détaillons la problématique de tests UI des applications Front Angular 6, pour explorer le Framework de tests end to end Protractor, ainsi que le Framework de BDD Cucumber. Nous parlons aussi de Rick et Morty.

Cucumber ?

Non pas le légume, mais le Framework de tests BDD : Behavior Driven Design. Il s’agit d’une méthode permettant de collaborer avec des intervenants non techniques pour partir des spécifications du produit, écrites sous forme de tests, vers l’implémentation. L’utilisation d’un langage commun, simple et expressif est alors nécessaire, c’est là qu’intervient Gherkin, la syntaxe utilisée par Cucumber pour rédiger les spécifications des tests.

Pour notre exemple, admettons qu’en réunissant les différents acteurs du produit autour de la table, ils se sont mis d’accord sur la spécification Gherkin suivante :

PS : Un Meeseeks est une créature fantastique de la série Rick et Morty, il est né pour accomplir un rêve et disparait juste après.

 

app.feature
Feature: Add a request for the Meeseeks
    The Meeseeks accepts my requests

    Scenario: Add request
        Given I am on the Meeseeks page
        When I fill the request
        And I create A Meeseeks
        Then The Meeseeks should fulfill my request

Pourquoi tester la UI ?

Comme tous les tests, les tests UI sont un moyen de garantir la qualité de son code. Il prouvent la conformité vis-à-vis de la spécification, garantissent la non régression en détectant rapidement les bugs, et réduisent les délais et les coûts des tests manuels en permettant de les jouer sur des matrices d’environnements.

Protractor quésaco !

Le mot, pas très esthétique à prononcer, veut dire rapporteur, l’outil de mesure d’angles. En se rappelant que c’est l’outil de tests end to end pour Angular, le jeu de mots est clair.

Ce Framework open source, faisant partie d’Angular lui même, permet de développer des tests depuis AngularJS et reste toujours assuré avec les dernières versions.

A noter qu’il dispose d’une importante communauté et une excellente documentation.

Protractor vs Selenium ?

Selenium est un outil qui permet de créer des tests automatiques sur les navigateurs. Il est historiquement l’outil le plus largement utilisé et permet de s’interfacer aux navigateurs les plus connus, et aux environnements de développement les plus communs.

Protractor est une surcouche de Selenium, il permet de :

  • Assurer l’ensemble des capacités de Selenium : tel que le branchement aux navigateurs, les sélecteurs des composants du DOM…
  • Interfaçage avec des Framework de tests BDD : “Out-Of-The-Box” comme avec Jasmine et Mocha, mais aussi avec des Frameworks Custom comme avec Cucumber
  • Fournir la méthode waitForAngular() : elle permet d’attendre les chargements d’Angular et les communications réseau… Ceci permet d’éviter les wait et les sleep, une très mauvaise pratique mais parfois nécessaire avec Selenium pour attendre les opérations asynchrones
  • Fournir des locators spécifiques Angular comme by.model() ou by.binding() ou by.repeater()
  • Réduire le coût de mise en place d’un outil de test :
    • Bonne nouvelle, vous avez probablement Protractor déjà mis en place avec un exemple d’un test dans le dossier e2e (si vous avez utilisé Angular CLI pour générer le projet)
    • Protractor embarque comme dépendance le WebDriver Selenium, donc pas besoin d’un Selenium externe (par contre c’est une bonne pratique pour la toolchain, on y reviendra dans la dernière partie)
    • C’est la même “expérience développeur”, tout est fourni pour assurer le bien être des développeurs Angular, même langages mêmes paradigmes en passant du développement Angular au développement des tests e2e
  • Plusieurs plugins sont fournis par Protractor, et d’autres par la communauté, ce qui en fait un véritable couteau suisse des tests

Comment intégrer Protractor et Cucumber à mon projet ?

Pour notre exemple, il faut avoir déjà installé node et npm.

  • Commençons par installer Angular CLI
npm install -g @angular/cli

 

  • Créons un nouveau projet Angular (Angular 6 au moment de la rédaction de cet article)
ng new project-protractor-cucumber

 

  • Créons une petite application
app.component.ts
import { Component } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  title = 'test-prot';
  requests: string[] = [];

  addRequest(request: string): void {
    this.requests.push(request);
  }
}

app.component.html
<div>
    <label>
        I'm Mr. Meeseeks look at me! Make a request :
        <input #request id="request" />
    </label>
    <button (click)="addRequest(request.value); request.value=''">
        Validate Meeseeks
    </button>
</div>

<div *ngFor="let request of requests">
    
Caaaan do {{request}}, yes sireee

</div>

  • Installer les dépendances nécessaires pour tester avec Cucumber
npm install --save-dev protractor-cucumber-framework cucumber chai @types/chai @types/cucumber

 

  • Configurer Protractor
// Protractor configuration file, see link for more information
// https://github.com/angular/protractor/blob/master/lib/config.tsxsxs

exports.config = {
  allScriptsTimeout: 11000,
  specs: [
    './src/**/*.e2e-spec.ts'
  ],
  capabilities: {
    browserName: 'firefox'
  },
  directConnect: true,
  baseUrl: 'http://localhost:4200/',
  framework: 'custom',
  frameworkPath: require.resolve('protractor-cucumber-framework'),
  specs: [
    './src/features/*.feature'
  ],
  cucumberOpts: {
    require: ['./src/steps/*.ts'],
    strict: true,
    dryRun: false,
    compiler: []
  },
  onPrepare() {
    require('ts-node').register({
      project: 'e2e/tsconfig.e2e.json'
    });
  }
};

{
  "extends": "../tsconfig.json",
  "compilerOptions": {
    "outDir": "../out-tsc/app",
    "module": "commonjs",
    "target": "es5",
    "types": [
      "cucumber",
      "node"
    ]
  }
}

Mon premier test !

Pour satisfaire l’arborescence des tests comme spécifié dans la configuration Protractor, il faut :

  • Supprimer les tests Jasmine existants. Ces derniers ne sont pas nécessaire étant donné que nous allons utiliser Cucumber. Ce sont les deux fichiers sous e2e/src
  • Créer deux dossiers sous e2e/src :
    • un dossier features, pour contenir les descriptions Gherkin des tests
    • un dossier steps, pour contenir les tests eux-mêmes
  • Créer un fichier app.feature sous e2e/src/features, et coller dedans le Gherkin discuté au début avec l’ensemble de l’équipe (voir au début de l’article)
  • Créer les fichiers d’implémentation :
    • e2e/src/app.steps.ts : ce fichier contient l’implémentation du test décrit dans le feature
    • e2e/src/app.po.ts : ce fichier permet d’appliquer le ‘Page Object Pattern”. Ce principe consiste à séparer les implémentation des tests des spécificités du DOM.
app.steps.ts
import { expect } from 'chai';
import { defineSupportCode } from 'cucumber';
import { AppPage } from './app.po';
import { browser, by, element } from 'protractor';

defineSupportCode(({ Given, When, Then, Before }) => {
    let page: AppPage;
    const request = 'Take two strokes off my golf game';

    Before(() => {
        page = new AppPage();
    });

    Given('I am on the Meeseeks page',
        () => page.navigateTo());

    When('I fill the request',
        () => page.addRequest(request));

    When('I create A Meeseeks',
        () => page.ValidateMeeseeks());

    Then('The Meeseeks should fulfill my wish',
        async () => expect(await page.getMeeseeksResponse()).equals('Caaaan do ' + request + ', yes sireee'));
});

app.po.ts
import { browser, by, element, until } from 'protractor';

export class AppPage {
  navigateTo() {
    return browser.get('/');
  }

  addRequest(request: string) {
    return element(by.id('request'))
      .sendKeys(request);
  }

  ValidateMeeseeks() {
    return element(by.buttonText('Validate Meeseeks'))
      .click();
  }

  getMeeseeksResponse() {
    return element.all(by.id('response')).first().getText();
  }
}

 

  • Lancer le test !
ng e2e

Aller plus loin avec les navigateurs !

Lancement du test avec Firefox

Pour y parvenir la première étape est de modifier la configuration de Protractor pour utiliser Firefox. Pour le faire il faut aller au fichier protractor.conf.js et modifier browserName: ‘chrome’ par browserName: ‘firefox’.

En lançant ng e2e, vous apercevez une erreur du type :

Pour le corriger il suffit de lancer le shell :

./node_modules/protractor/node_modules/webdriver-manager/bin/webdriver-manager update

 

Ceci permettra de télécharger et/ou mettre à jour les drivers des navigateurs utilisés par le Webdriver Selenium, y compris celui de Firefox : geckodriver.

En relançant, le test est un succès !

PS : Sous certaines configurations, Protractor n’arrive pas à trouver le binaire de Firefox. Dans ce cas il l’annoncera clairement dans un message d’erreur du même type que l’erreur mentionnée précédemment. Pour le corriger, il suffit d’ajouter le path du binaire de Firefox dans la variable globale PATH.

Lancement avec plusieurs navigateurs

Il suffit de remplacer :

capabilities: {
  browserName: 'firefox'
},

 

par :

multiCapabilities: [{
  'browserName': 'firefox'
}, {
  'browserName': 'chrome'
}],

 

Ce qui permettra de lancer le test sur plusieurs navigateurs en parallèle.

Point Toolchain

Jusqu’à là tout va bien « en local ». Mais pour l’exercice réel, nous aurons besoin nécessairement de lancer les tests E2E en environnement d’intégration continue.
Pour y arriver, l’idée est de déployer un Selenium webdriver sur un de nos serveurs avec le navigateur, pour l’utiliser comme machine de test.

Pour ce faire, sur cette machine :

  • Installer le webdriver-manager :
sudo npm install -g webdriver-manager
  • Mettre à jour les drivers
webdriver-manager update
  • Démarrer le webdriver-manager
webdriver-manager start
  • En enfin configurer Protractor pour utiliser ce webdriver :
protractor.conf.js
...
exports.config = {
  allScriptsTimeout: 11000,
  specs: [
    './src/**/*.e2e-spec.ts'
  ],
  capabilities: {
    browserName: 'firefox'
  },
  seleniumAddress: 'http://localhost:4444/wd/hub',
...

Gestion du timeout

En exécutant les tests sur une machine peu puissante ou qui subit de la charge, il se peut que Cucumber atteigne le timeout (par défaut de 5 secondes) et annonce la failure du test.

Pour un environnement d’intégration continue ou pour des démos, ceci peut être frustrant. Afin d’éviter ce problème il est possible de modifier le timeout par défaut en ajoutant dans app.steps.ts :

app.steps.ts
import { expect } from 'chai';
import { defineSupportCode, setDefaultTimeout } from 'cucumber';
import { AppPage } from './app.po';
import { browser, by, element } from 'protractor';

setDefaultTimeout(60 * 1000);

...

Conclusion

Ici s’achève notre article. Nous avons compris que Protractor est une surcouche de Selenium, qui apporte des fonctionnalités supplémentaire adaptées à Angular comme waitForAngular() et les sélecteurs spécifiques. Nous avons discuté du rôle du BDD pour assurer la qualité de code et faire émerger à partir des besoins métier des tests End to End compréhensibles par l’ensemble de l’équipe. Et finalement nous avons configuré Protractor et implémenté un exemple de test.

Pour un exemple fonctionnel, le code source discuté dans cet article est disponible dans le dépôt git illustrant Protractor et Cucumber.

Publié par

Commentaire

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *

Nous recrutons

Être un Xebian, c'est faire partie d'un groupe de passionnés ; C'est l'opportunité de travailler et de partager avec des pairs parmi les plus talentueux.