1. Introducción

    En el desarrollo de aplicaciones web, muchas veces nos encontramos con casos en que es necesario hacer algún tipo de pruebas repetitivas sobre una interfaz de nuestra aplicación. Estas pruebas pueden tomar una buena cantidad de tiempo y aún más si tenemos que probar varias veces mientras hacemos integraciones o nuevas funcionalidades.

    Para estos casos se vuelve necesario programar pruebas automatizadas, para ahorrarnos tiempo valioso y que nos permitan verificar nuestras las aplicaciones a medida que avanzamos en el desarrollo. Una de este tipo de herramientas que existe en el mercado es CasperJS.

    CasperJS es un script de navegación de código abierto y utilidad de la prueba escrita en Javascript para el navegador sin cabeza PhantomJSWebKit y SlimerJS ( Gecko ) . Facilita el proceso de definir un escenario de navegación completa y proporciona funciones de alto nivel de utilidad , métodos y azúcar sintáctico para hacer tareas comunes .

  2. Objetivo

    En nuestro caso de estudio queremos lograr implementar una serie de pruebas accediendo a formularios de nuestro sitio realizando diversas pruebas que pueden ser funcionales, de calidad, o algún propósito en especial . A medida que se vaya ejecutando las pruebas, necesitamos guardar un log en un archivo de texto. Adicionalmente queremos guardar capturas de pantalla de cada uno de los pasos que realizamos.

    Nuestro código se pegará a un web service de donde sacaremos la data necesaria para nuestras pruebas. Este web service lo configuraremos en Drupal.

    • Requisitos
    • PhantomJS
    • CasperJS
    • Instalación de Drupal 7
    • Módulo contribuido de Drupal : Services, Ctools
  3. Código

    • Proceso básico:
      1. Iniciar Casperjs
      2. Solicitar al webservice un nodo o elemento, el cual pasará una prueba.
      3. Acceder a la url del nodo(formulario).
      4. Llenar los campos del formulario (hacer pruebas).
      5. Obtener resultados
      6. Actualizar estado de nuestra prueba a drupal via webservice.

    La estructura básica para iniciar un proceso de CasperJS sería la siguiente:

    
    //Declaramos nuestro proceso de casper
    casper.test.begin('mytest', 1, function suite(test)
    {
        // Casper start, como parámetro un host de destino.
        casper.start(HOST_URL, function()
        {
    
        /* Funciones o acciones al iniciar casper*/
        });
    
        /* Grupo de pasos de nuestro proceso
        Cabe resaltar que para que un proceso inicie debe terminar el anterior.
        Para definir un paso usamos las funciones “casper.then”
        */
        casper.then(function step1()
        {
            // Funciones como clicks o navegación entre páginas.
        });
        casper.then(function step2()
        {
            // Funciones como clicks o navegación entre páginas.
        });
    // run our casper
        casper.run();
    });
    

    CasperJS y PhantomJS nos proporcionan variedad de funciones y recursos que podemos utilizar para nuestros propósitos, Aquí los enlaces a sus respectivas documentaciones:

    http://docs.casperjs.org/en/latest/
    http://phantomjs.org/documentation/

    Nosotros crearemos un archivo Utils con funciones que reutilizan las librerías de CasperJS, con ligeras modificaciones para nuestros fines:

    
    /**
    * lib/utils.js
    * Para nuestros propósitos hemos referenciado dentro de options.casper
    * nuestro objeto casper inicial.
    */
    module.exports =
    {
    
        CaptureScreen: function (options, fileName) {
            if (options.ScreenshotsFlag != null) {
                options.ScreenshotsCounter++;
                options.casper.capture('screenshots/' + fileName + '.png', {
                    top: 0,
                    left: 0,
                    width: 1024,
                    height: 2048
                });
            }
        },
    
    //  Usamos casper.wait para aplicar un delay si es necesario
    
        Delay: function (options, seconds) {
            options.casper.wait((seconds * 1000), function () {
            });
        },
    
    // casper.die termina nuestro proceso y nos devuelve un mensaje
    
        Die: function (options, message) {
            this.CaptureScreen(options, 'error');
            options.casper.die('[PROCESS_FAIL] - ' + message);
        },
    
    // Funciones custom para el completado de formularios, se usa las funciones de casper:
    // senKeys, FillSelectors, waitForSelector entre otras.
    
        FillTextInput: function (options, inputId, inputValue) {
            if (inputValue != null) {
                options.casper.sendKeys(inputId, inputValue.toString(), {'reset': true});
            }
        },
    
    //  Funciones que interactúan con nuestro webservice, usamos evaluate para enviar un
    //  llamado ajax al web service con metodos put or get.
    
        call_ws: function (path, method, data) {
            return this.options.casper.evaluate(function (ws_endpoint, path, method, data) {
                return JSON.parse(__utils__.sendAJAX(ws_endpoint + path, method, data, false));
            }, {
                ws_endpoint: this.options.ws_endpoint,
                path: path,
                method: method,
                data: data
            });
        },
        get_queue: function () {
            return this.call_ws("/domain_queue/next");
        },
        update_queue: function(nid, data){
            return this.call_ws("/domain_queue/" + nid, "PUT", data);
        }
    };
    

    Ahora nuestra estructura final de CasperJS, Sería la siguiente:

    Para comenzar incluiremos nuestra librería utils, y la librería utils de casper, definimos nuestras variables.

    
    // casper-test.js
    /* INCLUDES */
    var Utils = require('./lib/utils');
    var casperutils = require('utils');
    
    /* VARIABLES */
    var HOST_URL = 'http://mypage.dev/';
    var options;
    var ws_endpoint = 'http://mypage.dev/api/v1';
    var selectors ={
        'form_link' : '.menu-1168 a'
    };
    var service;
    

    Según la estructura base iniciaremos casper definiendo options, con argumentos y variables que usaremos en todo el proceso,

    
    casper.test.begin('mytest', 1, function suite(test)
    {
        casper.start(HOST_URL, function()
        {
            options = {
                // Llamada recursiva a nuestro objeto casper para ser utilizado en otras funciones
                'casper': this,
                'ScreenshotsFlag': true,
                'ScreenshotsCounter': 0,
                'Utils': Utils
            };
            Utils.CaptureScreen(options, options.ScreenshotsCounter);
        });
    

    Primer paso , obtener la data de nuestro web service, se pueden definir then() dentro de otro de ser necesario, en caso nuestro webservice retorne un valor falso o empty usamos casper.die para terminar nuestro proceso.

    
        casper.then(function get_webservice()
        {
            service = Utils.get_queue();
    
            /* dump function allow to see objects on our console.*/
            casperutils.dump(service);
            if(service.result == 'empty'){
                options.casper.die('[PROCESS_FAIL] - Queue is empty');
            }
    

    Si en caso el webservice nos devuelve data, accedemos al formulario con casper.thenOpen

    
            else{
                Utils.CaptureScreen(options, options.ScreenshotsCounter);
                casper.thenOpen(service.url ,function access_form(){});
    

    Nuestro siguiente paso , el llenado del formulario, usaremos nuestras funciones custom , cabe resaltar que casperJS nos facilita esta labor con sus funciones predefinidas.

    
                casper.then(function submit_form()
                {
                    Utils.FillTextInput(
                    options,
                    '#edit-submitted-name',
                    'Roberto Cardenas');
    
                    Utils.FillTextInput(
                        options,
                        '#edit-submitted-email',
                        // Force to error with email format;
                        'wrong_email');
                        // En algunos casos necesitamos un delay durante el llenado de un formulario
                        Utils.Delay(5);
    
                    Utils.FillTextInput(
                        options,
                        '#edit-submitted-edad',
                        '20');
    
                     // Hacemos un submit al formulario mediante ajax.
    
                    var submit_selector =  '.webform-submit';
                    var result = casper.evaluate(function (submit_selector) {
                        jQuery(submit_selector).trigger('click');
                        return jQuery(submit_selector).html();
                    }, submit_selector);
    

    La variable result nos devuelve un resultado a nuestra petición de formulario, si esta no existe podemos determinar que nuestra prueba falló

    
                /* We can test based on result variable.*/
                    options.test_status = (typeof result === 'undefined') ? 'failed': 'tested';
                    Utils.CaptureScreen(options, options.ScreenshotsCounter);
                });
    

    Finalmente actualizamos en drupal nuestros resultados, mediante nuestro webservice.

    
            casper.then(function update_test_status()
            {
                var data = {};
                data.status = options.test_status;
                var nid  = service.nid;
                var result = Utils.update_queue(nid, data);
             });
        }});
    
        casper.run()function(){
            test.done();
        };
    });
    

    Como se mencionó antes utilizaremos un Web service en drupal para solicitar una cola de pruebas y posteriormente actualizar su estado. Para esto debemos tener instalado el módulo Services, y crear nuestros propios recursos, Aquí un ejemplo de como hacerlo:

    
    /**
    * Retrieves the new element for test.
    */
    function mymodule_queue_retrieve() {
        $result = _module_actions('testing_table', 'pop');
        if(!empty($result)){
            $element = array(
                'nid' => $result->nid,
                /* More custom vars for our test. */
            );
            $return = $element;
        }
        else{
            $return = array('result' => 'empty');
        }
    }
    
    /**
    * Updates element after testing.
    */
    function mymodule_queue_update($nid, $data){
        $query = db_update('testing_table');
        $query->fields(array(
            'status' => $data['status'],
        ))
        ->condition('nid', $nid, '=')
        ->execute();
    }
    
  4. Conclusiones:

    Según la página oficial de CasperJS estas son las ventajas que nos ofrece:

    • Definir y ordenar los pasos de navegación de navegación
    • Llenar y enviar formularios
    • Clic y siguientes enlaces
    • Captura de pantalla de una página (o parte de ella )
    • Pruebas DOM remoto
    • Eventos de registro
    • La descarga de recursos , incluidos los binarios
    • La escritura de pruebas funcionales , guardar los resultados.

    CasperJS es una herramienta muy potente y facil de implementar, con infinidad de funciones y herramientas predefinidas, gracias a la cual podemos hacer pruebas a nuestras aplicaciones como:

    • Crear automatizaciones de pruebas en masa.
    • Pruebas funcionales.
    • Pruebas de eficiencia.
    • Pruebas contra bots que pueden acceder a nuestras páginas web.
    • Interactuar con un Web service para obtener logs , capturas de pantallas y resultados de estas pruebas.

    Por último decir que este caso de estudio fue implementado con conocimientos básicos sobre CasperJS, sin embargo gracias a su usabilidad y potencia, me impulsa a investigar a fondo y desarrollar scripts mas avanzados para lograr herramientas potentes en el uso de testing de aplicaciones web.