Banear

domingo, 24 de febrero de 2013

Pruebas de Regresión con Selenium y Wicket con Internet Explorer y Firefox

Introducción


Selenium nos permite, entre otras cosas, grabar pruebas realizadas sobre una aplicación web (las acciones que hacemos desde el navegador) para reproducirlas y repetirlas en un futuro sin necesidad de intervención del usuario (o tester).

Ventajas

  • Una vez grabado el caso de prueba, si queremos repetirlo, simplemente lo reproducimos desde Selenium. Si por ejemplo un caso de prueba nos implica hacer 15 clicks por pantallas y tardamos N minutos, si lo grabamos con Selenium, no tendremos que repetir el proceso otra vez mecánicamente como si fuésemos "monos", ya se encarga Selenium de reproducirlo las veces que queramos.
  • Lo podemos utilizar con otros fines. Por ejemplo, para preparar una demostración en la que queremos pasar por N pantallas, para que no se nos olvide nada (o simplemente como guión).
  • Una vez grabados todos los casos de prueba, podemos reproducirlos de golpe para comprobar que tras nuevos cambios que hayamos hecho no se ha estropeado nada (efectos colaterales sobre otras pantallas). Obviamente, habrá que actualizar los casos de prueba (realizando una nueva grabación del caso de prueba o eliminado/insertando pasos) si hemos introducido o eliminado componentes en las pantallas.

Integración de Selenium



El botón de la izquierda es para reproducir los tests y el de la derecha para grabar las interacciones con el navegador (las pruebas que hagamos).
Hacemos un par de clicks sobre la aplicación web con la que estemos trabajando, y grabamos la prueba:

 

Integración de Selenium con Wicket (Consideraciones "especiales")

Si reprodujésemos el test, tendríamos problemas al trabajar con Wicket, ya que si no se especifica en cada componente el MarkupId, Wicket lo autogenera (diferente en cada nueva reproducción de la prueba, por lo que en no encontraría el componente por variar su id en cada plan de ejecución).
Para subsanar este problema, hacemos lo siguiente:
  • En la clase que tengamos que extienda de WebApplication, sobrescribimos el método init, añadiendo la línea getDebugSettings().setOutputComponentPath(true), que nos sirve para habilitar el atributo"wicketpath" (identificador único).

@Override

protected void init() {

   super.init();

   getDebugSettings().setOutputComponentPath(true);

}
  • Creamos una carpeta "wicketPathLocatorBuilder" (Donde queramos, en D: o donde sea
  • Creamos un fichero "user-extension.js.wicketPathLocatorBuilder" con el siguiente contenido, que guardamos en esa carpeta:
 LocatorBuilders.add('wicketpath', function(e) {
        this.log.debug("wicketpath: e=" + e);
        if (e.attributes && e.hasAttribute("wicketpath")) {
            this.log.info("found attribute " + e.getAttribute("wicketpath"));
            return "//" + this.xpathHtmlElement(e.nodeName.toLowerCase()) + "[@wicketpath=" + this.attributeValue(e.getAttribute("wicketpath")) + "]";
        }
        return null;
    });
LocatorBuilders.order.unshift(LocatorBuilders.order.pop());

  • Vamos a Selenium y seleccionamos la opción Options > Options
  • Indicamos la ruta en "Selenium Core extensions"

  • Aceptamos y reiniciamos Firefox
Ya podemos grabar y reproducir nuestras pruebas con Selenium en Wicket sin preocuparnos de que genere ids distintos de cada vez:


Exportando los casos de prueba a scripts

Utilizando "Export Test Case As...":

Podemos exportar los casos de prueba como "scripts" para lanzarlos con Junit, Ruby, C#, ...
Por ejemplo, con Ruby:


Y el script sería este (al ejecutarlo nos abre automáticamente el Firefox, lanza la prueba y cuando acaba se cierra y pone los resultados):

require "selenium-webdriver"
gem "test-unit"
require "test/unit"

class Papidal < Test::Unit::TestCase

  def setup
    @driver = Selenium::WebDriver.for :firefox
    #@base_url = "http://localhost:8090/"
    @accept_next_alert = true
    @driver.manage.timeouts.implicit_wait = 30
    @verification_errors = []
  end
 
  def teardown
    @driver.quit
    assert_equal [], @verification_errors
  end
 
  def test_papidal
    @driver.get("http://localhost:8090/myproject/")
    @driver.find_element(:xpath, "//a[@wicketpath='showModalLink']").click
  end
 
  def element_present?(how, what)
    @driver.find_element(how, what)
    true
  rescue Selenium::WebDriver::Error::NoSuchElementError
    false
  end
 
  def verify(&blk)
    yield
  rescue Test::Unit::AssertionFailedError => ex
    @verification_errors << ex
  end
 
  def close_alert_and_get_its_text(how, what)
    alert = @driver.switch_to().alert()
    if (@accept_next_alert) then
      alert.accept()
    else
      alert.dismiss()
    end
    alert.text
  ensure
    @accept_next_alert = true
  end
end

Si queremos lanzarlo con Internet Explorer, simplemente nos aseguramos que el zoom del navegador esté al 100% (o nos dará un error del estilo "browser zoom level was set to 104%. It should be set to 100%"). Para ello ajustamos el IE pulsando Ctrl+Scroll (menuda frikada :)).

Además, en el caso de ruby con IE, simplemente sería cambiar el Driver, poniendo esta línea:
@driver = Selenium::WebDriver.for :ie

Y añadiendo al PATH (variable de entorno) el fichero: IEDriverServer.exe, que descargaremos de una de estas dos  rutas:
Download version 2.30.2 for (recommended) 32 bit Windows IE or 64 bit Windows IE (http://code.google.com/p/selenium/downloads/detail?name=IEDriverServer_x64_2.30.2.zip)

Como en casa tengo un equipo de 64 bits, me bajé la segunda opción.

El resultado al lanzar el script, sería la apertura automática del IE ejecutando los tests y al acabar se cerraría sólo mostrando por consola esta traza:

Exportando los casos de prueba de Selenium con Junit

Si preferimos exportar el caso de prueba con Junit, para poder lanzarlo cómodamente desde Eclipse, necesitaremos algunas dependencias adicionales. Según el navegador que deseemos usar, modificaremos a nuestro gusto la dependencia "selenium-firefox-driver", en este caso, nos permitirá lanzar nuestros tests contra Firefox:

<dependency>

   <groupId>junit</groupId>

   <artifactId>junit</artifactId>

   <version>4.8.2</version>

</dependency>

<dependency>

      <groupId>org.seleniumhq.selenium</groupId>

      <artifactId>selenium-java</artifactId>

      <version>2.29.1</version>

</dependency>

  

  <dependency>

      <groupId>org.seleniumhq.selenium</groupId>

      <artifactId>selenium-firefox-driver</artifactId>

      <version>2.29.1</version>

</dependency>

Además, para evitar otros errores al ejecutar el test, necesitaremos la siguiente versión de "xml-apis":

<dependency>

     <groupId>xml-apis</groupId>

     <artifactId>xml-apis</artifactId>

     <version>1.4.01</version>

</dependency> 

Selenium, provee los drivers para dar soporte con Safari, IE, Firefox, … Con lo que podemos comprobar si nuestras pruebas funcionan en los distintos navegadores (compatibilidad). En este ejemplo me centro en Firefox, pero en teoría sólo hay que cambiar la clase del driver del navegador que queremos que ejecute el test.

Con JUNIT, con el servidor en local levantado en el puerto 8080, al lanzar el test se abrirá automáticamente el Firefox y en él se ejecutará el caso de prueba (navegando él sólo por las pantallas que le hemos indicado). Al finalizar, si no hubo errores, se cierra el navegador sólo y tendremos el test en "verde".



Para lanzar los Junit contra IE (acordaros de descargar el IEDriverServer.exe):

File file = new File("D:\\util\\IEDriverServer.exe");//El path que sea
System.setProperty("webdriver.ie.driver", file.getAbsolutePath());
driver = new InternetExplorerDriver();

Otras consideraciones

En ocasiones, el "wicketpath" no es suficiente, y para algún elemento puede ser necesario setear los siguientes atributos:

botonFiltro.setMarkupId("filtro1");
botonFiltro.setOutputMarkupId(true);

Se aconseja encarecidamente configurar el orden de los localizadores en el plugin de Selenium de la siguiente forma (según necesidades):



Si es posible que tarde unos segundos una pantalla hasta que se cargue, le damos tiempo a que se cargue la página, con la función isElementPresent. Cuando nos devuelva true, sabemos que ya está cargada la pantalla con el elemento que queremos usar para la prueba. Ejemplo:

//...

driver.findElement(By.xpath("//div[@id='bigPageLinks']/ul/li[3]/form/a")).click();
    for (int second = 0;; second++) {
         if (second >= 60) fail("timeout");
         try { if (isElementPresent(By.cssSelector("#modelo"))) break; } catch (Exception e) {}
         Thread.sleep(1000);
    }

    driver.findElement(By.cssSelector("#modelo")).clear();
    driver.findElement(By.cssSelector("#modelo")).sendKeys("8032");

//...

martes, 12 de febrero de 2013

USOSME WEB: Probando el mundo de los PaaS

Hace unos meses, aprovechando el tiempo libre para aprender un poquillo sobre el Cloud Computing interactúar un poco con las PaaS y esas cosas, empecé a montar una aplicación web (idealmente tendría su imagen para android) hasta que empecé a tener menos tiempo o cambiaron mis prioridades.

Al final quedó en esto: http://usosmeweb.appspot.com

Voy a dividir esta esta entrada en dos: Para qué podría valer (tal y como está actualmente) y cómo está estructurada técnicamente.

Para qué vale

Puedes loguearte en usosmeweb a través de tu cuenta de Facebook (ten en cuenta que te logueas sobre la plataforma de Facebook, así que yo no voy a conocer tú contraseña, simplemente Facebook me dirá si te has logueado correctamente o no. De hecho, le pido la mínima información al servicio) o si lo prefieres, proporcionando un nombre de usuario y una cuenta de correo a la que enviaré un correo de confirmación de la cuenta.



Una vez logueado, podrás acceder a las opciones del menú. Actualmente están activas las siguientes:
  • Alertas
    • Públicas: Puedes ver los avisos que han creado los usuarios. Pulsando el botón "Detalle" puedes ver los comentarios asociados o añadir nuevos, como si fuese un foro. Podrías crear un aviso para informar sobre algún evento o simplemente para hacer una quedada para ir en bici :). Recomiendo encarecidamente no escribir datos especialmente sensibles.
    •  Crear/Revisar: Desde aquí podrás crear nuevas alertas o revisar las que has creado. Para crear una alerta, simplemente debes decribirla, seleccionar el nivel de urgencia, el tipo de actividad y la privacidad (si sólo quieres que la vean tus amigos, colegas, tu familia o si es pública). También podrás "cancelar" una alerta que hayas creado. Cuando la canceles aparecerá tachada.
    • Recibidas: Desde aquí podrás consultar las alertas "privadas". Es decir, si eres amigo de otro usuario y ese usuario ha creado una alerta que sólo pueden verla sus amigos, tú la verás, pero no será pública.
  • Mi Gente
    • Podrás decidir y ver a quién sigues y quien te sigue. Al estilo Facebook, pero mucho más simplificado. Tú decides que grupo de gente puede ver tus alertas.
  • Mi cuenta
    • Configuración: Para cambiar tu avatar/imagen de usuario. Sólo permito imágenes pequeñas, porque la cuota que me facilitan no es demasiado grande :)


Tardará en cargar un ratillo si el servidor tiene que "levantar" la instancia. He metido algún error para comprobar la redirección a la página genérica de errores. Como actualmente no la estoy manteniendo, en cuanto Google vuelva a cambiar librerías de GAE, fallará estrepitosamente y en ese momento eliminaré el enlace.

Estructura técnica

- PaaS: Google App Engine
- Cliente: JSF (PrimeFaces), CSS, HTML, JS
- Servidor: Spring, DataNucleus, JDO
- Construcción: Eclipse, Maven, Plugin para GAE
- Cacheo en servidor de imágenes y páginas estáticas
- Integración con API de Facebook para autenticación
- Control de versiones: Mercurial

Después de investigar otro poco las plataformas que ofrecían servidores, bases de datos y otros servicios en la nube y tras probar Openshift y Google App Engine (con los que espero sacar una comparativa algún día), posteriormente he montado, en un día malo de estos que vienen en verano, una aplicación con la siguiente arquitectura para CloudBees:

Cliente: Wicket y HTML5
Servidor: JEE5 (en esta versión los EJBs y todo lo demás tiene mejor pinta y es un calco de Spring)
Base de datos: MySQL
Construcción: Maven
Servidor de aplicaciones: JBoss 7.1

Tengo los cimientos montados (aunque no son visibles) y simplemente he dejado una pequeña chorrada para probar el DnD (Drag And Drop) de HTML5. Lo cierto es que tiene bastante buena pinta, aunque como siempre, mientras no paguemos, nuestras pruebas estarán bastante limitadas, en este caso por la cuota de espacio de BD que nos proporciona CloudBees, por ejemplo.

lunes, 11 de febrero de 2013

Como no ser elegidos en una selección de personal (crítica)

Hace unas semanas llegó a mí poder un interesante pdf, "Cómo no ser elegidos en una selección de personal (y viceversa)" que os dejo por aquí para que lo descarguéis. 


Antes de empezar a "rajar", comentar que me ha parecido un documento entretenido, bien redactado y desgraciadamente refleja bastante bien la realidad en los procesos de selección.

Ya que el libro va de "impresiones" lo primero que comentaré es lo superficial y cómoda que me parece la visión del seleccionador (no me refiero a Vicente del Bosque, en este caso). Lo que no excluye que vaya a hacer algo parecido pero con otra visión durante la redacción de esta entrada.

Tampoco acabo de entender que se valore mentir (bueno, mentir "bien" que casi es peor, puesto que denota una total falta de ética y escrúpulos). Por ejemplo, cito literalmente:

"Seleccionador: ¿Qué opinión le merece la política fiscal del Gobierno?

Candidato: Podría responder que carezco de suficientes datos y que, por lo tanto, no tengo una opinión formada. Pero eso no pasaría de ser una respuesta políticamente correcta y daría la impresión de no tener un criterio formado. Quiero pensar que todos los Gobiernos pretenden el bien común. Es su obligación. Cierto es que en la práctica no siempre se percibe así por todos los ciudadanos. Es lícito que cada uno tenga una opinión propia. La actual situación parece difícil y cualquier política será discutida. Los resultados dirán si las decisiones tomadas son acertadas.

Escritor: Es una respuesta diplomática, aparentemente no elude contestar una pregunta comprometida. El candidato será calificado positivamente."

Diplomática no es la palabra. El candidato no se ha mojado, simplemente ha lanzado un bote de humo para no decir absolutamente nada y perder cinco minutos. Si se presenta a un puesto de portavoz del gobierno, es el candidato perfecto. Sino pues podría ser el típico trabajador que no sabe resolver un problema y antes de decir "no tengo ni idea" o "no soy capaz de tomar una decisión o manifestar una opinión", te hace perder tiempo (el de él posiblemente sea menos valioso -que desde luego no es lo mismo que cobrar menos la hora- y productivo que el tuyo). Curiosamente, este tipo de trabajadores suelen llegar muy lejos por su aparente diplomacia (en realidad falta de liderazgo y temerosos (precavidos, no) de manifestar sus opiniones).

Cuando hablo de manifestar opiniones me refiero a un diálogo "no agresivo" pero siendo transparentes, porque aunque se crea que ocultar ciertos detalles valiosos es una habilidad inteligente, a medio y largo plazo acaba siendo una carga que frecuentemente es difícil de justificar y que se acaba absorbiendo.

Cito otra perla:

"Generalmente bastarán cinco minutos para que un buen entrevistador, ayudado por el nerviosismo del candidato, descubra que no somos sinceros".

Coincido, siempre y cuando el que acude a la entrevista es una persona sincera e intenta ocultar torpemente algún tema que no desea que salga a la luz en la entrevista. Pero desde luego, discrepo en el caso de personas sin ética que anteponen siempre el dinero a sus principios. Dudo que tengan problema alguno para "engañar" al entrevistador, ya que los entrevistadores mediocres (pueden seguir siéndolo aunque lleven a sus espaldas miles de entrevistas si no aprenden de sus fracasos) los percibirán como gente con liderazgo, carismática y con labia.

Aunque a ojos de otra gente, en la que me incluyo, dichos candidatos sean charlatanes que ni curran ni son productivos, pero habitualmente con unas ventajas laborales envidiables.

Otro punto interesante:

"Se dice que un 40% de los currículos que se reciben contienen datos erróneos o que no son verdaderos"

Correcto. La gente que no mentimos en el currículo estamos en clara desventaja si el seleccionador no se molesta en contrastarlo o lo detectan tarde. Actualmente se valora más ornamentar frases y "bailar" con el lenguaje corporal que ser un currante que resuelve problemas y genera beneficios.

Acabo esta entrada con una frase del propio libro:

"Hay estudios, manuales, teorías, consejos. Mil cosas que dan pautas para que una selección de personal resulte lo más adecuada posible. Y aún así no siempre se acierta. Es más el porcentaje de fracasos es, a mi modo de ver, alto."

Parece que algo falla...

"Sé el cambio que quieras ver en el mundo". Gandhi.