PhantomJS, headless browser per test con Javascript

8 maggio 2012

Il nome stesso, PhantomJS, fa pensare a qualcosa che non c’è. Ed in effetti questo tool consente di compiere operazioni che normalmente vengono fatte con un browser, senza però mostrare il browser stesso.

Si tratta infatti di un cosiddetto headless browser, cioè di un tool che consente la manipolazione via JavaScript del DOM, di CSS, JSON, Ajax altre tecnologie Web client-side da riga di comando, senza alcun rendering a video.

Basato su WebKit, PhantomJS è uno strumento multipiattaforma e può essere utilizzato in tutti quei contesti in cui si ha bisogno di automatizzare le tipiche attività di un Web browser, ma non solo.

Per fare qualche esempio, esso può essere utilizzato per attività di Web scraping, per l’automazione di test su siti ed applicazioni Web, per il monitoraggio di rete, ma anche per il rendering in SVG, l’interfacciamento con servizi Web ed addirittura per la creazione di un semplice Web server.

Installare PhantomJS

PhantomJS è un tool multipiattaforma: i pacchetti per Linux, Windows e Mac sono scaricabili insieme ai sorgenti rilasciati sotto licenza BSD.

Per installarlo è sufficiente copiare il contenuto del pacchetto in una cartella ed invocare l’eseguibile phantomjs da riga di comando con la seguente sintassi:

phantomjs [opzioni] nomeScript [argomentiScript]

Gli script che PhantomJS esegue possono essere scritti in JavaScript o in CoffeeScript.

Il primo script con PhantomJS

Come è tradizione, proviamo a creare il classico Hello world! in JavaScript da far eseguire a PhantomJS:

console.log("Hello world!");
phantom.exit();

Salvando il codice in un file helloWorld.js ed eseguendo il seguente comando da riga di comando:

phantomjs helloWorlds.js

otterremo a video il classico saluto iniziale.

Se vogliamo scrivere a video una stringa generica passata come argomento allo script possiamo fare come mostrato di seguito:

if (phantom.args.length != 0) {
  console.log(phantom.args[0]);
} else {
  console.log("Inserisci una stringa come argomento");
}
phantom.exit();

L’oggetto globale phantom mette a disposizione una serie di funzionalità dell’ambiente di esecuzione tra le quali l’array args è un esempio autoesplicativo.

Come altro esempio abbiamo utilizzato il metodo exit(returnValue), che consente di uscire dal programma passando eventualmente un codice di ritorno. Negli script è necessario invocare esplicitamente phantom.exit() per terminare l’esecuzione, altrimenti PhantomJS rimane attivo.

Gestire pagine Web

Proviamo a realizzare uno script con una valenza un po’ più concreta accedendo all’home page di HTML.it e riportando il titolo e la sua descrizione.

Per questo obiettivo utilizzeremo l’oggetto WebPage che consente di effettuare tutte le operazioni di gestione di una pagina Web che faremmo con un browser. Ad esempio, il seguente codice carica l’home page di HTML.it:

var page = new WebPage();

page.open("http://www.html.it");

Ovviamente con queste istruzioni non facciamo altro che caricare il DOM della pagina Web, ma non abbiamo nessun effetto visivo.

Possiamo intercettare gli eventi di inizio e fine caricamento della pagina associando delle funzioni di callback come di seguito mostrato:

var url = 'http://www.html.it';
var page = new WebPage();

page.onLoadStarted = function () {
  console.log("Inizio caricamento...");
};

page.onLoadFinished = function (status) {
  console.log("Fine caricamento...");
  phantom.exit();
};

page.open(url);

In questo modo visualizzeremo un messaggio che ci avvisa quando sta iniziando il caricamento ed uno che comunica il suo completamento. Da notare come l’invocazione di phantom.exit() avvenga all’interno della funzione di fine caricamento.

Se la invochiamo nel flusso di esecuzione principale, ad esempio subito dopo page.open(url), non vedremo alcun messaggio a video in quanto il caricamento avviene in forma asincrona e quindi l’uscita da PhantomJS avverrebbe verosimilmente prima del caricamento della pagina remota.

Una volta caricata una pagina Web, abbiamo accesso al DOM risultante sfruttando il metodo evaluate() dell’oggetto WebPage. Questo metodo consente di eseguire una funzione all’interno del contesto della pagina caricata. Nel nostro caso dunque, per catturare il titolo e la descrizione della pagina possiamo scrivere una funzione come la seguente:

function getInfo() {
  var info = {};
  info.titolo = document.title;
  info.descrizione = document.getElementsByName("description")[0].content
  return info;
}

e richiamarla come mostrato di seguito:

page.onLoadFinished = function (status) {
  var info;
  console.log("Fine caricamento...");
  info = page.evaluate(getInfo)
  console.log("Titolo della pagina: " + info.titolo);
  console.log("Descrizione della pagina: " + info.descrizione);
  phantom.exit();
};

Il metodo evaluate() esegue la funzione getInfo() nel contesto della pagina caricata e restituisce il relativo risultato.

Da notare come la chiamata a console.log() per visualizzare le informazioni relative al DOM debba avvenire fuori dal contesto della pagina, cioè non all’interno della funzione getInfo(). Ciò dipende dal fatto che il codice JavaScript di getInfo() viene eseguito in una sandbox che impedisce l’accesso a qualsiasi oggetto esterno al contesto della pagina.

Includere script esterni

Nella gestione del DOM di una pagina Web, PhantomJS prevede la possibilità di caricare script esterni. Una prima possibilità è data dall’inclusione di uno script remoto tramite il metodo includeJs() dell’oggetto WebPage. Ad esempio, è possibile includere jQuery dai server di Google in questo modo:

page.includeJs("http://ajax.googleapis.com/ajax/libs/jquery/1.7.1/jquery.min.js",
               function() {
			     //Sfrutta jQuery per manipolare il DOM della pagina caricata
			   });

Il primo parametro della funzione indica l’URL che punta allo script mentre il secondo parametro specifica la funziona da eseguire in seguito all’avvenuto caricamento dello script.

Per includere invece uno script locale occorre utilizzare il metodo injectJs():

page.injectJs("jquery.min.js");

In questo caso si suppone che il file jquery.min.js si trova nella stessa cartella dello script in esecuzione.

Moduli

Alcune delle funzionalità di PhantomJS sono organizzate in moduli, cioè in unità di codice che possono essere caricate dinamicamente. Il caricamento di un modulo viene effettuato tramite la funzione require(), come mostra il seguente esempio:

var mioModulo = require("nomeModulo");

Allo stato attuale sono previsti tre moduli: webpage, fs e webserver.

Il modulo webpage consente di accedere alle funzioni di caricamento e manipolazione del DOM, tramite l’oggetto WebPage.

In realtà negli esempi che abbiamo visto finora abbiamo utilizzato l’oggetto WebPage senza aver caricato esplicitamente il relativo modulo. Si tratta di una semplificazione presente per compatibilità con le versioni precedenti di PhantomJS. La procedura regolare per accedere all’oggetto WebPage sarebbe quella di seguito mostrata:

var moduloWebPage = require("webpage");
var page = moduloWebPage.create();

Il modulo fs consente l’accesso al file system con la possibilità di gestire file e cartelle, mentre il modulo webserver consente di realizzare con poche istruzioni un semplice server web, come mostrato dal seguente codice:

var server, service;
server = require('webserver').create();

service = server.listen(8080, function (request, response) {
  response.statusCode = 200;
  response.write('Benvenuto!');
});

Il pacchetto di PhantomJS

Il pacchetto con cui scarichiamo il tool contiene un buon numero di esempi avanzati di utilizzo: dall’esecuzione automatizzata di unit test con QUnit e Jasmine all’interfacciamento con le Weather e Direction API di Google, dal rendering di pagine Web in SVG e PDF alla ricezione degli aggiornamenti Twitter di un dato account.

Alla nostra fantasia il compito di sfruttare a pieno le potenzialità di PhantomJS.

Link utili

Se vuoi aggiornamenti su PhantomJS, headless browser per test con Javascript inserisci la tua e-mail nel box qui sotto:
 
X
Se vuoi aggiornamenti su PhantomJS, headless browser per test con Javascript

inserisci la tua e-mail nel box qui sotto:

Ho letto e acconsento l'informativa sulla privacy

Acconsento al trattamento di cui al punto 3 dell'informativa sulla privacy