Pruebas en JavaScript con PhantomJS
Spanish (Español) translation by CYC (you can also view the original English article)
Creo que no tengo que convencerte de que probar tu código JavaScript es una buena idea. Pero, a veces puede resultar tedioso probar código JavaScript que requiere un DOM. ¿Esto significa que necesitas probar tu código en el navegador y no puedes utilizar la terminal, verdad? Error, de hecho ingresa a PhantomJS.
¿Qué es PhantomJS? Bueno, aquí es una propaganda de la Página Web de PhantomJS:
PhantomJS es un WebKit sin cabeza con la API de JavaScript.
Como ustedes saben, Webkit es el motor que usan Chrome, Safari y algunos otros navegadores de nicho. Entonces PhantomJS es un navegador, pero un navegador sin cabeza. Esto significa que las páginas web renderizadas nunca se muestran realmente. Esto te puede sonar raro; por lo que puedes pensar en él como un navegador programable para la terminal. Veremos un ejemplo simple en un minuto, pero primero tenemos que instalar PhantomJS.
Instalando PhantomJS
La instalación de PhantomJS es realmente muy sencilla: es simplemente un solo binario que puedes descargar y pegar en la ruta de la terminal. Para descargar PhantomJS a través de la página, selecciona tu sistema operativo y descarga el paquete correcto. Luego mueve el archivo binario desde el paquete descargado en un directorio dentro del directorio en la terminal (me gustaría poner algo en ~/bin).
Si estás en Mac OS X, hay una manera más simple de instalar PhantomJS (y este es realmente el método que utilizo). Sólo tienes que utilizar Homebrew, como este:
1 |
brew update && brew install phantomjs |
Ahora debes tener PhantomJS instalado. Puedes comprobar la instalación ejecutando esto:
1 |
phantomjs --version
|
Estoy viendo 1.7.0; ¿Y tú?
Un pequeño ejemplo
Vamos a empezar con un pequeño ejemplo.
simple.js
1 |
console.log("we can log stuff out."); |
2 |
|
3 |
function add(a, b) { |
4 |
return a + b; |
5 |
}
|
6 |
|
7 |
conslole.log("We can execute regular JS too:", add(1, 2)); |
8 |
|
9 |
phantom.exit(); |
Continúa y ejecuta este código emitiendo el siguiente comando:
1 |
phantomjs simple.js |
Deberías ver el resultado de las dos líneas console.log en la ventana de tu terminal.
Sin duda, esto es simple, pero hace un buen punto: PhantomJS puede ejecutar JavaScript al igual que un navegador. Sin embargo, este ejemplo no tiene ningún código específico de PhantomJS... bueno, aparte de la última línea. Esa es una línea importante para cada script PhantomJS porque sale del script. Esto podría no tener sentido aquí, pero recuerda que JavaScript no siempre se ejecuta linealmente. Por ejemplo, es posible que desees colocar la llamada exit() en una función callback.
Veamos un ejemplo más complejo.
Cargando páginas
Usando la API de PhantomJS, podemos cargar cualquier URL y trabajar con la página desde dos perspectivas:
- como JavaScript en la página.
- como un usuario que mira la página.
Comencemos eligiendo cargar una página. Crea un nuevo archivo Script y agrega el siguiente código:
script.js
1 |
var page = require('webpage').create(); |
2 |
|
3 |
page.open('http://net.tutsplus.com', function (s) { |
4 |
console.log(s); |
5 |
phantom.exit(); |
6 |
});
|
Comenzamos cargando el módulo webpage dePhantomJS y creando un objeto de página web. Luego llamamos al método open, pasándole una URL y una función callback; dentro de esta función callback, podemos interactuar con la página real. En el ejemplo anterior, simplemente registramos el estado de la solicitud, proporcionada por el parámetro de la función callback. Si ejecutas esta secuencia de comandos (con phantomjs script.js), debes obtener 'éxito' impreso en la terminal.
Pero hagámoslo más interesante cargando una página y ejecutando JavaScript. Comenzamos con el código anterior, pero luego hacemos una llamada a page.evaluate:
1 |
page.open('http://net.tutsplus.com', function () { |
2 |
var title = page.evaluate(function () { |
3 |
var posts = document.getElementsByClassName("post"); |
4 |
posts[0].style.backgroundColor = "#000000"; |
5 |
return document.title; |
6 |
});
|
7 |
page.clipRect = { top: 0, left: 0, width: 600, height: 700 }; |
8 |
page.render(title + ".png"); |
9 |
phantom.exit(); |
10 |
});
|
PhantomJS es un navegador, pero un navegador sin cabeza.
La función que pasamos a page.evaluate se ejecuta como JavaScript en la página web cargada. En este caso, encontramos todos los elementos con la clase de publicación; luego, establecemos el fondo de la primera publicación en negro. Finalmente, devolvemos el document.title. Esta es una buena característica, devuelve un valor de nuestra devolución de llamada evaluate y lo asigna a una variable (en este caso, title).
Luego, configuramos el clipRect en la página; estas son las dimensiones de la captura de pantalla que tomamos con el método render. Como puedes ver, configuramos los valores top y left para establecer el punto de inicio, y también establecemos un width y height. Finalmente, llamamos a page.render, pasándole un nombre para el archivo (la variable title). Después, terminamos llamando a phantom.exit().
Continúa y ejecuta este script, y deberías tener una imagen que se vea así:


Puede ver ambos lados de la moneda PhantomJS aquí: podemos ejecutar JavaScript desde el interior de la página, y también ejecutar desde el exterior, en la propia instancia de la página.
Esto ha sido divertido, pero no increíblemente útil. Centrémonos en usar PhantomJS cuando probemos nuestro JavaScript relacionado con DOM.
Probando con PhantomJS
Yeoman usa PhantomJS en su procedimiento de prueba, y es virtualmente sin costura.
Para una gran cantidad de código JavaScript, puede probar sin necesidad de un DOM, pero hay ocasiones en que sus pruebas necesitan trabajar con elementos HTML. Si eres como yo y prefieres ejecutar pruebas en la línea de comando, aquí es donde PhantomJS entra en juego.
Por supuesto, PhantomJS no es una biblioteca de prueba, pero muchas de las otras bibliotecas de prueba populares pueden ejecutarse sobre PhantomJS. Como puedes ver en la página wiki de PhantomJS sobre pruebas sin cabeza, los correctores de prueba PhantomJS están disponibles para casi todas las bibliotecas de prueba que desees usar. Veamos cómo usar PhantomJS con Jasmine y Mocha.
Primero, Jasmine y un descargo de responsabilidad: no hay un buen corredor PhantomJS para Jasmine en este momento. Si utilizas Windows y Visual Studio, deberías echarle un vistazo a Chutzpah , y los desarrolladores de Rails deberían probar guard-jazmine. Pero aparte de eso, el soporte de Jasmine + PhantomJS es escaso.
Por esta razón, te recomiendo que uses Mocha para las pruebas relacionadas con DOM.
SIN EMBARGO.
Es posible que ya tengas un proyecto usando Jasmine y quieras usarlo con PhantomJS. Un proyecto, phantom-jazmine, requiere un poco de trabajo para configurarlo, pero debería ser el truco.
Comencemos con un conjunto de pruebas JasmineJS. Descargue el código de este tutorial (enlace en la parte superior) y revise la carpeta jasmine-starter . Verá que tenemos un único archivo tests.js que crea un elemento DOM, establece algunas propiedades y lo agrega al cuerpo. Luego, realizamos algunas pruebas de jazmín para garantizar que el proceso realmente funcione correctamente. Aquí está el contenido de ese archivo:
tests.js
1 |
describe("DOM Tests", function () { |
2 |
var el = document.createElement("div"); |
3 |
el.id = "myDiv"; |
4 |
el.innerHTML = "Hi there!"; |
5 |
el.style.background = "#ccc"; |
6 |
document.body.appendChild(el); |
7 |
|
8 |
var myEl = document.getElementById('myDiv'); |
9 |
it("is in the DOM", function () { |
10 |
expect(myEl).not.toBeNull(); |
11 |
});
|
12 |
|
13 |
it("is a child of the body", function () { |
14 |
expect(myEl.parentElement).toBe(document.body); |
15 |
});
|
16 |
|
17 |
it("has the right text", function () { |
18 |
expect(myEl.innerHTML).toEqual("Hi there!"); |
19 |
});
|
20 |
|
21 |
it("has the right background", function () { |
22 |
expect(myEl.style.background).toEqual("rgb(204, 204, 204)"); |
23 |
});
|
24 |
});
|
El archivo SpecRunner.html es bastante común; la única diferencia es que moví las etiquetas del script al body para asegurar que el DOM se cargue completamente antes de que se ejecuten nuestras pruebas. Puedes abrir el archivo en un navegador y ver que todas las pruebas pasen bien.
Hagamos la transición de este proyecto a PhantomJS. Primero, clona el proyecto phantom-jasmine:
1 |
git clone git://github.com/jcarver989/phantom-jasmine.git |
Este proyecto no es tan organizado como podría ser, pero hay dos partes importantes que necesitas de él:
- el corredor PhantomJS (que hace que Jasmine use un DOM PhantomJS).
- el reporte de la consola Jasmine (que da salida a la consola).
Ambos archivos residen en la carpeta lib; cópialos en jasmine-starter/lib . Ahora debemos abrir nuestro archivo SpecRunner.html y ajustar los elementos <script />. Así es como deberían lucir:
1 |
<script src="lib/jasmine-1.2.0/jasmine.js"></script> |
2 |
<script src="lib/jasmine-1.2.0/jasmine-html.js"></script> |
3 |
<script src="lib/console-runner.js"></script> |
4 |
<script src="tests.js"></script> |
5 |
|
6 |
<script>
|
7 |
var console_reporter = new jasmine.ConsoleReporter() |
8 |
jasmine.getEnv().addReporter(new jasmine.HtmlReporter()); |
9 |
jasmine.getEnv().addReporter(console_reporter); |
10 |
jasmine.getEnv().execute(); |
11 |
</script>
|
Ten en cuenta que tenemos dos reporteros para nuestras pruebas: un reportero HTML y un reportero de la consola. Esto significa que SpecRunner.html y sus pruebas se pueden ejecutar tanto en el navegador como en la consola. Eso es práctico. Lamentablemente, necesitamos tener esa variable console_reporter porque se usa dentro del archivo CoffeeScript que estamos a punto de ejecutar.
Entonces, ¿cómo hacemos para ejecutar estas pruebas en la consola? Suponiendo que estás en la carpeta jasmine-starter en la terminal, aquí está el comando:
1 |
phantomjs lib/run\_jasmine\_test.coffee ./SpecRunner.html |
Estamos ejecutando el script run\_jasmine\_test.coffee con PhantomJS y pasando nuestro archivo SpecRunner.html como parámetro. Deberías ver algo como esto:


Por supuesto, si falla una prueba, verás algo como lo siguiente:


Si planeas usar esto a menudo, puede ser una buena idea mover run\_jasmine\_test.coffee a otra ubicación (como ~/bin/run\_jasmine\_test.coffee) y crear un alias de terminal para todo el comando. Así es como harías eso en un shell Bash:
1 |
alias phantom-jasmine='phantomjs /path/to/run\_jasmine\_test.coffee' |
Solo tira eso en tu archivo .bashrc o .bash_profile. Ahora, sólo puedes ejecutar:
1 |
phantom-jasmine SpecRunner.html |
Ahora tus pruebas de Jasmine funcionan bien en la terminal a través de PhantomJS. Puedes ver el código final en la carpeta jasmine-total en la descarga.
PhantomJS y Mocha
Afortunadamente, es mucho más fácil integrar Mocha y PhantomJS con mocha-phantomjs. Es super fácil de instalar si tienes NPM instalado (lo cual deberías):
1 |
npm install -g mocha-phantomjs |
Este comando instala un binario mocha-phantomjs que usaremos para ejecutar nuestras pruebas.
En un tutorial anterior, te mostré cómo usar Mocha en la terminal, pero harás las cosas de manera diferente al usarlo para probar el código DOM. Al igual que con Jasmine, comenzaremos con un reportero de prueba HTML que se puede ejecutar en el navegador. La belleza de esto es que podremos ejecutar el mismo archivo en la terminal para los resultados de la prueba de la consola con PhantomJS; como pudimos con Jasmine.
Entonces, construyamos un proyecto simple. Crea un directorio de proyecto y muévete a él. Comenzaremos con un archivo package.json :
1 |
{
|
2 |
"name": "project", |
3 |
"version": "0.0.1", |
4 |
"devDependencies": { |
5 |
"mocha": "*", |
6 |
"chai" : "*" |
7 |
}
|
8 |
}
|
Mocha es el framework de prueba, y usaremos Chai como nuestra biblioteca de afirmación. Los instalamos ejecutando NPM.
Llamaremos a nuestro archivo de prueba test/tests.js, y aquí están sus pruebas:
1 |
describe("DOM Tests", function () { |
2 |
var el = document.createElement("div"); |
3 |
el.id = "myDiv"; |
4 |
el.innerHTML = "Hi there!"; |
5 |
el.style.background = "#ccc"; |
6 |
document.body.appendChild(el); |
7 |
|
8 |
var myEl = document.getElementById('myDiv'); |
9 |
it("is in the DOM", function () { |
10 |
expect(myEl).to.not.equal(null); |
11 |
});
|
12 |
|
13 |
it("is a child of the body", function () { |
14 |
expect(myEl.parentElement).to.equal(document.body); |
15 |
});
|
16 |
|
17 |
it("has the right text", function () { |
18 |
expect(myEl.innerHTML).to.equal("Hi there!"); |
19 |
});
|
20 |
|
21 |
it("has the right background", function () { |
22 |
expect(myEl.style.background).to.equal("rgb(204, 204, 204)"); |
23 |
});
|
24 |
});
|
Son muy similares a las pruebas Jasmine, pero la sintaxis de la afirmación de Chai es un poco diferente (así que, no solo copies tus pruebas Jasmine).
La última pieza del rompecabezas es el archivo TestRunner.html:
1 |
<html>
|
2 |
<head>
|
3 |
<title> Tests </title> |
4 |
<link rel="stylesheet" href="./node_modules/mocha/mocha.css" /> |
5 |
</head>
|
6 |
<body>
|
7 |
<div id="mocha"></div> |
8 |
<script src="./node_modules/mocha/mocha.js"></script> |
9 |
<script src="./node_modules/chai/chai.js"></script> |
10 |
<script>
|
11 |
mocha.ui('bdd'); |
12 |
mocha.reporter('html'); |
13 |
var expect = chai.expect; |
14 |
</script>
|
15 |
<script src="test/test.js"></script> |
16 |
<script>
|
17 |
if (window.mochaPhantomJS) { mochaPhantomJS.run(); } |
18 |
else { mocha.run(); } |
19 |
</script>
|
20 |
</body>
|
21 |
</html>
|
Hay varios factores importantes aquí. Primero, observa que esto es lo suficientemente completo como para ejecutarse en un navegador; tenemos el CSS y JavaScript de los módulos nodo que instalamos. Luego, nota la etiqueta de script en línea. Esto determina si PhantomJS está cargado, y si es así, ejecuta la funcionalidad PhantomJS. De lo contrario, se queda con la funcionalidad Mocha en bruto. Puedes probar esto en el navegador y verlo funcionar.
Para ejecutarlo en la consola, simplemente ejecuta esto:
1 |
mocha-phantomjs TestRunner.html |
¡Voilá! Ahora estás ejecutando pruebas en la consola, y todo gracias a PhantomJS.
PhantomJS y Yeoman
Apuesto a que no sabías que el popular Yeoman utiliza PhantomJS en su procedimiento de prueba, y que es vitablemente impensable. Veamos un ejemplo rápido. Asumiré que tienes Yeoman todo configurado.
Crea un nuevo directorio de proyecto, ejecuta yeoman init dentro de él y responde 'No' a todas las opciones. Abre el archivo test/index.html y encontrarás una etiqueta de secuencia de comandos en la parte inferior con un comentario que te indica que la reemplaces con tus propias especificaciones. Ignora por completo ese buen consejo y colócalo dentro del bloque it:
1 |
var el = document.createElement("div"); |
2 |
expect(el.tagName).to.equal("DIV"); |
Ahora, ejecuta la prueba yeoman y verás que la prueba funciona bien. Ahora, abre el archivo test/index.html en el navegador. ¡Funciona! ¡Perfecto!
Por supuesto, hay mucho más que puedes hacer con Yeoman, así que consulta la documentación para obtener más información.
Conclusión
Utiliza las bibliotecas que amplían PhantomJS para simplificar tus pruebas.
Si estás utilizando PhantomJS por sí solo, no hay ninguna razón para aprender sobre PhantomJS; solo puedes saber que existe y usar las bibliotecas que amplían PhantomJS para simplificar tus pruebas.
Espero que este tutorial te haya animado a mirar PhantomJS. Recomiendo comenzar con los archivos de ejemplo y la documentación que ofrece PhantomJS; realmente te abrirán los ojos a lo que puedes hacer con PhantomJS: todo desde la automatización de páginas hasta el rastreo de redes.
¿Por lo tanto, se puede pensar de un proyecto que mejoraría la PhantomJS? Vamos a escuchar acerca de él en los comentarios!



