Toolurile de testare automată din ziua de azi oferă o alternativă la metodele de testare manuală, deoarece testele sunt executate rapid și în mod repetat. Mai mult, rezultatele testelor pot fi comparate automat cu rezultatele comportamentului așteptat, iar diferenţele pot fi astfel evidenţiate. Testarea automată presupune un efort iniţial. Beneficiile viitoare sunt semnificative deoarece se traduc în stabilitate mărită. CasperJS este un tool util pentru testarea automată a aplicaţiilor web, fiind foarte ușor și rapid de instalat.
În contextul actual, livrarea rapidă pe piaţă este crucială, iar erorile și bugurile nu sunt foarte tolerate. Prin urmare, este important să se livreze produse de calitate. Deoarece asigurarea calităţii nu este un obiectiv principal din cauza unor constrângeri precum timpul, costul, resursele, acest aspect este doar parţial acoperit în munca efectivă. Consecinţa imediată este o experienţă negativă a utilizatorului.
Testare manuală are probleme de fiabilitate, deoarece este laborioasă și înceată. De exemplu, până când se descoperă un bug, se vor uita detaliile importante care trebuie reproduse. De asemenea, rezultatele testelor manuale ar trebui să fie bine documentate de tester pentru a funcţiona ca reper pentru alți testeri. Din aceste motive, testarea este o disciplină în cadrul căreia automatizarea poate ajuta cu adevărat.
Prin comparaţie cu testarea manuală, testarea automată aduce următoarele îmbunătăţiri la procesul de dezvoltare de software:
Calitate software îmbunătăţită: testele automate repetabile și consistente fac mai ușoară livrarea de software de calitate.
Documentaţie îmbunătăţită: toolurile de testare automată măsoară concretizarea obiectivelor, presupunând rezultate detaliate admis/respins.
CasperJS este construit pentru aplicaţii web și este potrivit pentru automatizarea interacţiunii cu o aplicaţie web. Este un utilitar open source pentru navigare și testare , scris în JavaScript, proiectat să utilizeze diverse render engines. În momentul scrierii, sunt suportate următoarele:
WebKit (motorul browserului Apple Safari), via PhantomJS;
CasperJS nu este limitat la acestea două, motoare de redare/render engines suplimentare pot fi suportate prin scrierea de plug-in-uri. Rularea unui test cu PhantomJS este opţiunea default, dar acest lucru poate fi schimbat printr-o opţiune din linia de comandă. Puteţi rula scripturi CasperJS din linia de comandă, iar, astfel, acestea pot fi ușor incluse în procesele de integrare continuă.
Acest articol prezintă modul de instalare a CasperJS, a feature-urilor sale principale și oferă câteva exemple de bază. Scopul acestui articol nu este de a prezenta toate feature-urile CasperJS, ci să ofere o viziune de ansamblu a feature-urilor acestui tool și a cazurilor când este indicată folosirea acestuia.
CasperJS poate fi instalat pe Mac OS X, Windows și pe majoritatea distribuţiilor Linux.
Binarul PhantomJS poate fi descărcat sub formă de arhivă și extras în orice locaţie. Dacă folosiţi Windows, locul instalării (terminată cu directorul "/bin") ar trebui adăugat la variabila PATH
environment.
Pentru a testa dacă PhantomJS este gata să fie folosit, deschideţi o linie de comandă și scrieţi:
phantomjs -v
Rezultatul ar trebui să fie numărul de versiune. În cazul unui răspuns "file not found" sau "unknown command", verificaţi variabila PATH environment.
Binarul CasperJS pote fi descărcat sub formă de arhivă și extras în orice folder. În funcţie de sistemul de operare, instalarea se poate face folosind Homebrew, un manager popular de pachete pentru Mac OSX, sau din npm (NodeJS package manager), sau utilizând git (din sursă). Dacă folosiţi Microsoft Windows, trebuie să adăugaţi la final calea locală către directorul bin la variabila PATH
environment.
Pentru a testa dacă CasperJS este gata să fie folosit, deschideţi o linie de comandă și scrieţi:
casperjs --version
Rezultatul ar trebui să fie numărul de versiune. În cazul unui răspuns "file not found" ("fișierul de a fost găsit") sau "unknown command" ("comandă necunoscută"), verificaţi variabila PATH environment.
Cazurile de test CasperJS sunt create cu JavaScript. Cel mai ușor mod de a accesa o instanţă casper este folosirea metodei create() din modulul casper:
var casper = require('casper').create();
O metodă alternativă de a obţine o instanţă casper este prin obținerea funcţiei principale main și instanţierea ei directă:
var casper = new require('casper').Casper();
Atât funcţia create(), cât și constructorul Casper primesc un singur argument, și anume un obiect JavaScript standard. Iată un exemplu documentat:
var casper = require('casper').create({
// log messages will be printed out to the console
verbose : true,
// only "debug" level messages will be logged
logLevel : 'debug',
viewportSize: {
// override default browser windows size
width: 1024,
height: 768
},
pageSettings: {
//The WebPage instance used by Casper will use these //settings
"loadImages" : true,
"loadPlugins" : true,
"webSecurityEnabled" : false,
"ignoreSslErrors" : true
}
});
Parametrii și opţiunile se explică de la sine și sunt ușor de utilizat și de schimbat de cei care nu cunosc programare.
CasperJS oferă două module principale, modulul casper și modulul tester. Modulul casper se axează pe simplificarea interacţiunii cu browserul. Aceste module facilitează automatizarea navigării și procesul de scriere a testelor, oferind metode high-level pentru efecutarea diverselor sarcini, precum:
Definirea și ordonarea pașilor de navigare în browser;
Completarea și depunerea de formulare;
Accesarea linkurilor;
Realizarea de capturi de imagine pentru o pagină întreagă sau pentru segmente din ea;
Testarea DOM la distanţă;
Logarea de evenimente ;
Descărcarea de resurse inclusiv cele binare;
Scrierea de suite de teste funcţionale și salvarea rezultatelor ca JUnit XML ;
Un script CasperJS este organizat ca o serie de pași. La apelul metodei then()
, funcţia trimisă ca parametru este pusă într-o coadă de așteptare. După ce toţi pașii de navigare au fost definiţi, iar metoda run()
a fost apelată, toate funcţiile aflate în coada de așteptare sune executate secvenţial. CasperJS utilizează flaguri pentru a detecta dacă pasul următor trebuie să aștepte ca un predecesor să finalizeze procesul.
Prin utilizarea API oferit de CasperJS, dezvoltatorul de script poate defini scenarii de navigare, deci să interacţioneze cu o aplicaţie web ca un utilizator obișnuit. Testele și scenariile de navigare pot fi scriptate și executate în mod repetat, simulând studii de caz din "lumea reală".
Scrapingul unei pagini web, care încarcă toate resursele unui URL, oferă un exemplu relativ simplu. Bucata de script de mai jos ar putea fi salvată într-un fișier numit firstexample.js. În acest exemplu, linkurile de pe pagina oficială CasperJS, trec prin procesul scrap și sunt printate de la consolă.
var casper = require('casper').create();
var links;
function getLinks() {
var links = document.querySelectorAll(
'ul.navigation li a');
return Array.prototype.map.call(links,
function (e) {
return e.getAttribute('href')
});
}
casper.start('http://casperjs.org/');
casper.then(function () {
links = this.evaluate(getLinks);
});
casper.run(function () {
for(var i in links) {
console.log(links[i]);
}
casper.done();
});
Scriptul face următoarele acţiuni:
Nr. linie | Descriere |
---|---|
1 | Această linie creează o nouă instanţă |
casper. Modulul casper module este | |
încărcat, iar metoda create() | |
returnează o instanţă a clasei Casper. | |
--------------- | ------------------------------------------ |
3-8 | Instrucţiunile fac scrap la |
linkurile de pe pagină. În linia 4 | |
DOM-ul paginii este interogat și toate | |
referinţele și elementele din listă sunt | |
recuperate. În linia 5 este creat un | |
tablou (mulţime de elemente) invocând o | |
funcţie pentru fiecare element al | |
referinţelor recuperate. În linia 6, | |
funcţia anonimă (invocată pe un element | |
de tip link) returnează conţinutul | |
unui atribut href. | |
--------------- | ------------------------------------------ |
9 | Metoda start() va porni Casper și va |
încărca pagina de pornire a CasperJS. | |
--------------- | ------------------------------------------ |
10-12 | Metoda then() este modul standard de a |
adăuga un nou pas de navigare în coadă. | |
Are doar un parametru, o funcţie simplă. | |
În acest caz, funcţia va evalua | |
link-urile. Metoda evaluate() va | |
executa funcţia getLinks(). | |
--------------- | ------------------------------------------ |
13-18 | În linia 13, metoda run() este |
apelată, ceea ce execută întreaga stivă | |
de comenzi. Are un singur parametru, o | |
funcţie callback care este executată | |
când coadă pașilor anteriori este | |
finalizată. În acest caz, funcţia | |
callback oferă ca rezultat linkurile | |
liniilor 14-15. | |
--------------- | ------------------------------------------ |
Pentru a rula acest script, scrieţi următoarea comandă:
casperjs firstexample.js
Următorul script este complex, reprezentând un scenariu de navigare pentru un utilizator care vrea să calculeze condiţiile unui împrumut folosind pentru aceasta o aplicaţie web specializată:
"use strict";
var fs = require('fs');
var utils = require('utils');
var webserver = require('webserver');
var testclientURL = 'http://www.exampledomain.com/testclient.htm';
// Create CasperJS instance
var casper = require('casper').create({
verbose : true,
logLevel : 'debug',
viewportSize: {
width: 1024,
height: 768
},
pageSettings: {
"loadImages" : true,
"loadPlugins" : true,
"webSecurityEnabled" : false,
"ignoreSslErrors" : true
}
});
// Read file path
var filePath = casper.cli.get(0);
// Read request data
var requestHeader = fs.read('request-header.txt');
var requestFooter = fs.read('request-footer.txt');
// Define global variables
var request;
var productNumber = 0; // -1 means no product selection, otherwise it's the position in the list counting from 0
// Register error handler
casper.on('complete.error', function(err) {
this.die("Complete callback has failed: " + err);
});
casper.on('error', function(msg, err) {
this.die(msg + " -> " + err);
});
/*
* Initializes the test procedure by login a "login" page, altering the request form
* and sending the data to the server to perform the login
* @param casper The casper instance to add steps to
* @param url The URL to load the login page fromCharCode
* @param request The complete request to send to MARZIPAN
*/
function startTestcase(casper, url, request) {
// Create directory to log screenshots to
fs.makeDirectory('log');
// Loading first form
casper.thenOpen(url);
casper.then(function _saveScreen01() {
this.capture('log/screen01_Test-Client-Form.png');
});
// Updating form input with loaded request
casper.waitForSelector('body > form:nth-child(3)',
function success() {
console.log('[INFO] Filling out login form ...');
this.fill('body > form:nth-child(3)', {
'CALLINTERFACE' : request
}, true);
},
function fail() {
console.log('[ERROR] Login Form not found');
}
);
}
/*
* Method selects a product form the list of available products.
* @param casper The CasperJS instance to add steps to
* @param productNumber The number of the product list to select
*/
function selectProduct(casper, productNumber) {
var productSelection = '#SUBMIT__produktListe_' + productNumber +
'_formName_' + productNumber + '__common_ladeProduktFuerImport';
casper.wait(4000);
// Select product
casper.waitForSelector(productSelection,
function success() {
console.log('[INFO] Selecting product...');
this.capture('log/screen02_Produktauswahl.png');
this.click(productSelection);
},
function fail() {
this.capture('log/screen02_Produktauswahl_err.png');
console.log('[ERROR] Product not found');
}
);
}
/*
* This method fills a form of data to manually enter data
* @param casper The CasperJS instance to add steps to
*/
function fillForm(casper) {
// Fill the form
casper.waitForSelector('#content-area > form:nth-child(1)',
function success() {
console.log('[INFO] Filling out form...');
this.capture('log/screen03_Eingabemaske.png');
this.sendKeys('input[name="zinsbindungslaufzeit"]', '10');
this.sendKeys('input[name="auszahlungsdatum"]', '17.08.2016');
this.sendKeys('input[name="rate"]', '900');
this.sendKeys('input[name="effektivzins"]', '2');
this.capture('log/screen04_EingabemaskeAusgefuellt.png');
},
function fail() {
this.capture('log/screen03_Eingabemaske_err.png');
console.log('[ERROR] Form not found');
}
);
}
/*
* Presses the "berechen Nominalzins" Button
* @param casper The CasperJS instance to add test steps to
*/
function pressBerechneNominalzinsAnnuitaetendarlehen(casper) {
// Compute nominal interest rate
casper.thenClick('input[
name="SUBMIT_/aktiv/berechneNominalzinsAnnuitaetendarlehen"]');
casper.waitForSelector(
'#SUBMIT__aktiv_berechneNominalzinsAnnuitaetendarlehen',
function success() {
this.wait(7000, function _saveScreen05() {
this.capture('log/screen05_NominalzinsBerechnet.png');
});
},
function fail() {
this.capture('log/screen05_NominalzinsBerechnet_err.png');
console.log('[ERROR] Failed to calculate nominal interest rate');
}
);
}
/*
* Presses the "Ruecksenden" Button
* @param casper The CasperJS instance to add test steps to
*/
function pressSubmitRuecksendenGeschaeft(casper) {
// Select Rücksprung
casper.thenClick('input[
name="SUBMIT_/aktiv/ruecksendenGeschaeft"]', function _saveScreen06()
{
this.capture('log/screen06_Ruecksprung.png');
this.wait(7000, function() {
this.capture('log/screen07_Ergebnis-HTML.png');
});
});
}
// Start CasperJS engine
casper.start();
try {
console.log('[INFO] Considering >> ' + filePath);
// Check whether it's a regular file
if(fs.isFile(filePath)) {
console.log('[INFO] Processing ' + filePath);
console.log('[INFO] Loading request file >> ' + filePath);
request = fs.read(filePath);
// The complete request to send is header + content + footer
var completeRequest = requestHeader + request + requestFooter;
startTestcase(casper, testclientURL, completeRequest);
if(productNumber >= 0 ) {
selectProduct(casper, productNumber);
} else {
console.log('[INFO] Skipping product selection');
}
fillForm(casper);
pressBerechneNominalzinsAnnuitaetendarlehen(casper);
pressSubmitRuecksendenGeschaeft(casper);
console.info('[INFO] Testcase finished');
} else {
console.log('[INFO] Ignoring '+ filePath+' as it is no regular file');
}
} catch(err) {
console.log('[ERROR] ' + err);
}
// Execute the chain of steps and exit on success
casper.run(function () {
this.exit();
});
Primul pas pentru a calcula condiţiile împrumutului este partea de login. Acest lucru se realizează modificând formularul de cerere și trimiţând datele către server pentru a efectua loginul (vezi linia 156). Cererea completă ce trebuie transmisă serverului conţine trei părţi: antetul cererii, conţinutul și subsolul cererii. Toate aceste părţi sunt citite din fișiere (vezi liniile 24, 25 și 153). Calea către conţinutul cererii este dată sub forma unui parametru de linie de comandă și este accesat în linia 21. După un login de succes, se selectează un produs (vezi linia 159). Apoi, un formular este completat cu niște constrângeri precum data plăţii, renta, dobânda efectivă (vezi liniile 98, 99, 100). În pasul următor, se calculează dobânda nominală (vezi linia 164). În ultimul pas, se apasă butonul "Rücksprung" (vezi linia 165), iar scenariul de navigare se termină.
Pentru a urmări ducerea la bun sfârșit a pașilor de navigare, se folosește un mecanism de captură de ecran, de exemplu în liniile 49, 77, 82.
Presupunând că scriptul se salvează într-un fișier numit navigationScript.js, se execută următoarea comandă:
casperjs navigationScript.js C:/requestContent.txt
CasperJS oferă o gamă utilă de funcţii și declaraţii. De obicei, componentele de bază ale fișierului de test CasperJS sunt:
Declararea suitei de teste: conţine o descriere, un număr de teste și codul însuși;
Pașii funcţionali: codul din interiorul suitei - acţiuni pe website și declaraţii;
Iată un script simplu pentru testare:
casper.test.begin('Testing Google', 1, function(test){
casper.start('http://google.com');
casper.then(function(){
test.assertTitle('Google',
'Google has correct title');
});
casper.run(function(){
test.done();
})
});
Scripturile de testare sunt diferite de cele de tip scraping, deși au în comun majoritatea API-ului. O diferenţă este că scripturile de testare nu necesită crearea unei instanţe casper. Acestea trebuie rulate având cuvântul cheie 'test' în linia de comandă. Subcomanda casperjs test configurează un mediu de testare pentru voi, deci un obiect casper va fi disponibil în scriptul vostru de testare.
Presupunând că scriptul se salvează într-un fișier numit firsttest.js, trebuie să scrieţi următoarea comandă pentru a-l rula:
casperjs test firsttest.js
În linia 1, accesul la o instanţă a clasei Tester, oferită de CasperJS, se face din oficiu prin proprietatea test a oricărei instanţe de clasă Casper. Apoi, se apelează metoda begin, trecând prin trei argumente: descriere, numărul așteptat de teste care trebuie să ruleze și funcţia callback. În acest caz, va porni o suită care conţine un test. Funcţia callback, al treilea parametru, va lua instanţa Tester curentă drept prim argument.
În linia 2, utilizând metoda start(), se încarcă pagina web a Google.
În linia 4 una dintre declaraţii, assertTitle, este folosită pentru a verifica că titlul este echivalent cu cel așteptat. Primul parametru este valoarea așteptată, al doilea parametru este opţional și este mesajul care va fi afișat după ce declaraţia este realizată.
Metoda done() trebuie apelată pentru finalizarea suitei. Apelul a fost plasat în funcţia callback a metodei run(), vezi linia 7. Acest lucru presupune că va fi executată după finalizarea pașilor anteriori.
CasperJS este un tool util pentru testarea automată a aplicaţiilor web, fiind foarte rapid și ușor de instalat. Pune la dispoziţie metode high-level care facilitează acţiuni click pe butoane, completarea formularelor, efectuarea de capturi de ecran pentru o pagină sau pentru părţi ale unei pagini, descărcarea de resurse, scraping de conţinut Web. Are un API bine definit și o documentaţie foarte utilă, clară și concisă.
Dacă doriţi să automatizaţi interacţiunea cu o pagină web sau să o testaţi, CasperJS este în mod cert o alegere foarte bună.
de Mihai Varga
de Călin Diniș , Mihai Tentis