Automatización de Pruebas en Kotlin con Selenium, TestNG y Page Object Model

En la actualidad la automatización de pruebas resulta ser un componente importante dentro del ciclo de desarrollo de software y aún más para el desarrollo de software ágil. Al ser un proceso automatizado facilita realizar pruebas repetitivas, end to end en flujos de negocio, abarcando muchos escenarios, lo cual sería muy difícil y costosos al realizarlo de forma manual. Como herramienta es de gran ayuda, ya que da una visión mayor y permite detectar errores en fases tempranas del desarrollo, tanto así, que en ciclos de integración continua, con el uso de esta herramienta es posible detectar errores antes de que los cambios sean liberados en ambientes de calidad, esto es favorable ya que desde QA se generarían menos reprocesos 🚀💣.

La reducción considerable de errores en ambientes de calidad y productivos es significativa y por tanto el costo de mantenibilidad es mucho menor. Las pruebas automatizadas facilitan realizar regresiones y pruebas de integración muy fácil y repetitivamente. Para no entrar más en detalle, el siguiente proyecto es una básica y sencilla introducción a la automatización de pruebas utilizando Kotlin, TestNG, Selenium y Page Object Model; para este caso en particular utilizaremos una pequeña fuente — YouTube, realizaremos una búsqueda del video de una canción que me gusta mucho, posterior a esto, reproduciremos el video que deseamos.

Kotlin como Lenguaje de Programación

Nuestro medio se diversifica más y más cada día. Últimamente se muestran muchos lenguajes de programación que permiten hacer mucho más con menos. Kotlin como lenguaje es interoperable, conciso y seguro. Al ser un lenguaje que corre sobre la maquina virtual de Java o la JVM (por sus siglas en ingles) permite una abierta interacción con el código Java y por tanto con los frameworks y librerías disponible para Java de forma casi transparente.

Kotlin toma lo mejor de Java y de Scala, los tiempos de respuesta son similares a trabajar Java de forma nativa, lo que es una ventaja considerable sobre Scala. Otro punto favorable es que Google lo adoptó como lenguaje de programación oficial para Android, denota la estabilidad del lenguaje.

Ahora si manos a la obra…! 👐

Requerimientos del proyecto

  • Java JDK 1.8
  • IDE: IntelliJ IDEA o Eclipse
  • Maven
  • Plugin Kotlin: IntelliJ IDEA o Eclipse

Creación del Proyecto

Iniciaremos con la creación de nuestro proyecto, para esto tenemos dos formas, eligen la forma que más le parezca:

Creación por consola

La forma más común y me refiero al tipo vieja escuela, es por consola, el siguiente script te facilitará la forma de realizarlo:

$ mvn archetype:generate -DgroupId=com.cafekotlin -DartifactId=TAEYouTube -DarchetypeArtifactId=maven-archetype-quickstart -DinteractiveMode=false

Creación por IDE

En mi caso elegí IntelliJ IDEA por la facilidad y las potentes características que posee. Iniciamos:

Al abrir IntelliJ IDEA Seleccionamos > Create new Project.

Figura 1. Creación Nuevo Proyecto

Ahora se presentará la siguiente pantalla, en este punto elegimos el tipo de proyecto Maven y seleccionamos la creación a partir de un arquetipo > Create from archetype. El arquetipo que seleccionaremos es > org.jetbrains.kotlin:kotlin-archetype-jvm y continuamos presionando el botón Next.

Figura 2. Selección de Arquetipo Maven

A fin de no alargar el articulo, avanzaremos mas rápido. Las opciones que se presentaran será: Definir grupo y arquetipo para el proyecto, en este caso es > GroupId: com.cafekotlin y ArtifactId:TAEYouTube.

Estructura del Proyecto

Una vez creado nuestro proyecto, adicionamos en el archivo pom.xml el siguiente contenido:

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<kotlin.version>1.1.2-4</kotlin.version>
<testng.version>6.8.8</testng.version>
<selenium.version>3.1.0</selenium.version>
</properties>

<dependencies>
<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-stdlib</artifactId>
<version>${kotlin.version}</version>
</dependency>

<dependency>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-test-junit</artifactId>
<version>${kotlin.version}</version>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-java</artifactId>
<version>${selenium.version}</version>
</dependency>

<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
</dependency>

<dependency>
<groupId>org.seleniumhq.selenium</groupId>
<artifactId>selenium-support</artifactId>
<version>${selenium.version}</version>
</dependency>
</dependencies>

<build>
<sourceDirectory>src/main/kotlin</sourceDirectory>
<testSourceDirectory>src/test/kotlin</testSourceDirectory>

<plugins>
<plugin>
<groupId>org.jetbrains.kotlin</groupId>
<artifactId>kotlin-maven-plugin</artifactId>
<version>${kotlin.version}</version>
<executions>
<execution>
<id>compile</id>
<phase>compile</phase>
<goals>
<goal>compile</goal>
</goals>
</execution>
<execution>
<id>test-compile</id>
<phase>test-compile</phase>
<goals>
<goal>test-compile</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>

Realizado este paso podemos iniciar a trabajar con Kotlin, Selenium y TestNG. Ahora el paso siguiente será la creación de los directorios para Kotlin, debe quedar algo similar a la siguiente imagen:

Figura 3. Estructura del Proyecto

Buscando Localizadores -> Locators

En la página principal de YouTube iniciaremos la búsqueda del video, el encabezado tiene una caja de texto y el botón de búsqueda. Al modelar páginas web como objetos, es necesario encontrar e identificar los elementos que requeriremos para automatizar nuestros casos de pruebas. Firefox cuenta con unas extensiones dispuestas FireBug y FirePath para tal fin y las cuales deben ser instaladas para poder utilizarlas, estas facilitan localizar todos los elementos web necesarios para la automatización. Al hacer clic con el botón derecho en cualquier elemento de una página, puede activar la opción “Inspect in FirePath” del menú contextual y así obtener información detallada sobre el elemento.

Desde la pestaña FirePath de Firefox, tenemos la opción de obtener nuestros localizadores de formas diferentes, según sea nuestra necesidad, esta disponible XPath, CSS, Sizzle y el Id del componente.

Figura 4. FirePath en Firefox

Trabajando con Page Object Model

Ahora bien, es necesario representar cada página que requiramos de la aplicación en un objecto, para esto es necesario crear una clase por página a fin de obtener instancias de clases y poder trabajar con programación orientada a objetos.

Aquí tenemos nuestros Pages Objects:

HomePage

package com.cafekotlin.webpages

import org.openqa.selenium.WebDriver
import org.openqa.selenium.WebElement
import org.openqa.selenium.support.FindBy
import org.openqa.selenium.support.PageFactory

class HomePage(private val driver: WebDriver) {

@FindBy(id = "masthead-search-term")
private val searchBox: WebElement? = null

@FindBy(id = "search-btn")
private val searchButton: WebElement? = null

@FindBy(xpath = ".//*[@id='section-list-201801']/li[1]/div/div[1]/div/p")
private val numResult: WebElement? = null

init
{
PageFactory.initElements(driver, this)
}

fun searchVideo(video: String) {
searchBox?.sendKeys(video)
searchButton?.click()
}
}

ResultPage

package com.cafekotlin.webpages

import org.openqa.selenium.WebDriver
import org.openqa.selenium.WebElement
import org.openqa.selenium.support.FindBy
import org.openqa.selenium.support.PageFactory


class ResultPage(private val driver: WebDriver) {

@FindBy(xpath = ".//*/li[1]/div/div[1]/div/p")
private val numResult: WebElement? = null

@FindBy(xpath = ".//*[@id='results']/ol/li/ol/li/div/div/div[2]/h3/a")
private val videos: List<WebElement>? = null

@FindBy(xpath = ".//*[@id='eow-title']")
private val titleVideo: WebElement? = null

@FindBy(xpath = ".//*[@id='watch7-user-header']/div/a")
private val channel: WebElement? = null

init
{
PageFactory.initElements(driver, this)
}

fun isPageOpened(): Boolean{
return numResult?.text.toString().contains("About")
}

fun selectVideo(selectVideo: String){
for (webElement in videos!!) {
if (webElement.getText().contains(selectVideo)) {
webElement.click()
break
}
}
}

fun playingVideo(titleVideo: String, channel: String): Boolean{
return titleVideo?.equals(titleVideo) && channel.equals(channel)
}
}

Desarrollando un Test Sencillo

Una vez hemos concluido con nuestros Page Objects procederemos con el desarrollo de los test. Para esto he incluido un cambio adicional, utilice el patrón de diseño llamado Abstract Method, este facilitara la reutilización de código y cada clase test extenderá de una clase padre abstracta llamada TestBase. Esto favorece aún más la legibilidad del código y la centralización de procesos, en donde cada clase hija tiene la única responsabilidad de realizar la implementación concreta de cada test.

TestBase -> Clase Abstracta

package com.cafekotlin.test


import com.cafekotlin.UtilResources
import org.openqa.selenium.WebDriver
import org.openqa.selenium.chrome.ChromeDriver
import org.testng.annotations.AfterTest
import org.testng.annotations.BeforeTest
import java.net.URI
import java.util.concurrent.TimeUnit

abstract class TestBase {

lateinit var driver: WebDriver
private set

@BeforeTest
fun setup() {
System.setProperty(UtilResources.getProperties("nameDriver"),
UtilResources.getProperties("pathDriver") + UtilResources.getProperties("exeDriver"))
driver = ChromeDriver()
driver.manage().timeouts().implicitlyWait(10, TimeUnit.SECONDS)
driver.manage().window().maximize()
driver.get(URI(UtilResources.getProperties("pageURL")).toString())
}

@AfterTest
fun driverClose() {
driver.close();
}
}

SearchVideoTest -> Clase Hija

package com.cafekotlin.test

import com.cafekotlin.UtilResources
import com.cafekotlin.webpages.HomePage
import com.cafekotlin.webpages.ResultPage
import org.testng.Assert
import org.testng.annotations.Test

class SearchVideoTest() : TestBase() {

@Test
fun searchVideo() {
val homePage = HomePage(driver!!)
homePage.searchVideo(UtilResources.getProperties("nameVideo"))

val resultPage = ResultPage(driver!!)
Assert.assertTrue(resultPage.isPageOpened())

resultPage.selectVideo(UtilResources.getProperties("selectVideo"))

Assert.assertTrue(resultPage.playingVideo(UtilResources.getProperties("selectVideo"),
UtilResources.getProperties("channel")))
}

}

Utilidad

Desde mi apreciación personal considero que la organización y responsabilidad de cada componente dentro del software es determinante para la mantenibilidad del mismo. Adicioné un plus para mejorar el código y no tener hard-code 🔥, así resultará más fácil cambiar los parámetros de la búsqueda y no tener que ir a modificar directamente el código fuente.

Si han detallado las clases dispuestas para los test, tendrán curiosidad por la utilidad que aparece en algunas líneas. La implementación de UtilResources.getProperties( ) permitirá obtener valores desde un archivo de propiedades:

package com.cafekotlin

import java.io.FileInputStream
import java.io.IOException
import java.util.*

object UtilResources {

private var properties: Properties? = null

fun
loadProperties(): Unit {
try {
properties = Properties()
properties?.load(FileInputStream("config.properties"))
} catch (e: IOException) {
e.printStackTrace()
}
}

fun getProperties(properties: String): String {
loadProperties()
return UtilResources.properties?.getProperty(properties).toString()
}
}

Archivo config.properties:

pathDriver = C:/Driver/
nameDriver
= webdriver.chrome.driver
exeDriver
= chromedriver.exe

pageURL
= https://www.youtube.com/

nameVideo
= one metallica
selectVideo
= Metallica - One (Video)
channel
= Warner Bros. Records

Ejecución del Test

Es el momento de observar si lo hecho hasta ahora resulta exitoso o no:

Estructura final del proyecto y ejecución del test:

Figura 5. Estructura final del proyecto y ejecución del test.

Durante la ejecución del test:

Figura 6. Ejecución del test.

Ejecución en el browser:

Figura 7. Ejecución del test en el browser.

Al finalizar el test que fue ejecutado tenemos en el IDE el resultado:

Figura 8. Resultado del test.

Conclusiones

La implementación de Selenium, la adición de Page Object Model en combinación con Page Factory, es una grandiosa forma de modelar nuestras pruebas automatizadas y abarca una muy buena práctica para la automatización de pruebas. Dentro de los proyectos y sus equipos, facilita la vida tanto de desarrolladores como de los analistas de calidad -> QAs, de igual manera favorece el desarrollo ágil de software en gran medida.

Una correcta implementación de un framework para la automatización de pruebas es clave y puede dar alcance a toda la suite de pruebas del proyecto que se desee abarcar. Una abstracción clara del modelo de negocio y casos de pruebas bien planteados pueden permitir la mantenibilidad del framework y favorecer la adaptabilidad de este a los requisitos cambiantes a un costo menor.

Espero que esta pequeña introducción a la automatización de pruebas sea de utilidad en sus labores diarias y sea fácilmente comprendido. Utilicé Kotlin por la versatilidad del lenguaje, a parte la similitud con Scala y el hecho que pueda trabajar con el paradigma de Programación Orientada a Objectos me parece genial.