Et si nos POs commençaient à faire leur travail?
+
+
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!
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
En Angular, le framework de tests e2e par défaut est Protractor.
Comment fonctionnent Protractor et Selenium?
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
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????
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)"
Les gars, j'ai ZE IDEA!!!
DEV
BADASS
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;Grâce à mon PageObject, les tests sont compréhensibles!
Sauf que...
Grâce à mon PageObject, les tests sont compréhensibles!
J'ai fait pareil...
2
J'ai fait pareil...
3
etc...
Qu'est-ce que c'est?
npm install -g testcafe
testcafe chrome mon-test.jsLes (autres) avantages:
Comment ça fonctionne?
testcafe-hammerhead
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!!!
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();
});Utilisation des Selectors TestCafe:
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');Création de Selectors custom:
import { Selector } from 'testcafe';
const elementWithIdOrClassName = Selector(value => {
return document.getElementById(value) || document.getElementsByClassName(value);
});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');SELECTORS SEAL OF APPROVAL
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);Des providers existent pour l'intégration avec:
npm install testcafe-browser-provider-browserstack
npm install testcafe-browser-provider-saucelabs
npm install testcafe-browser-provider-electronEn conclusion :
Question: Qu'en est-il de la concurrence?
Navigateurs supportés: Chrome & co
GIVEN un contexte initial
WHEN une action est effectuée
THEN un comportement est attendu
$ npm install cucumber
$ ./node_modules/.bin/cucumber-js features/**/*.feature# 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 |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();
});var {After, Before} = require('cucumber');
Before(function () {
...
});
Before(function (testCase, callback) {
...
callback();
});
After(function () {
...
});Et si on lançait une instance TestCafe via un Hook cucumber?
createTestCafe('localhost', 1338)
.then(testcafe => {
return testcafe.createRunner()
.src('test.js')
.browsers('chrome')
.run();
});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());
});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);
}$ npm i bdd-testcafe --save-dev
$ npm run e2e
$ npm run e2e:reportJe suis très content!
Les tests e2e sont: