00:00:00

Tests fonctionnels avec CasperJS

J. Muetton

ITNetwork, 8 mars 2013

Notes

Sommaire

Cette présentation est composée de beaucoup de code pour faciliter votre démarrage !

  • Qui est CasperJS ?
  • Débuter avec CasperJS
  • Tester avec CasperJS
  • Quelques outils

Notes

Qui est CasperJS

Architecture

  • CasperJS pilote PhantomJS
  • PhantomJS pilote WebKit
  • WebKit : un vrai navigateur !

Il sait faire

  • des captures d'écran
  • exécuter le javascript de la page
  • cliquer, soumettre un formulaire
  • ...

Notes

Code first

// Créer un navigateur
var casper = require('casper').create();

// Lancer un navigateur à une URL donnée
casper.start('http://google.fr/', function onReady () {
  /* Initialize */
});

// Interragir avec la page
casper.then(function() {
  this.fill('form[action="/search"]', { q: 'itn' }, true);
});
// Interragir avec la réponse
casper.then(function() {
    this.click('h3.r a');
});

// Lancer la recette
casper.run(function() {
  this.echo('Done.').exit(0);
});

Notes

Quelques trucs fun

Prendre une capture d'écran (doc)

casper.capture('./screenshot.png');

Injecter du code dans le navigateur (doc)

casper.evaluate(function (tag) {
    return document.querySelector(tag).innerHTML;
}, 'title');

Attendre qu'un élément existe (doc)

casper.waitForSelector('.tweet-row', function() {});

Notes

Installer CasperJS

Notes

Installer CasperJS 1/2

Installer phantom

Télécharger l'exécutable pour votre plateforme puis ajoutez le au PATH ou à /usr/local/bin

récupérer casper

Il suffit ensuite de récupérer la dernière version via git :

$ git clone git://github.com/n1k0/casperjs.git
$ cd casperjs
$ git checkout tags/1.0.2 # replace with last tag

Casper est installé, rendons le exécutable

Notes

Installer CasperJS 2/2

Linux

$ ln -sf `pwd`/bin/casperjs /usr/local/bin/casperjs

Windows

Ajouter ";C:\{CASPER_DIR}\batchbin" au PATH.

# A la place de la commande casper
C:> casperjs.bat myscript.js

Plus d'infos sur le site de CasperJS

Notes

Débuter avec CasperJS

Notes

Les sélecteurs

CSS3

casper.start('http://example.org', function () {
  casper.getHTML('h3 a:first-child');
});

XPath

var x = require('casper').selectXPath;

casper.start('http://example.org', function () {
  casper.captureSelector('screen.png', x('//*[@id="plop"]'));
});

W3C XPath tutorial.

Notes

Écrire de bons sélecteurs

Rester flexible face aux évolutions du markup

casper.thenClick('nav a.parameters');

var x = require('casper').selectXPath;
casper.thenClick(x('//nav//a[contains(., "Paramètres")]'));

Factoriser dans des méthodes

casper.
    thenGoToMenu("Administration", "Paramètres");
  • Simplifie la lecture
  • Principe DRY : utiliser des variables

Notes

Les évènements

Un ensemble d'évènements pour étendre CasperJs

var f = require('utils').format;

casper.on('viewport.changed', function (size) {
    this.log(f('resized to %dx%d', size[0], size[1]));
});

Evènements personnalisés

casper.on('app.signin', function (username) {
    this.echo('signed in');
})

// ...
casper.emit('app.signin', "demo")

Notes

Programmation asynchrone

Notes

Client / serveur

// Envoi de la requête
casper.start('http://www.wikipedia.org/');
// ...
// Exécution serveur
// ...
casper.then(function () { // réponse

  // Nouvelle requête
  this.fill('form.search-form', {
      search: "github"
  }, true);
});
// ...
// Exécution serveur
// ...
casper.then(function () { // réponse
  var res = this.getHTML('#mw-content-text p:first-of-type');
  this.echo(res);
});

casper.run(function() { this.exit(0); });

Notes

Action interne à la page

Testons l'autocomplétion de elasticsearch

casper.start('http://elasticsearch.org/');

// rempli le champs texte
casper.then(function () {
    this.click('input#search');
    this.sendKeys('input#search', 'guide');
});

// Attente des résultats
casper.waitUntilVisible(
  '.ui-autocomplete.ui-menu',
  function () {

    // cliquer sur le premier résultat
    this.click('.ui-autocomplete.ui-menu a:first-of-type');
  });

casper.run(function() { this.exit(0); });

Notes

L'essentiel

La doc c'est par là

Notes

Rappels de JavaScript

Notes

Règle indispensables

On déclare toujours ses variables.

var foo = "bar",
    i;
for (var j = 0 ; j < 2 ; j++ ) {
    // ...
}

Les { sont toujours en fin de ligne

function foo () {
}

Attention au this !

Notes

Ressources JavaScript

Tutoriels

Video / Articles

C'est un vrai langage et il faut l'apprendre !

Notes

Tester avec CasperJS

Notes

Le premier test

// à partir de 1.1
casper.test.begin('ITN popup', 3, function (test) {
    casper.start('http://www.itnetwork.fr/').

    then(function () {
        test.assertVisible('div#overlay');
        test.assertExists('.skipTeaser');
    }).

    thenClick('.skipTeaser').

    waitWhileVisible('div#overlay', function () {
        test.pass("Overlay is hidden.");
    }, function onError () {
        test.fail("Overlay did not hide.");
    }, 5000). // 5 seconds

    run(function() {
        test.done();
        test.renderResults(true);
    }); });

Notes

Lancer le premier test

Sous Linux

$ casperjs test itnPopupTest.js

Sous Windows

C:\tests\> casperjs.bat test itnPopupTest.js

Résultat

PASS Selector is visible
PASS Found an element matching: .skipTeaser
PASS Overlay is hidden.
PASS 3 tests executed, 3 passed, 0 failed.     

Notes

Les assertions

Casper propose une librairie d'assertions

// simple
casper.test.pass("Perfect")
casper.test.fail("Perfect")

// DOM
casper.test.assertVisible('.selector')
casper.test.assertSelectorExists('.selector')

// text
casper.test.assertSelectorHasText('title', 'U rock')
casper.test.assertTextDoesntExist('bing', 'body without "bing"');

// http
casper.test.assertHttpStatus(200)

plus sur la page dédiée

Notes

Organiser sa suite de tests

Notes

La configuration

Lire une variable

// tests/includes/pre.js
var screenshot = casper.cli.get('screenshot') ||
                 './build/error.png';

Capture en cas d'erreur

// tests/includes/pre.js
casper.on('error', function on_error(failure) {
    casper.echo(failure, 'ERROR');
    casper.capture(screenshot);
    casper.exit(1);
});

Notes

Démarrage du navigateur

Factorisons tout ce qui doit être répété :

var auth = {
    username: "httpuser",
    password: "httppassword"
};

casper.setupAndStart = function () {
    if (!this.started) {
        this.start();
    }

    // set viewport
    this.viewport(1280,1024);
    // http authentication
    this.setHttpAuth(auth.username, auth.password);

    return this;
};

Notes

Se connecter

// tests/includes/pre.js
casper.signIn = function signIn (username, password) {
    if (!this.started) { this.setupAndStart(); }

    this.thenOpen('https://mysecuresite.io/login')

    this.then(function () {

        this.fill('form[/login]', {
            username: username || "demo",
            password: password || "$ecr3t"
        }, true);
    });

    this.then(function () {
        this.test.assertHttpStatus(200, 'user has signed in');
    });

    // fluid interface
    return this;
}

Notes

Tester une fonctionalité

casper.test.begin('Access admin area', 1, function (test) {
    casper.setupAndStart().

    signIn("admin", "$ecr3t").

    thenClick('header nav li>a:contains("Administration")').

    then(function () {
        if ("200" !== this.status(true)) {
            // fin du test
            test.fail("Accès à l'administration refusé.");
        }

        test.assertTextExists('Bienvenue Démo !');
    }).

    run(function () {
        test.done();
    });
});

Notes

Lancer les tests

Directement

$ casper test tests/suite --includes=tests/include/pre.js \
        --screenshot=build/error.png

Avec un script shell 1/3

#!/usr/bin/env bash
SCRIPTPATH=`readlink -f $0`;
SCRIPTNAME=$(basename $SCRIPTPATH)
ROOT_DIR="$(dirname $(dirname $SCRIPTPATH))"

CASPER_COMMAND="casperjs test $ROOT_DIR/suite --include=$ROOT_DIR/include/pre.js"
XUNIT="build/junit-casper.xml"
SHORT_OPT="t:s:xh"
LONG_OPT="host:,screenshot:caper-xunit,help"

Notes

Avec un script shell 2/3

# write usage
function usage {
    printf "Launch tests\n"
    printf "\n"
    printf "  $SCRIPTNAME --host http://localhost/\n"
    printf "Options\n"
    printf "\t-t --host         : changes host\n"
    printf "\t-s --screenshot   : changes screenshot destination\n"
    printf "\t-x --casper-xunit : output casper tests in xunit format\n"
    printf "\t-h --help         : this help\n"
}

# parse arguments
OPTS=$( getopt -o "$SHORT_OPT" -l "$LONG_OPT" -- "$@" )
if  [ $? != 0 ]; then
    usage;
    exit 1;
fi
eval set -- "$OPTS"

Notes

Avec un script shell 3/3

# read options
while true ; do
    case "$1" in
        -t|--host) HOST="$2"
            shift2;;
        -s|--screenshot) SCREENSHOT="$2"
            shift2;;
        -h|--help) usage
            exit 0;;
        -x|--casper-xunit)
            CASPER_COMMAND="$CASPER_COMMAND --xunit=$XUNIT"
            shift2;;
        --) shift
            break;;
    esac
done

# extend comand with options
CASPER_COMMAND="$CASPER_COMMAND --host=$HOST"
CASPER_COMMAND="$CASPER_COMMAND --screenshot=$ERROR"
$CASPER_COMMAND

Notes

Que tester ?

  • Des scenarios / User stories
    En tant qu'administrateur,
    Quand je clique sur Administration
    Le panneau d'administration est affiché

    Quand je clique sur "Utilisateur"
    La liste des utilisateurs est affichée

    Quand je clique sur nouveau
    Un formulaire apparait

    Quand je soumets ce formulaire avec des données valides
    La liste des utilisateurs est affichée
  • Que toutes les pages s'affichent correctement
  • Faire des screenshots pour voir les erreurs

Notes

Quelques outils

Notes

Resurect.io

La documentation et le code de Resurect.io sont sur github.

  • Extension Chrome
  • Génère des scenarios à partir de la navigation

Reportez vous à la procédure d'installation.

Les tests générés doivent être modifiés manuellement !

Notes

Remplacer webkit par Geko

Ce projet est au stade expérimental

Le projet slimerjs a pour but de :

  • proposer un outil similaire à PhantomJS
  • basé sur le moteur de Firefox

Notes

Merci

Notes