Une histoire de fantômes

CasperJS

CasperJS ?

  1. Un outil de test d’interfaces web, écrit en JavaScript

  2. vous écrivez vos tests en JavaScript (ou en CoffeeScript)

  3. ils se lancent sous PhantomJS

PhantomJS ?

  1. Un navigateur webKit en mode headless (sans IHM)

  2. Support Javascript

  3. Interprête le DOM, les sélecteurs CSS

  4. Même Canvas et SVG

  5. vous pouvez prendre des captures d’écrans

no-node.js

  1. Cela n’a rien à voir avec node.js

  2. C’est un outil autonome (nécessite PhantomJS toutefois)

Alternatives

  1. HTMLUnit. Headless. Reimplementation des fonctionnalités d’un navigateur

  2. Selenium. Plusieurs languages dont Java. Plusieurs navigateurs.

  3. Cucumber. BDD. Gherkin + Ruby + Selenium.

  4. etc...

Une image plutôt qu’un long discours

Quelques conseils sur les tests d’IHM

  1. Ils sont importants !

  2. Il est utile de tester toute la "stack" !

  3. Les tests doivent être lancées régulièrement depuis votre usine logicielle

  4. Les tests d’intégrations ne remplacent pas les tests manuels...

  5. mais donnent un bon indicateur de confiance

  6. Les tests d’intégrations ne remplacent pas les tests unitaires

  7. Ne pas tout tester par contre (cas aux limites, tests trop fragiles ou compliqués) ⇒ KISS, YAGNI

  8. Si vous testez sous Internet Explorer, utilisez une suite minimaliste (lenteur, fragilité)

  9. Les tests d’intégration sont lents : lancez les en parallèle et peut-être pas à chaque commit

Étapes de navigation

      
        var casper = require('casper').create();

        casper.start('http://google.fr/', function() {
        });

        casper.then(function() {
        });

        casper.thenClick('link', function() {
        });

        casper.then().repeat(2, function() {
        });

        casper.back(function() {
        });

        casper.run(function() {
        });
      
   

Sélecteurs : CSS, label, XPath

      
      casper.start('http://domain.tld/page.html', function() {
         this.test.assertExists('#logo', 'The logo exists');
      });

      casper.then(function() {
         this.click('.geoEntity.restaurant a');
      });

      casper.then(function() {
         this.clickLabel('Fermer la fenêtre');
      });
      
   
      
        var x = require('casper').selectXPath;

        casper.thenOpen('http://domain.tld/page.html', function() {
            this.click(x('//*[@id="logo"]'));
        });
       
    

Formulaires

      
         <form action="/contact" id="contact-form" enctype="multipart/form-data">
             <input type="text" name="subject">
             <input type="radio" name="civility" value="Mr"/> Mr
             <input type="radio" name="civility" value="Mme"/> Mme
             <input type="text" name="name"/>
             <input type="email" name="email"/>
             <input type="file" name="attachment"/>
             <input type="checkbox" name="cc"/> Copie carbone
             <input type="submit"/>
         </form>
      
   
      
      casper.start('http://some.tld/contact.form', function() {
         this.fill('form#contact-form', {
              'subject':    'Réclamation',
              'civility':   'Mr',
              'name':       'Chuck Norris',
              'email':      '[email protected]',
              'cc':         true,
              'attachment': '/Users/chuck/i-am-not-happy.doc'
          }, true);
      });
      
   

waitFor, waitWhile

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

       casper.waitWhileSelector('.selector', function() {});

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

       casper.waitWhileVisible('.selector', function() {});

       casper.waitForPopup(/popup\.html$/, function() {});

       casper.waitForResource("foobar.png"), function() {});
       
    
      
      casper.waitFor(function check() {
          ...
      }, function then() {
          ...
      }, function timeout() {
         ...
      });
      
   

Évaluation de code dans le navigateur

      
        casper.waitFor(function check() {
            return this.evaluate(function(zoom) {
                return $('.slider-handler').css('bottom') === zoom;
            }, {'zoom': '50%'});
        });
      
   
      
      casper.start('http://www.google.fr/', function() {

          this.test.assertEval(function() {
              return document.querySelectorAll('form').length > 0;
          }, 'google.fr has at least one form');

          this.test.assertEval(function() {
              return document.title === title;
          }, 'google.fr title is "Google"', 'Google');

      });
      
   

Assertions

Captures d’écrans

      
      casper.start().thenOpen('http://google.com', function() {
          this.capture('google.png');
      });
      
   
      
      casper.start('http://www.weather.com/', function() {
          this.captureSelector('weather.png', '#wx-main');
      });
      
   
       
        casper.test.on("fail", function(failure) { // Capture en cas d’erreur
            var fileName = failure.file.split("/"); // Fichier de test
            fileName = fileName[fileName.length-1].split('.')[0]; // Sans l’extension
            this.capture('fail-' + failure.type + '-' + fileName);
        });
        
    

Téléchargements

      
      this.download('http://www.google.com', 'google_homepage.html');
      
   

Tips and tricks

       
       casper = require('casper').create {
           clientScripts: ["includes/underscore-min.js"]
           remoteScripts: ["http://code.jquery.com/jquery-1.9.1.min.js"]
       }
       
    

Événements et filtres

      
      casper.on('open', function(location, settings) {
          casper.echo(location);
      });
      
   
      
      // listening to a custom event
      casper.on('google.loaded', function() {
          this.echo('Google page title is ' + this.getTitle());
      });
      casper.start('http://google.com/', function() {
         // emitting a custom event
         this.emit('google.loaded');
      });
      
   
      
      casper.setFilter('open.location', function(location) {
         return /\?+/.test(location) ? location += "&foo=42" : location += "?foo=42";
      });
      
   

Débogage

  1. Par le créateur lui-même : "Debugging is hard"

  2. PhantomJS ne donne pas beaucoup d’informations

  3. Conseil 1 : Soignez les messages d’assertions

  4. Conseil 2 : Nommez vos fonctions anonymes

  5. Conseil 3 : Activez les traces :

  6.          
             var casper = require('casper').create({   
                 verbose: true,
                 logLevel: "debug"
             });
             
          

Travaillons ensemble

Pleins d’autres fonctionnalités


Assertions, Utils, ClientUtils, Casper options, Logging, etc...


http://casperjs.org


http://github.com/n1k0/casperjs


Integration with node.js


https://github.com/WaterfallEngineering/SpookyJS

BDD


https://github.com/cucumber/cucumber-js/


PhantomJS can be run from Selenium too


https://github.com/detro/ghostdriver


http://phantomjs.org/


A whole ecosystem available!


https://github.com/casperjs

Démo

/

#