Ajax tests con Selenium: prototype.js e ICEfaces.

1
15544

Ajax tests con Selenium: prototype.js e ICEfaces.

0. Índice de contenidos.

1. Introducción

En este tutorial vamos a hablar de cómo escribir tests funcionales con Selenium IDE sobre aplicaciones que realizan recargas controladas de la interfaz de usuario
con Ajax.

Si no estáis familiarizados con la tecnología, podéis encontrar, en adictos, tutoriales sobre Selenium y Ajax. En la redacción de este tutorial se da por hecho que el lector tiene experiencia con ambas, además de conocimientos de prototype.js e ICEfaces.

2. Entorno.

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil Asus G1 (Core 2 Duo a 2.1 GHz, 2048 MB RAM, 120 GB HD).
  • Sistema operativo: Windows Vista Ultimate.
  • Firefox 3.5, con Selenium IDE 1.0.2.
  • Prototype.js 1.6.0.3.
  • ICEfaces 1.8.0.

3. waitForCondition.

No existe, en la fecha de publicación de este tutorial, un comando específico de Selenium para esperar la respuesta a una petición Ajax.
Para ello se usa el comando waitForCondition que recibe como parámetro una expresión javascript que será evaluada, durante un tiempo determinado, hasta que devuelva «true».

Dicha expresión:

  • acepta más de una línea, si bien solo será evaluado el resultado de la última.
  • no se evalua en el entorno de la ventana de la aplicación, con lo que, para acceder a objetos de nuestra ventana, debemos utilizar selenium.browserbot.getCurrentWindow()
    y, si queremos acceder al javascript de la página, mejor selenium.browserbot.getUserWindow().

Vamos a tomar como referencia el ejemplo que se plantea en el tutorial de recarga controlada de selectores con prototype para llevar a cabo un test de su funcionalidad.
La interfaz de usuario presenta dos selectores, un evento de cambio de valor sobre el primero provoca una recarga del contenido del segundo, y dicha recarga se realiza en segundo plano, sin recargar la página completa, a través del
soporte para Ajax que nos proporciona prototype.js.

En el ejemplo la petición se realiza de forma síncrona, con lo que no sería necesario ningún comando de espera, puesto que el navegador ya se encarga de esperar la respuesta del servidor,
con lo que, a efectos de realizar la prueba, debemos modificar el parámetro que asigna el tipo de conexión, indicado que la misma será asíncrona.

La función que realiza la recarga, antes de enviar la petición, limpia el segundo selector, dejando su número de options a cero. Nos aprovecharemos de dicha «feature» para conocer cuándo se ha producido la respuesta,
cuando el número de options del select sea distinto mayor que cero se ha habrá producido la misma.

La siguiente secuencia de comandos muestra cómo se podría realizar un test de dicha funcionalidad:

Command Target Value
open http://localhost:8080/prototypeAjax/
assertTitle Recarga de selectores
select provincias label=Madrid
waitForCondition selenium.browserbot.getCurrentWindow().document.getElementById(‘poblaciones’).options.length > 0 5000
select poblaciones label=Alcobendas

El comando waitForCondition accede al selector de poblaciones, para comprobar su contenido, esto es, el número de options. Cuando se produce la respuesta Ajax, dicho selector se pobla con el contenido de la misma y deja de tener una longitud de cero.

El segundo parámetro del comando (columna de Value) es el tiempo de timeout máximo, durante el cual se repetirá la evaluación de la condición hasta que:

  • la expresión devuelva «true», en cuyo caso, se continuará con el siguiente comando,
  • expire el tiempo de timeout, terminando la ejecución del TestCase con un error.

Esta solución es perfectamente válida, si bien, requiere de un análisis individual de cada uno de los eventos que producen una recarga, vía Ajax, en nuestra aplicación,
para buscar una condición javascript que pueda ser usada y evaluar cuando se reproduce la respuesta.

4. Una solución mejor: basada en prototype.js.

Vamos a intentar evitar la necesidad de buscar una condición individual que evalue cuando se produce la respuesta, de modo que no tengamos que ir caso por caso, analizando los eventos Ajax que se producen en nuestra aplicación.

La mejor manera de llevarlo a cabo es bajar al nivel del framework que maneja las conexiones, en nuestro caso prototype.js y lo podemos hacer de dos maneras distintas:

  • enganchándonos al ciclo de vida de la petición Ajax, de modo que programemos una función de callback que sea invocada al inicio y final de cada petición,
    (podéis ver un ejemplo de registro vía ‘onComplete:’ en este post) o
  • accediendo a la variable que registra el número de conexiones activas.

En este caso, vamos a optar por la segunda de las opciones por su sencillez, y, sobre la base del mismo ejemplo podríamos programar la siguiente secuencia de comandos:

Command Target Value
open http://localhost:8080/prototypeAjax/
assertTitle Recarga de selectores
select provincias label=Madrid
waitForCondition selenium.browserbot.getUserWindow().Ajax.activeRequestCount == 0 5000
select poblaciones label=Alcobendas

Ahora sí, la condición es genérica y, como tal, susceptible de incorporarse a un comando personalizado que pueda ser invocado en cualquiera de nuestros scripts de Selenium,
como si de un comando propio del IDE se tratase.

5. Una solución mucho mejor: waitForAjaxRequest.

Selenium es plugable, esto es, ofrece la posiblidad de incluir nuestros propios scripts con comandos personalizados.

Basándonos en la condición anterior podemos crear la siguiente función:

Para poder hacer uso de ella basta con incluirla en un fichero .js y añadirlo al entorno del IDE, en el menú de opciones:

Opciones de Selenium

Será necesario cerrar y volver a abrir la ventana del IDE, para que lea el fichero, y ya tendremos nuestro comando dentro del catálogo de disponibles.

waitForAjaxRequest

Siguiendo con el mismo ejemplo, la secuencia de comandos quedaría como sigue:

Command Target Value
open http://localhost:8080/prototypeAjax/
assertTitle Recarga de selectores
select provincias label=Madrid
waitForAjaxRequest 5000
select poblaciones label=Alcobendas

Mucho más limpio, ni que decir tiene que si pasamos a utilizar otro framework que gestione las conexiones vía Ajax, el único punto de colisión sería nuestro script.

6. Testing ICEfaces partialSubmit.

Conociendo que ICEfaces hace uso extensivo de prototype.js para la recarga del árbol DOM en cliente
(Direct-to-DOM rendering), sería de esperar que hiciesen también uso del soporte para Ajax que proporciona dicha librería, máxime cuando distribuyen el código fuente.

Prototype en ICEfaces

Prototype en ICEfaces

Si fuese así, podríamos hacer uso del comando personalizado que hemos creado en el punto anterior para cubrir los tests de partialSubmit de ICEfaces. Sin embargo, el número de conexiones siempre está a cero, la variable no se incrementa

ICEfaces ha reimplementado el soporte de Ajax para escribir el suyo propio, no hacen uso del de prototype.js.
La buena noticia es que, a partir de la versión 1.8.0, proporciona un api de javascript para engancharse al ciclo de vida de las peticiones, de modo que podemos hacer uso de las siguientes funciones:

  • Ice.onSendReceive(id, sendCallback, receiveCallback): registra un método de callback para el envío y la respuesta de una petición síncrona con el servidor,
  • Ice.onAsynchronousReceive(id, callback): registra un método de callback para la respuesta de una petición asíncrona del servidor.

El parámetro id se correponde con el identificador del elemento del árbol padre del contenedor del puente de comunicaciones entre el cliente y el servidor: en la mayoría de los casos será el body.

El resto son funciones de callback que serán invocadas cuando se produzcan los eventos.

Hay que advertir que existe un error en la documentación, o mejor en la implementación, puesto que la función Ice.onAsynchronousReceive,
en realidad se ha implementado como onAsyncronousReceive: ICE-4348

Con todo lo antes dicho, podemos escribir un comando personalizado que se enganche al ciclo de vida de las peticiones de ICEfaces para comprobar cuando existe una activa.
Ahora sí, siguiendo la filosofía que se recomienda en este post, con alguna variación, podemos programar un comando como el que sigue:

El comando se invocaría con waitForICEfacesAjaxRequest, que registraría los métodos de callback en la primera invocación tanto para las conexiones síncronas como para las asíncronas.

La siguiente secuencia de comandos muestra cómo se podría realizar un test sobre un formulario de alta de una entidad (contactos), en el que la validación de los campos de entrada se realiza con partialSubmit:

Command Target Value
focus contactForm:name
focus contactForm:surname
waitForICEfacesAjaxRequest 5000
assertText contactForm:nameMsg Campo obligatorio
focus contactForm:name
waitForICEfacesAjaxRequest 5000
assertText contactForm:surnameMsg Campo obligatorio

7. Conclusiones.

Los tests deben forman parte del proceso de desarrollo y, a este nivel, los tests funcionales son la respuesta ideal para testar la aplicación desde el entorno del cliente.
Ahora no podemos vivir sin ellos y como las aplicaciones toman mayor complejidad a nivel ténico tenemos que adaptarnos.

El uso extensivo de Ajax, que llevan a cabo librerías de componentes JSF como ICEfaces o RichFaces, nos hace inevitable que, si queremos probarlas, tengamos que seguir investigando en este sentido.

Y recuerda, los tests son los mejores amigos del desarrollador puesto que demuestran que no solo has concluido con tu trabajo, sino que, además, funciona 😉

Un saludo.

Jose

mailto:jmsanchez@autentia.com

1 COMENTARIO

  1. Hola.
    Hice exactamente lo explicado en el punto 5 y me sale este error en el ide de selenium
    [error] selenium.browserbot.getUserWindow().Ajax is undefined
    Podrías ayudarme a corregir ese error?

DEJA UNA RESPUESTA

Por favor ingrese su comentario!

He leído y acepto la política de privacidad

Por favor ingrese su nombre aquí

Información básica acerca de la protección de datos

  • Responsable:
  • Finalidad:
  • Legitimación:
  • Destinatarios:
  • Derechos:
  • Más información: Puedes ampliar información acerca de la protección de datos en el siguiente enlace:política de privacidad