Selenium con DSL
Selenium es una herramienta para pruebas de aplicaciones web. Quién tuvo la posibilidad de utilizarla, sabrá lo poderosa que es la herramienta, la cuál tiene diversas funciones para ejecutar con nuestra aplicación web. Puede ocurrir, que nuestros test de Selenium se vayan complejizando a medida que nuestra aplicación crece, por lo que esta es una posible solución, combinando el poder de Selenium con un enfoque DSL.
Contenido
Ejemplo
A continuación mostramos una propuesta de como encarar un test de Seleniumcon DSL. Para el ejemplo, suponemos un sistema de biblioteca, con las siguientes pantallas y su navegación. Se puede observar que pare este sistemas planteamos dos roles de usuario, el Bibliotecario y el Socio.
Creando un test
En el siguiente test, vamos a probar la funcionalidad de la reserva de libro. Para escribir el siguiente test, se utilizó una sintaxis acotada y simple con las siguientes reglas:
- ir<NombreDeLaPagina>: Los métodos que comiencen con el prefijo “ir”, nos retorna el nuevo contexto (página).
- verificar<Descripción>: Los métodos que comiencen con el prefijo "verificar", poseen los "assert" para comprobar alguna acción realizada en la página, este método siempre retona la misma página (this).
- ingresarComo<Nombre del Rol>: Rol con el que se probará el sistema.
- aceptar, cancelar, volver, buscar: Son nombre de las acciones básica que se realizan en un página y pueden (o nó) devolver otra página.
- <acciones propias de la página> : Estos métodos realizan acciones sobre el sistema sin ir a otra página. Ej.: reservar
ReservaSeleniumDslTest
package com.test.dsl;
import org.junit.AfterClass; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test;
public class ReservaSeleniumDslTest {
@BeforeClass public static void setUpClass() throws Exception { Usuario.inicializar(); } @AfterClass public static void tearDownClass() throws Exception { Usuario.finalizar(); } @Test public void comoBibliotecarioQuieroRealizarUnaReserva() { Usuario.ingresaComoBibliotecario() .irReservaDeLibro() .reservar() .aceptar() .verificarReserva() .aceptar() .salir(); }
}
De esta forma, escribimos el test de manera que al leer los métodos a los que se invoca, se entiende paso a paso lo que el test realiza, sin confundirnos con la implementación. Utilizando los patrones "method chaining" , "builder", "Page object" ayuda a realizar una abstracción de la implementación.
Si lo hubieramos realizado solo con selenium, el mismo test quedaría de la siguiente forma:
ReservaSeleniumTest
@Test public void comoBibliotecarioQuieroRealizarUnaReserva() { //Ingresar selenium.open(""); selenium.waitForPageToLoad(TIEMPO_ESPERA); selenium.type("username", username); selenium.type("password", password); selenium.click("submitbutton"); selenium.waitForPageToLoad(TIEMPO_ESPERA); //ir Pagina reserva de Libro selenium.click("link=ReservaLibro"); selenium.waitForPageToLoad(Usuario.TIEMPO_ESPERA); //reservar selenium.type("libro", "El señor de los anillos"); selenium.type("codSocio", "0001"); selenium.type("fechaHasta", "2011-01-01"); //Aceptar selenium.click("submitbutton"); selenium.waitForPageToLoad(TIEMPO_ESPERA) //verficiar Assert.assertTrue(selenium.isTextPresent("El señor de los anillos")); Assert.assertTrue(selenium.isTextPresent("0001")); Assert.assertTrue(selenium.isTextPresent("2011-01-01")); //aceptar para volver a la home selenium.click("submitbutton"); selenium.waitForPageToLoad(TIEMPO_ESPERA) //salir selenium.click("link=logout"); }
A continuación las implementaciones de las clases utilizadas en el test.
Usuario
package com.test.dsl;
import org.openqa.selenium.server.SeleniumServer;
import com.test.dsl.pagina.HomeBibliotecario; import com.test.dsl.pagina.HomeUsuario; import com.thoughtworks.selenium.DefaultSelenium; import com.thoughtworks.selenium.Selenium;
public class Usuario {
public static final String TIEMPO_ESPERA = "90000"; public static final String VELOCIDAD_CERO = "0"; public static final String VELOCIDAD_TEST = "350"; private static SeleniumServer seleniumServer; private static Selenium selenium ;
public static void inicializar() throws Exception { seleniumServer = new SeleniumServer(); seleniumServer.start(); selenium = new DefaultSelenium("localhost", 4444, "*chrome", "http://localhost:8585/"); selenium.start(); }
public static void finalizar() { selenium.stop(); seleniumServer.stop(); }
public static HomeBibliotecario ingresaComoBibliotecario() { Usuario.login("bibliotecario_test","123456"); return new HomeBibliotecario(selenium); } public static HomeUsuario ingresaComoUsuario() { Usuario.login("usuario_test","123456"); return new HomeUsuario(selenium); } private static void login(String username, String password) { selenium.open(""); selenium.waitForPageToLoad(TIEMPO_ESPERA); selenium.type("username", username); selenium.type("password", password); selenium.click("submitbutton"); selenium.waitForPageToLoad(TIEMPO_ESPERA);
}
}
Esta clase es la que determina con que rol vamos a probar la aplicación. El método ingresarComoBibliotecario instancia y devuelve un objeto de la clase HomeBibliotecario cuya implementación es:
HomeBibliotecario
package com.test.dsl.pagina;
import com.test.dsl.Usuario; import com.thoughtworks.selenium.Selenium;
public class HomeBibliotecario {
private Selenium selenium;
public HomeBibliotecario(Selenium selenium) { this.selenium = selenium; }
public ReservaDeLibro irReservaDeLibro() { selenium.click("link=ReservaLibro"); selenium.waitForPageToLoad(Usuario.TIEMPO_ESPERA); return new ReservaDeLibro(selenium); }
public void salir() { selenium.click("link=logout"); }
}
Volvemos a utilizar el patrón builder y el método ingresar denuncia devuelve un objeto de la clase AltaDenuncia. De esta manera, cada clase representa a una pantalla de la aplicación, y expone cada funcionalidad que la misma ofrece. AltaDenuncia sería así: Volvemos a utilizar el patrón method chaining y el método irReservaDeLibro devuelve un objeto de la clase ReservaDeLibro. De esta manera, cada clase representa a una pantalla de la aplicación (page object), y expone cada funcionalidad que la misma ofrece (acciones que puedo realizar en la página y páginas a la que puedo acceder)
ReservaDeLibro
package com.test.dsl.pagina;
import com.test.dsl.Usuario; import com.thoughtworks.selenium.Selenium;
public class ReservaDeLibro {
private Selenium selenium;
public ReservaDeLibro(Selenium selenium) { this.selenium = selenium; }
public ReservaDeLibro reservar() { selenium.type("libro", "El señor de los anillos"); selenium.type("codSocio", "0001"); selenium.type("fechaHasta", "2011-01-01"); return this; }
public ComprobanteDeLibro aceptar() { selenium.click("submitbutton"); selenium.waitForPageToLoad(Usuario.TIEMPO_ESPERA); return new ComprobanteDeLibro(selenium); }
}
ComprobanteDeLibro
package com.test.dsl.pagina;
import org.junit.Assert;
import com.test.dsl.Usuario; import com.thoughtworks.selenium.Selenium;
public class ComprobanteDeLibro {
private Selenium selenium;
public ComprobanteDeLibro(Selenium selenium) { this.selenium = selenium; }
public ComprobanteDeLibro verificarReserva() { Assert.assertTrue(selenium.isTextPresent("El señor de los anillos")); Assert.assertTrue(selenium.isTextPresent("0001")); Assert.assertTrue(selenium.isTextPresent("2011-01-01")); return this; } public HomeBibliotecario aceptar() { selenium.click("submitbutton"); selenium.waitForPageToLoad(Usuario.TIEMPO_ESPERA); return new HomeBibliotecario(selenium); }
}
Tanto el aceptar (como el cancelar en ReservaDeLibro), devuelven la home del bibliotecario.
Conclusiones
El resultado de hacer los test de esta manera, es la facilidad de escritura y mantenimiento del test en si mismo. A su vez, se podría diseñar las clases a utilizar en el DSL a conveniencia.