Testcafe & Cucumber
Et si nos POs commençaient à faire leur travail?



+
+

Il était une fois...
Il était une fois...
- Un développeur consciencieux & son application web Angular.



- Avec une couverture de tests unitaires presque parfaite.
Il était une fois...
- Mais un jour, l'impossible se produit: un BUG en production...


- Et des chefs de projet en colère!
Il était une fois...
A chaque push? Nightly build? Release?
Sur 1-n navigateurs? Via un Browserstack-like?
Comment les écrire? Les lancer?
Et le reporting?
QUI
DOIT
LES
ECRIRE???
Que faire pour ne pas réitérer ce problème?
Des tests end to end!
Pour lancer de manière automatisée des scénarios de tests!
Ce qui nous amène à...
Sommaire
1) e2e: existant et problématiques
2) Lancer ses tests avec Testcafe
3) Écrire ses tests avec cucumber-js
4) Solutions retenues
5) Conclusion & Questions
Etat de l'art...
1) e2e: existant et problématiques
En Angular, le framework de tests e2e par défaut est Protractor.
- Un programme Node.js utilisant Selenium
- Supporte les frameworks de tests Jasmine et Mocha.


1) e2e: existant et problématiques
Comment fonctionnent Protractor et Selenium?

1) e2e: existant et problématiques
C'est un peu lourd à installer tout ça, non?
webdriver-manager update --proxy https://proxy.com:8080
✹ ✭ webdriver-manager: using local installed version 12.0.6 events.js:160
throw er; // Unhandled 'error' eventI/hosted - Using the selenium server at http://localhost:4444/wd/hub
I/launcher - Running 1 instances of WebDriver
E/launcher - Error code: 135
E/launcher - Error message: ECONNREFUSED connect ECONNREFUSED 127.0.0.1:4444[14:19:22] I/launcher - Running 1 instances of WebDriver
[14:19:22] I/hosted - Using the selenium server at http://localhost:4444/wd/hub
[14:19:22] E/launcher - The driver executable does not exist: /root/.npm-global/lib/node_modules/protractor/node_modules/webdriver-manager/selenium/chromedriver_2.31
1) e2e: existant et problématiques
Et l'écriture des tests avec tout ça?
describe('my todo list', function() {
it('should add a todo', function() {
browser.get('https://my-todo-list.org');
element(by.model('todoList.todoText'))
.sendKeys('write first protractor test');
element(by.css('[value="add"]')).click();
var todoList = element.all(by.repeater('todo in todoList.todos'));
expect(todoList.count()).toEqual(3);
expect(todoList.get(2).getText())
.toEqual('write first protractor test');
todoList.get(2).element(by.css('input')).click();
var completedAmount = element.all(by.css('.done-true'));
expect(completedAmount.count()).toEqual(2);
});
});Whhhhhhaaaaaat????

1) e2e: existant et problématiques
PO: "Euh, ça veut dire quoi ce test là? C'est censé représenter quel cas métier?"
Dev remplaçant: "Bah je sais pas trop, je creuse (ma tombe)"
1) e2e: existant et problématiques

Les gars, j'ai ZE IDEA!!!
DEV
BADASS
1) e2e: existant et problématiques
Et si on utilisait le pattern Page Object?
'use strict';
var AngularPage = function () {
browser.get('http://my-todo-list.org');
};
AngularPage.prototype = Object.create({}, {
todoText: { get: function () { return element(by.model('todoText')); }},
addButton: { get: function () { return element(by.css('[value="add"]')); }},
yourName: { get: function () { return element(by.model('yourName')); }},
greeting: { get: function () { return element(by.binding('yourName')).getText(); }},
todoList: { get: function () { return element.all(by.repeater('todo in todos')); }},
typeName: { value: function (keys) { return this.yourName.sendKeys(keys); }},
todoAt: { value: function (idx) { return this.todoList.get(idx).getText(); }},
addTodo: { value: function (todo) {
this.todoText.sendKeys(todo);
this.addButton.click();
}}
});
module.exports = AngularPage;1) e2e: existant et problématiques

Grâce à mon PageObject, les tests sont compréhensibles!
Sauf que...
1) e2e: existant et problématiques

Grâce à mon PageObject, les tests sont compréhensibles!

J'ai fait pareil...
2

J'ai fait pareil...
3
etc...
TestCafe à la rescousse!


2) Lancer ses tests avec TestCafé
Qu'est-ce que c'est?
- Outil Node.js d'automatisation de tests e2e
- Gratuit & Open Source
- Ecriture des tests en JS (ES2017) ou Typescript
- Installé en une minute!
- Pas besoin d'outil tiers comme Webdriver
npm install -g testcafe
testcafe chrome mon-test.js2) Lancer ses tests avec TestCafé
Les (autres) avantages:
Timeouts manuels- testcafe-live
- Concurrency
- Remote testing
- ...
2) Lancer ses tests avec TestCafé
Comment ça fonctionne?

testcafe-hammerhead
2) Lancer ses tests avec TestCafé
Notre premier test:
import { Selector } from 'testcafe';
fixture `Hello world classique`
.page `https://devexpress.github.io/testcafe/example`;
test('Saluer le développeur', async t => {
await t
.typeText('#developer-name', 'Laurent Wroblewski')
.click('#submit-button')
.expect(Selector('#article-header').innerText)
.eql('Hello Laurent Wroblewski!');
});
Page Object pattern!!!
2) Lancer ses tests avec TestCafé
Utilisation des Selectors TestCafe:
import { Selector } from 'testcafe';
fixture `Example page`
.page `http://devexpress.github.io/testcafe/example/`;
test('My test', async t => {
const osCount = Selector('.column.col-2 label').count;
const submitButtonExists = Selector('#submit-button').exists;
const secondCheckBox = Selector('input')
.withAttribute('type', 'checkbox')
.nth(1);
await t
.expect(osCount).eql(3)
.click(secondCheckBox)
.expect(submitButtonExists).ok();
});2) Lancer ses tests avec TestCafé
Utilisation des Selectors TestCafe:
- Représente un sélecteur vers X éléments du DOM.
- Permet d’exécuter des actions sur ces éléments.
- Utilisables dans les assertions.
- Filtrer les éléments (par texte, attribut).
import { Selector } from 'testcafe';
const productCard = Selector('.product-card');await t.click(productCard);await t.expect(productCard.scrollHeight)
.eql(300);const selectedProductCard =
Selector('.product-card')
.withAttribute('selected');2) Lancer ses tests avec TestCafé
Création de Selectors custom:
import { Selector } from 'testcafe';
const elementWithIdOrClassName = Selector(value => {
return document.getElementById(value) || document.getElementsByClassName(value);
});2) Lancer ses tests avec TestCafé
Extension de Selectors:
interface UserTableSelector extends Selector {
getCellContent(rowIndex: number, columnIndex: number): Promise<string>;
}
const userTable: UserTableSelector = <UserTableSelector>Selector('#users')
.addCustomMethods({
getCellText: (table: HTMLTableElement, rowIndex: number, columnIndex: number) => {
return table.rows[rowIndex].cells[columnIndex].innerText;
}
}
);
await t.expect(userTable.getNameCellContent(1)).contains('Dupond');2) Lancer ses tests avec TestCafé

SELECTORS SEAL OF APPROVAL
2) Lancer ses tests avec TestCafé
Librairies de sélecteurs pour différents frameworks :
testcafe-angular-selectors
testcafe-react-selectors
testcafe-vue-selectors
testcafe-aurelia-selectors
import { AngularSelector } from 'testcafe-angular-selectors';
const list = AngularSelector('list');
const listAngular = await list.getAngular();
await t.expect(listAngular.inputState).eql(1);2) Lancer ses tests avec TestCafé
Des providers existent pour l'intégration avec:
- BrowserStack
- Sauce Labs
- electron



npm install testcafe-browser-provider-browserstack
npm install testcafe-browser-provider-saucelabs
npm install testcafe-browser-provider-electron2) Lancer ses tests avec TestCafé
En conclusion :
- Facile à installer et à lancer
- APIs intuitives et faciles à customiser
- S'intègre bien à vos process d'intégration continue
Question: Qu'en est-il de la concurrence?



Navigateurs supportés: Chrome & co
C'est bien beau tout ça...
Mais seuls les développeurs peuvent écrire ce genre de tests?!
Cucumber à la rescousse!


3) Ecrire ses tests avec cucumber-js
Famille des Cucurbitacées- Implémentation JS de cucumber
- Outil de lancement de tests automatisés BDD
- Ecriture des tests en Gherkin
GIVEN un contexte initial
WHEN une action est effectuée
THEN un comportement est attendu
3) Ecrire ses tests avec cucumber-js
- Comment l'utiliser?
$ npm install cucumber
$ ./node_modules/.bin/cucumber-js features/**/*.feature3) Ecrire ses tests avec cucumber-js
- Les fichiers feature décrivent les tests métiers.
- Chaque scénario représente un cas de test indépendant.
# features/user_login.feature
Feature: Login utilisateur
En arrivant sur le site, l'utilisateur se connecte pour accéder à son profil.
Scenario: Affichage de la Popup de login
Given Je me rends sur la page home
When Je clique sur le bouton "Connexion"
Then La modale "Connexion au site" devrait apparaitre
Scenario Outline: Connexion utilisateur
Given Je me rends sur la page home
When Je clique sur le bouton "Connexion"
And Je saisis la valeur "<login>" dans le champ de saisie "Votre login"
And Je saisis la valeur "<password>" dans le champ de saisie "Votre mot de passe"
And Je clique sur le bouton primaire
Then Le message suivant devrait apparaitre: "<result_login>"
Examples:
| login | password | result_login |
| Tom | 1234 | Erreur |
| Valid | Titi456! | Succès |3) Ecrire ses tests avec cucumber-js
- Step Definitions: glue entre les features et le système de tests.
import { Before, Given, Then, When } from 'cucumber';
import {PageObject} from '../page-objects/page-object';
When(/^Je me rends sur la page home$/, async function() {
await this.page.gotoPage('');
});
When(/^Je clique sur le bouton "(.*?)"/, async function(buttonLabel: string) {
await this.page.clickOnButton(buttonLabel);
});
When(/^Je saisis la valeur "(.*?)" dans le champ de saisie "(.*?)"$/,
async function(value, inputLabel) {
await this.page.writeInInput(inputLabel, value);
});
When(/^Je clique sur le bouton primaire$/, async function() {
await this.page.clickOnPrimaryButton();
});Euh... Juste un petit problème... ?
Donc on fait installer NodeJS, Webstorm/VSCode, etc à nos POs pour qu'ils puissent écrire leurs tests...?

2 besoins:
Rendre le développeur heureux
Rendre le PO heureux


4) Mélanger testcafe & cucumber
- L'écriture de tests en Gherkin avec cucumber-js
- La facilité de développement et de setup de TestCafe
- Comment faire?
- Grâce à la mécanique de World / Hook de cucumber
4) Mélanger testcafe & cucumber
- Qu'est-ce qu'un Hook cucumber?
- Un ensemble de points d'entrée pour initialiser et détruire l'environnement de test, avant et après chaque scénario
var {After, Before} = require('cucumber');
Before(function () {
...
});
Before(function (testCase, callback) {
...
callback();
});
After(function () {
...
});4) Mélanger testcafe & cucumber

Et si on lançait une instance TestCafe via un Hook cucumber?
4) Mélanger testcafe & cucumber
- L'API TestCafe fournit la méthode createTestCafe, qui permet d’exécuter une instance testcafe:
createTestCafe('localhost', 1338)
.then(testcafe => {
return testcafe.createRunner()
.src('test.js')
.browsers('chrome')
.run();
});4) Mélanger testcafe & cucumber
- Before du Hook cucumber:
Before(function() {
createTestCafe(config.host, 1338, 1339)
.then(tc => {
testcafe = tc;
const runner = testcafe.createRunner();
return runner
.useProxy(config.proxy)
.src(`node_modules/bdd-launcher/lib/test.js`)
.screenshots(config.screenshotsPath, false)
.concurrency(config.concurrency)
.browsers(config.browsers)
.run({ debugMode: config.debug })
.then(() => {
testcafe.close();
runner.stop();
})
.catch(() => {
console.log('closing testcafe on error...');
testcafe.close();
});
});
return this.waitForTestController
.then(testController => testController.maximizeWindow());
});4) Mélanger testcafe & cucumber
- Définition des steps avec les APIs TestCafé:
import { ClientFunction, Selector } from 'testcafe';
import { waitForAngular, AngularSelector } from 'testcafe-angular-selectors';
import {TestCafeWorld} from '../support/world';
export class PageObject {
getLocation = ClientFunction(() => document.location.href);
goBack = ClientFunction(() => window.history.back());
constructor(private t: TestController, private world: TestCafeWorld) {}
getContent(): Selector {
return this.getElement('body');
}
buttons(): Selector {
return this.getElement('button');
}
buttonWithLabel(label: string): Selector {
return this.buttons().withText(label);
}
gotoPage(page: string): Promise<any> {
return this.t.navigateTo('/' + page);
}4) Mélanger testcafe & cucumber
- Et pourquoi tu nous dis tout ça?
$ npm i bdd-testcafe --save-dev
$ npm run e2e
$ npm run e2e:report
Je suis très content!
Et pour les POs?
4) TestCafé Studio?

4) Solution maison: bdd-editor

En conclusion...
Conclusion...
- TestCafe: c'est bien
- cucumber-js: c'est bien aussi
- Les deux c'est mieux!
- Mais ça ne suffit pas...
Et plus sérieusement?
Conclusion...
- Les tests e2e sont TRÈS importants...
- ... Mais pas tels qu'ils sont implémentés dans 90% des cas.
Les tests e2e sont:
- Réservés aux seuls développeurs.
- Une vérification du fonctionnement technique de mon code.
- Accessibles à tous les membres de l'équipe
- Une documentation métier, et source de validation de specs métier
Merci à tous!!!
Des questions?
testcafe & cucumber: Et si nos POs travaillaient un peu?
By Laurent WROBLEWSKI
testcafe & cucumber: Et si nos POs travaillaient un peu?
- 667