Práctica con Selenium WebDriver con Java
Una inmersión profunda en el desarrollo de pruebas de extremo a extremo
Boni García
Práctica con Selenium WebDriver con Java
por Boni García
Copyright © 2022 Boni García. Todos los derechos reservados.
Impreso en los Estados Unidos de América.
Publicado por O'Reilly Media, Inc., 1005 Gravenstein Highway North, Sebastopol, CA
95472.
Los libros de O'Reilly pueden ser adquiridos para uso educativo, comercial o
promocional de ventas. Las ediciones en línea también están disponibles para la
mayoría de los títulos (https://oreilly.com). Para más información, contacta a nuestro
departamento de ventas corporativas/institucionales: 800-998-9938 o
corporate@oreilly.com.
Editora de adquisiciones: Suzanne McQuade
Editora de desarrollo: Rita Fernando
Editora de producción: Kristen Brown
Correctora de estilo: Piper Editorial Consulting, LLC
Corrector de pruebas: JM Olejarz
Indexador: Sam Arnold-Boyd
Diseñador de interiores: David Futato
Diseñadora de portada: Karen Montgomery
Ilustradora: Kate Dullea
Abril 2022: Primera edición
Historial de revisiones para la primera edición
2022-03-31: Primera publicación
Consulta https://oreilly.com/catalog/errata.csp?isbn=9781098110000 para detalles de
la publicación.
El logotipo de O'Reilly es una marca registrada de O'Reilly Media, Inc. "Práctica con
Selenium WebDriver con Java", la imagen de la portada y la apariencia comercial
relacionada son marcas comerciales de O'Reilly Media, Inc.
Las opiniones expresadas en este trabajo son del autor y no representan las opiniones
del editor. Aunque el editor y el autor han hecho esfuerzos de buena fe para asegurar
que la información e instrucciones contenidas en este trabajo sean precisas, el editor y
el autor renuncian a toda responsabilidad por errores u omisiones, incluida, sin
limitación, la responsabilidad por daños resultantes del uso o la confianza en este
trabajo. El uso de la información e instrucciones contenidas en este trabajo es bajo su
propio riesgo. Si algún ejemplo de código u otra tecnología que este trabajo contenga o
describa está sujeto a licencias de código abierto o derechos de propiedad intelectual
de otros, es su responsabilidad asegurar que su uso cumpla con dichas licencias y/o
derechos.
Para lo más precioso del mundo para mí: mis hijos, Pablo y Carlos.
Los amo más que a nada.
Tabla de Contenidos
Prólogo. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xi
Prefacio. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . xv
Parte I. Introducción
1. Introducción a Selenium. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
Componentes principales de Selenium 3
Selenium WebDriver 5
Selenium Grid 6
Selenium IDE 8
Ecosistema de Selenium 10
Enlaces de Lenguaje 10
Gestores de Controladores 11
Herramientas de Localización 12
Frameworks 12
Infraestructura del Navegador 14
Comunidad 15
Fundamentos de Pruebas de Software 16
Niveles de Pruebas 16
Tipos de Pruebas 19
Metodologías de Pruebas 21
Herramientas de Automatización de Pruebas 24
Resumen y Perspectivas 28
2. Preparación para las Pruebas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 29
Requisitos 29
Máquina Virtual de Java 30
Editor de Texto o IDE 30
Navegadores y Controladores 30
Herramientas de Construcción 31
Software Opcional 31
Configuración del Proyecto 32
Diseño del Proyecto 32
Dependencias 34
Hola Mundo 45
Uso de Navegadores Adicionales 48
Resumen y Perspectivas 49
Parte II. La API de Selenium WebDriver
3. Fundamentos de WebDriver. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 53
Uso Básico de WebDriver 53
Creación de WebDriver 53
Métodos de WebDriver 57
Identificador de Sesión 58
Descarte de WebDriver 59
Localización de WebElements 59
Modelo de Objeto del Documento (DOM) 59
Métodos de WebElement 61
Estrategias de Localización 62
Encontrar Localizadores en una Página Web 72
Localizadores Compuestos 74
Localizadores Relativos 76
¿Qué Estrategia Deberías Usar? 80
Acciones del Teclado 82
Carga de Archivos 83
Deslizadores de Rango 84
Acciones del Ratón 85
Navegación Web 85
Casillas de Verificación y Botones de Radio 86
Gestos de Usuario 86
Clic Derecho y Doble Clic 88
Mouseover 89
Arrastrar y Soltar 90
Clic y Mantenimiento 91
Copiar y Pegar 92
Estrategias de Espera 94
Espera Implícita 94
Espera Explícita 96
Espera Fluida 98
Resumen y Perspectivas 100
4. Funciones Independientes del Navegador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 101
Ejecución de JavaScript 101
Scripts Sincrónicos 102
Scripts Fijados 108
Scripts Asincrónicos 109
Tiempos de Espera 110
Tiempo de Carga de Página 110
Tiempo de Carga de Script 111
Capturas de Pantalla 112
Capturas de Pantalla de WebElement 115
Tamaño y Posición de la Ventana 116
Historial del Navegador 117
El DOM Sombra 118
Cookies 120
Listas Desplegables 125
Elementos de Lista de Datos 127
Objetivos de Navegación 128
Pestañas y Ventanas 129
Marcos y Iframes 131
Cuadros de Diálogo 133
Alertas, Confirmaciones y Solicitudes 134
Ventanas Modales 136
Almacenamiento Web 137
Escuchadores de Eventos 138
Excepciones de WebDriver 142
Resumen y Perspectivas 144
5. Manipulación Específica del Navegador. . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . 145
Capacidades del Navegador 145
Navegador Sin Cabeza 147
Estrategias de Carga de Página 151
Emulación de Dispositivos 153
Extensiones Web 155
Tabla de Contenidos | viiGeolocalización 160
Notificaciones 162
Binario del Navegador 165
Proxies Web 166
Recolección de Registros 168
Obtener Medios de Usuario 169
Cargar Páginas No Seguras 171
Localización 173
Modo Incógnito 175
Edge en Modo Internet Explorer 175
Protocolo Chrome DevTools 177
Envolturas de Selenium CDP 177
Comandos Raw de CDP 180
Contexto de Ubicación 191
Autenticación Web 191
Imprimir Página 193
WebDriver BiDi 194
Resumen y Perspectivas 196
6. WebDriver Remoto. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . . . . . 197
Arquitectura de Selenium WebDriver 197
Creación de Objetos RemoteWebDriver 199
Constructor RemoteWebDriver 199
Constructor RemoteWebDriver Builder 201
Constructor WebDriverManager 201
Selenium-Jupiter 202
Selenium Grid 203
Standalone 203
Hub-nodes 207
Totalmente Distribuido 208
Observabilidad 213
Configuración 216
Proveedores de Nube 217
Navegadores en Contenedores Docker 219
Imágenes Docker para Selenium Grid 220
Selenoid 222
WebDriverManager 224
Selenium-Jupiter 227
Resumen y Perspectivas 228
viii | Tabla de Contenidos Parte III. Conceptos Avanzados
7. El Modelo de Objeto de Página (POM). . ... . . . . . . . . . . . . . . . . .. . . . . . . . . . 231
Motivación 231
El Patrón de Diseño POM 232
Objetos de Página 234
Objetos de Página Robustos 236
Creación de un Lenguaje Específico de Dominio (DSL) 240
Fábrica de Páginas 242
Resumen y Perspectivas 244
8. Especificaciones del Marco de Pruebas. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 245
Pruebas Parametrizadas 245
Pruebas en Múltiples Navegadores 252
Categorización y Filtrado de Pruebas 256
Ordenación de Pruebas 261
Análisis de Fallos 265
Reintento de Pruebas 273
Ejecución Paralela de Pruebas 278
Escuchadores de Pruebas 282
Pruebas Deshabilitadas 286
Resumen y Perspectivas 289
9. Integraciones de Terceros. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . . . . . . 291
Descarga de Archivos 291
Uso de Capacidades Específicas del Navegador 292
Uso de un Cliente HTTP 294
Captura de Tráfico de Red 296
Pruebas No Funcionales 298
Rendimiento 298
Seguridad 303
Accesibilidad 306
Pruebas A/B 307
API Fluida 308
Datos de Prueba 309
Informes 312
Desarrollo Orientado por Comportamiento 316
Marcos Web 320
Resumen y Perspectivas 322
10. Más Allá de Selenium. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 325
Aplicaciones Móviles 325
Pruebas Móviles 326
Appium 327
Servicios REST 332
REST Assured 334
Alternativas a Selenium 336
Cypress 336
WebDriverIO 339
TestCafe 340
Puppeteer 341
Playwright 342
Resumen y Observaciones Finales 344
A. Novedades en Selenium 4. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . . . . . . 345
B. Gestión de Controladores. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .. . . 351
C. Configuración del Repositorio de Ejemplos. . . . . . . . . . . . . . . . . . . . .. . . . . . . . . 359
Índice. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 379
Prólogo
En 1999, Kent Beck escribió *Extreme Programming Explained*. Este libro introdujo al
mundo el Extreme Programming (XP). Para muchas personas, esta fue la primera vez
que oyeron hablar del desarrollo ágil de software. Durante los siguientes 20 años,
muchas de las ideas detrás del libro se desvanecieron, pero hubo una idea que perduró:
deberíamos escribir pruebas automatizadas que verifiquen que nuestro código funcione
como se espera. XP esperaba que estas pruebas se escribieran antes de la lógica de la
aplicación, lo que llevó al Desarrollo Guiado por Pruebas (TDD).
Hoy en día, aunque el TDD estricto rara vez se practica, la idea de escribir pruebas es
predominante (¡aunque no siempre popular!). La mayoría de las empresas ahora
reconocen la necesidad de algún tipo de prueba automatizada. ¡Muchos de nosotros
realmente escribimos pruebas! Incluso los roles de QA "regulares" ahora
frecuentemente requieren que las personas escriban código.
En 2004, Jason Huggins comenzó Selenium en una consultora de desarrollo de software
llamada Thoughtworks, que se especializaba en desarrollo ágil. Los empleados estaban
inmersos en XP y eran fervientes defensores del TDD. Desde el principio, Selenium ha
estado estrechamente asociado con las pruebas.
En aquel entonces, probar sitios web en un navegador era relativamente simple. Eran
los viejos tiempos, cuando había docenas de navegadores entre los que elegir y JS
todavía se escribía como “JavaScript”. Los sitios eran pequeños, la funcionalidad limitada
y las interacciones que el usuario podía tener a través del navegador también eran
limitadas: tal vez solo llenar un formulario y hacer clic en un botón de "enviar". Este es
el mundo en el que nació Selenium, y las APIs y funcionalidades que ofrecía estaban tan
enfocadas como la plataforma que probaba.
Luego, el mundo descubrió XMLHttpRequest escondido en Internet Explorer, se
implementó en Firefox y, de repente, “Web 2.0” se convirtió en la nueva palabra de
moda. Google Maps mostró al mundo lo que los navegadores podían hacer, ¡y al mundo
le encantó! Los sitios web comenzaron a ofrecer más funcionalidades, impulsadas por
JS cuidadosamente elaborado. Selenium también se adaptó y evolucionó. Yo escribí las
APIs de WebDriver, y estas salieron a la luz. Aunque pretendían guiar a las personas en
una dirección específica, la complejidad subyacente de lo que Selenium intentaba
automatizar significaba que se convirtió en una herramienta más complicada.
A medida que escribo esto, los navegadores son más capaces, potentes y flexibles que
nunca. Ya no escribimos “sitios web” sino “aplicaciones web”, siendo la expresión
máxima actual el “Single Page App” o SPA. Estos empujan a los navegadores más que
nunca, pero son una evolución natural. Afortunadamente, una vez más, Selenium ha
evolucionado y crecido para permitir que estos tipos de aplicaciones sean
automatizados, añadiendo una gama de nuevas características en Selenium 4 para
ayudar a lidiar con las nuevas necesidades de prueba. Añadir esta funcionalidad ha
hecho que Selenium sea una herramienta aún más complicada.
Pero a pesar de haberse vuelto más complicada con los años, Selenium es una
herramienta utilizada por personas de todos los niveles de comodidad y habilidad en
programación. Hay más en escribir una prueba Selenium exitosa que “simplemente”
aprender las APIs. Hay una gran cantidad de tecnología que la rodea, desde los marcos
de prueba que puedes usar, hasta los Patrones de Diseño que puedes (¡y deberías!)
seguir al escribir las pruebas, hasta cómo gestionamos las dependencias binarias
requeridas por nuestras pruebas. Si queremos que nuestras pruebas se ejecuten en un
tiempo razonable, necesitamos tener acceso a una infraestructura que lo soporte.
Hay tanto por aprender, y sorprendentemente hay poca orientación sobre cómo
encajan todas las piezas.
Por eso estoy tan contento de que Boni haya escrito este libro. Comienza explicando
qué es Selenium y los diversos componentes dentro de él, y luego cada capítulo se basa
en los anteriores, introduciendo gradualmente más ideas y conceptos de una manera
que se siente natural y obvia.
Mejor aún, Boni va más allá de simplemente discutir cómo usar las APIs en bruto.
También describe el ecosistema de servicios, herramientas y ejecutores de pruebas que
las personas necesitan entender para sacar el máximo provecho de la herramienta. Su
experiencia usando Selenium y proporcionando algunas de estas herramientas de apoyo
brilla: estás en manos de un maestro aquí.
La forma en que está estructurado este libro permite a cualquier persona que use
Selenium sumergirse en el punto que le parezca adecuado. ¿Solo estás comenzando?
Entonces empieza desde el principio del libro, ya que Boni expone los conceptos básicos
de una manera atractiva y accesible. ¿Tal vez estás familiarizado con Selenium pero
quieres saber qué hay de nuevo en Selenium 4, o algunas de las características menos
conocidas que ofrece? Entonces salta al medio del libro; ¡hay tanto allí que incluso yo
aprendí algunas cosas!
Una cosa que espero que la gente saque de este libro es que Selenium es solo una parte
del rompecabezas que es la prueba automatizada. Boni también cubre esto,
presentando a los lectores (¡a ti!) cómo integrarlo en tus marcos de prueba, a las
diversas bibliotecas de pruebas unitarias que podrías querer usar y a los Patrones de
Diseño que pueden ayudar a mantener tus pruebas mantenibles y frescas. Después de
todo, aunque puede llevar tiempo escribir una prueba automatizada en primer lugar,
puede vivir durante años, y poder trabajar en ella con facilidad es importante.
Este libro allana el camino para dominar Selenium y usarlo de manera efectiva.
Sinceramente espero que esto haga que su uso sea más fácil y—¿me atrevo a decirlo?—
más agradable.
— Simon Mavi Stewart
Creador de WebDriver,
Líder del Proyecto Selenium 2009–2021,
y coeditor de las especificaciones W3C WebDriver y
WebDriver BiDi
Londres, enero de 2022
Prefacio
Selenium es un proyecto de código abierto que permite la automatización de
navegadores web. El componente principal del proyecto Selenium es Selenium
WebDriver, una biblioteca para controlar navegadores (por ejemplo, Chrome, Firefox,
Edge, Safari u Opera) de manera programática. Selenium WebDriver proporciona una
Interfaz de Programación de Aplicaciones (API) compatible con varios lenguajes de
programación (oficialmente soportado en Java, JavaScript, Python, C# o Ruby).
Aunque podemos usar Selenium WebDriver para múltiples propósitos relacionados con
la automatización de navegadores, su uso principal es implementar pruebas de extremo
a extremo para la verificación de aplicaciones web. Miles de organizaciones y testers
ahora utilizan Selenium en todo el mundo, y es una de las principales soluciones para
pruebas de extremo a extremo, apoyando una industria multimillonaria.
Quién Debería Leer Este Libro
Este libro proporciona un resumen completo de las principales características de
Selenium WebDriver versión 4, utilizando Java como lenguaje de enlace. Revisa los
aspectos principales de la navegación web automatizada, la manipulación de
navegadores, la interacción con elementos web, la suplantación de usuarios, la gestión
automatizada de drivers, el patrón de diseño Page Object Model (POM), el uso de
infraestructura remota y en la nube, la integración con Docker y herramientas de
terceros, y mucho más.
El público principal de este libro incluye programadores de Java de diferentes niveles
(desde principiantes hasta avanzados), como desarrolladores, testers, ingenieros de QA,
etc. Por lo tanto, es necesario tener un conocimiento básico del lenguaje Java y de la
programación orientada a objetos. El objetivo final es tener una comprensión completa
de los principales aspectos de Selenium WebDriver para crear pruebas de extremo a
extremo en Java utilizando diferentes marcos de pruebas a su elección (por ejemplo,
JUnit o TestNG).
Por Qué Escribí Este Libro
La automatización de pruebas es una técnica de pruebas de software que aprovecha las
herramientas de automatización para controlar la ejecución de pruebas. Permite una
mayor eficiencia y efectividad mientras garantiza la calidad general de un sistema de
software. En este ámbito, Selenium WebDriver es la biblioteca estándar de facto para
desarrollar pruebas de extremo a extremo para aplicaciones web. Este libro proporciona
la primera revisión completa de Selenium 4 hasta la fecha.
El libro sigue un enfoque de aprender haciendo. Para ese propósito, revisamos las
principales características de Selenium WebDriver a través de ejemplos de pruebas listos
para ejecutar. Estos ejemplos están disponibles públicamente en un repositorio de
código abierto en GitHub (https://github.com/bonigarcia/selenium-webdriver-java).
Para ser completos, este repositorio contiene cada ejemplo de prueba en diferentes
versiones del marco de pruebas integrado: JUnit 4, JUnit 5 (solo o con Selenium-Jupiter),
y TestNG.
Navegando por Este Libro
El contenido de este libro está dividido en 3 partes y 10 capítulos:
Parte I: Introducción
La Parte I proporciona el contexto tecnológico sobre Selenium, la automatización de
pruebas y la configuración del proyecto. Esta parte, más teórica que práctica, está
compuesta por dos capítulos:
• Capítulo 1: “Introducción a Selenium”, presenta los componentes principales del
proyecto Selenium (WebDriver, Grid e IDE) y su ecosistema (es decir, las
herramientas y tecnologías alrededor de Selenium). Además, este capítulo revisa
los principios de las pruebas de extremo a extremo relacionadas con Selenium.
• Capítulo 2: “Preparación para las Pruebas”, explica cómo configurar un proyecto
de Java (Maven y Gradle) que contenga pruebas de extremo a extremo que
utilicen la API de Selenium WebDriver. Luego, aprenderás cómo desarrollar tus
primeras pruebas con WebDriver utilizando diferentes marcos de pruebas: JUnit
4, JUnit 5 (solo o en combinación con Selenium-Jupiter), y TestNG.
Parte II: La API de Selenium WebDriver
La Parte II proporciona una visión práctica de la API de Selenium WebDriver. Esta parte
está guiada por pruebas disponibles en el repositorio de ejemplos e incluye los
siguientes capítulos:
• Capítulo 3: “Fundamentos de WebDriver”, describe los aspectos principales de
la API de Selenium WebDriver para llevar a cabo la interacción automatizada con
aplicaciones web. Así, este capítulo revisa varias estrategias para localizar y
esperar elementos web. Además, descubrirás cómo simular acciones de usuario
(es decir, interacciones automatizadas usando el teclado y el ratón) en un
navegador.
• Capítulo 4: “Características Independientes del Navegador”, revisa aquellos
aspectos de la API de Selenium WebDriver que son interoperables en diferentes
navegadores. Por lo tanto, este capítulo muestra cómo ejecutar JavaScript, crear
escuchadores de eventos, gestionar ventanas, tomar capturas de pantalla,
manejar el DOM sombreado, manipular cookies, acceder al historial del
navegador o al almacenamiento web, o interactuar con ventanas, pestañas e
iframes, entre otros elementos.
• Capítulo 5: “Manipulación Específica del Navegador”, explica aquellos aspectos
de la API de Selenium WebDriver particulares para navegadores específicos. Este
grupo de características cubre capacidades del navegador (opciones,
argumentos, preferencias, etc.), el Protocolo Chrome DevTools (CDP), funciones
de geolocalización, autenticación básica y web, impresión de páginas en PDF, o
la API WebDriver BiDi.
• Capítulo 6: “Remote WebDriver”, describe cómo utilizar la API de Selenium
WebDriver para controlar navegadores remotos. Luego, aprenderás cómo
configurar y usar Selenium Grid versión 4. Finalmente, descubrirás cómo usar
infraestructura avanzada para pruebas de Selenium en proveedores de nube
(por ejemplo, Sauce Labs, BrowserStack o CrossBrowserTesting, entre otros) y
navegadores en contenedores Docker.
Parte III: Conceptos Avanzados
La Parte III se centra en aprovechar la API de Selenium WebDriver en diferentes ámbitos
y casos de uso. Esta parte incluye los siguientes capítulos:
• Capítulo 7: “El Modelo de Página (POM)”, introduce POM, un patrón de diseño
popular utilizado en combinación con Selenium WebDriver. Este patrón permite
a los usuarios modelar páginas web usando clases orientadas a objetos para
facilitar el mantenimiento de las pruebas y reducir la duplicación de código.
• Capítulo 8: “Especificaciones del Marco de Pruebas”, revisa varias características
particulares del marco de pruebas unitarias utilizado junto con Selenium
WebDriver que permiten mejoras en diferentes aspectos del proceso general de
pruebas. Con ese fin, este capítulo primero explica cómo llevar a cabo pruebas
cruzadas de navegadores (es decir, reutilizar la misma lógica de prueba para
verificar aplicaciones web usando diferentes navegadores) utilizando pruebas
parametrizadas y plantillas de prueba. Luego, aprenderás cómo dividir las
pruebas en diferentes categorías para filtrado de ejecución, ordenar pruebas,
análisis de fallos (es decir, recopilar y analizar datos para determinar la causa de
un fallo), reintentar pruebas, ejecución paralela de pruebas, escuchadores de
pruebas, o desactivar pruebas.
• Capítulo 9: “Integraciones de Terceros”, revisa diferentes tecnologías que
puedes usar para mejorar tus pruebas de Selenium WebDriver, como
herramientas de informes, generación de datos de prueba y otros marcos (por
ejemplo, Cucumber o Spring). Además, este capítulo describe cómo usar
bibliotecas externas con Selenium para implementar casos de uso específicos,
como la descarga de archivos o pruebas no funcionales (como carga, seguridad
o accesibilidad).
• Capítulo 10: “Más Allá de Selenium”, presenta un par de marcos de
automatización relacionados con Selenium: Appium (para pruebas móviles) y
REST Assured (para pruebas de servicios web REST). Para concluir, revisamos
algunas de las alternativas más relevantes actuales a Selenium WebDriver, como
Cypress, WebDriverIO, TestCafe, Puppeteer o Playwright.
Convenciones Utilizadas en Este Libro
Las siguientes convenciones tipográficas se utilizan en este libro:
- Cursiva: Indica términos nuevos, URL, direcciones de correo electrónico, nombres de
archivos y extensiones de archivos.
- Ancho constante: Usado para listados de programas, así como dentro de los párrafos
para referirse a elementos de programa como nombres de variables o funciones, bases
de datos, tipos de datos, variables de entorno, declaraciones y palabras clave.
- Negrita en ancho constante: Muestra comandos u otro texto que el usuario debe
escribir literalmente.
- Cursiva en ancho constante: Muestra texto que debe ser reemplazado por valores
proporcionados por el usuario o por valores determinados por el contexto.
Este elemento señala un consejo o sugerencia.
Este elemento señala una nota general.
Este elemento indica una advertencia o precaución.
Uso de Ejemplos de Código
Los ejemplos de código están disponibles para descarga en
[https://github.com/bonigarcia/seleniumwebdriver-
java](https://github.com/bonigarcia/seleniumwebdriver-java). Si tienes una pregunta
técnica o un problema con el uso de los ejemplos de código, por favor envía un correo a
bookquestions@oreilly.com.
Este libro está aquí para ayudarte a realizar tu trabajo. En general, si se ofrecen ejemplos
de código con este libro, puedes usarlos en tus programas y documentación. No
necesitas contactarnos para obtener permiso a menos que estés reproduciendo una
porción significativa del código. Por ejemplo, escribir un programa que utilice varios
fragmentos de código de este libro no requiere permiso. Vender o distribuir ejemplos
de libros de O’Reilly sí requiere permiso. Responder a una pregunta citando este libro y
citando ejemplos de código no requiere permiso. Incorporar una cantidad significativa
de código de ejemplo de este libro en la documentación de tu producto sí requiere
permiso.
Agradecemos, pero generalmente no requerimos, atribución. Una atribución
generalmente incluye el título, autor, editor e ISBN. Por ejemplo: “Hands-On Selenium
WebDriver with Java por Boni García (O’Reilly). Copyright 2022 Boni García, 978-1-098-
11000-0.”
Si consideras que tu uso de los ejemplos de código cae fuera del uso justo o del permiso
dado arriba, no dudes en contactarnos en permissions@oreilly.com.
O’Reilly Online Learning
Durante más de 40 años, O’Reilly Media ha
proporcionado formación, conocimiento e información tecnológica y empresarial para
ayudar a las empresas a tener éxito.
Nuestra red única de expertos e innovadores comparte su conocimiento y experiencia a
través de libros, artículos y nuestra plataforma de aprendizaje en línea. La plataforma
de aprendizaje en línea de O’Reilly te da acceso a cursos de formación en vivo, rutas de
aprendizaje en profundidad, entornos interactivos de codificación y una vasta colección
de textos y videos de O’Reilly y más de 200 otros editores. Para más información, visita
[https://oreilly.com](https://oreilly.com).
Cómo Contactarnos
Por favor, dirige los comentarios y preguntas sobre este libro al editor:
O’Reilly Media, Inc.
1005 Gravenstein Highway North
Sebastopol, CA 95472
800-998-9938 (en Estados Unidos o Canadá)
707-829-0515 (internacional o local)
707-829-0104 (fax)
Tenemos una página web para este libro, donde listamos erratas, ejemplos y cualquier
información adicional. Puedes acceder a esta página en
[https://oreil.ly/handsOn_SeleniumWDJ](https://oreil.ly/handsOn_SeleniumWDJ).
Envía un correo a bookquestions@oreilly.com para comentar o hacer preguntas
técnicas sobre este libro.
Para noticias e información sobre nuestros libros y cursos, visita https://oreilly.com
Síguenos en Facebook: https://facebook.com/oreilly.
Síguenos en Twitter: https://twitter.com/oreillymedia
Síguenos en YouTube: https://www.youtube.com/oreillymedia
Agradecimientos
Primero, quiero agradecer al equipo de O’Reilly por hacer realidad este libro. Su apoyo
editorial ha sido ejemplar en cada etapa de este proceso.
También quiero reconocer a los revisores técnicos que ayudaron con este libro. Su
valiosa retroalimentación y consejos expertos mejoraron significativamente su calidad:
Diego Molina (ingeniero de software en Sauce Labs y líder técnico del proyecto
Selenium), Filippo Ricca (profesor asociado de informática en la Università di Genova),
Andrea Stocco (investigador postdoctoral en el Software Institute de la Università della
Svizzera italiana), Ivan Krutov (desarrollador de software en Aerokube) y Daniel Hinojosa
(consultor independiente, programador, instructor, ponente y autor)—muchas gracias.
Por último, me gustaría reconocer la contribución de Simon Stewart (creador de
WebDriver y líder del proyecto Selenium hasta 2021). Muchas gracias, Simon, por
escribir el prólogo de este libro y por tu valiosa retroalimentación sobre su contenido.
Pero principalmente, quiero reconocer tu trabajo durante todos estos años liderando el
proyecto Selenium. Tus contribuciones a la comunidad de pruebas de automatización
ya son parte de la historia del software.
PARTE I
Introducción
Selenium es un proyecto de código abierto compuesto por tres componentes
principales: WebDriver, Grid e IDE. Selenium ofrece capacidades avanzadas para la
automatización de navegadores que los profesionales suelen utilizar para implementar
pruebas de extremo a extremo para aplicaciones web. Esta primera parte del libro es
una visión general completa del proyecto Selenium y su ecosistema. Además,
proporciona una introducción a la teoría de pruebas de software, centrándose en sus
aplicaciones prácticas para Selenium WebDriver. Finalmente, descubrirás cómo
configurar un proyecto (usando Maven o Gradle) para desarrollar pruebas con
WebDriver. Para ser completo, cubro diferentes alternativas respecto al marco de
pruebas unitarias utilizado para integrar las llamadas a la API de Selenium WebDriver, a
saber, JUnit 4, JUnit 5 (solo o extendido por Selenium-Jupiter) y TestNG.
CAPÍTULO 1
Introducción a Selenium
Selenium es un conjunto de herramientas de código abierto compuesto por un conjunto
de bibliotecas y herramientas que permiten la automatización de navegadores web.
Podemos ver a Selenium como un proyecto paraguas con tres componentes principales:
WebDriver, Grid e IDE (Entorno de Desarrollo Integrado). Selenium WebDriver es una
biblioteca que permite controlar los navegadores de manera programática. Así,
podemos usar Selenium WebDriver para navegar por sitios web e interactuar con
páginas web (por ejemplo, haciendo clic en enlaces, completando formularios, etc.) de
manera automatizada, como lo haría un usuario real. El uso principal de Selenium
WebDriver es la prueba automatizada de aplicaciones web. Otros usos de Selenium
incluyen la automatización de tareas de administración basadas en la web o el web
scraping (extracción automatizada de datos web).
Este capítulo ofrece una visión general completa de los componentes principales de
Selenium: WebDriver, Grid e IDE. Luego, revisa el ecosistema de Selenium, es decir, otras
herramientas y tecnologías relacionadas. Finalmente, analiza los fundamentos de las
pruebas de software relacionadas con Selenium.
Componentes Principales de Selenium
Jason Huggins y Paul Hammant crearon Selenium en 2004 mientras trabajaban en
Thoughtworks. Eligieron el nombre “Selenium” como contraparte de Mercury, un marco
de pruebas existente desarrollado por Hewlett-Packard. El nombre es significativo
porque el selenio químico es conocido por reducir la toxicidad del mercurio.
Esa versión inicial de Selenium (conocida hoy como Selenium Core) es una biblioteca de
JavaScript que simula las acciones de los usuarios en las aplicaciones web. Selenium Core
interpreta los comandos llamados Selenese para lograr esta tarea. Estos comandos
están codificados como una tabla HTML compuesta por tres partes: comando (acción
ejecutada en un navegador web, como abrir una URL o hacer clic en un enlace), objetivo
(localizador que identifica un elemento web, como el atributo de un componente dado)
y valor (datos opcionales, como el texto escrito en un campo de un formulario web).
Huggins y Hammant añadieron una capa de scripting a Selenium Core en un nuevo
proyecto llamado Selenium Remote Control (RC). Selenium RC sigue una arquitectura
cliente-servidor. Los clientes utilizan un lenguaje de enlace (como Java o JavaScript) para
enviar comandos Selenese a través de HTTP a un proxy intermedio llamado el Servidor
Selenium RC. Este servidor lanza navegadores web bajo demanda, inyectando la
biblioteca Selenium Core en un sitio web y actuando como intermediario entre las
solicitudes de los clientes y Selenium Core. Además, el Servidor Selenium RC enmascara
el sitio web de destino al mismo URL local de la biblioteca Selenium Core inyectada para
evitar problemas de política de origen. Este enfoque fue revolucionario para la
automatización de navegadores en ese momento, pero tenía limitaciones significativas.
Primero, debido a que JavaScript es la tecnología subyacente para soportar la
automatización, algunas acciones no están permitidas ya que JavaScript no las permite,
como subir y descargar archivos o manejar ventanas emergentes y cuadros de diálogo,
por nombrar algunos. Además, Selenium RC introduce una sobrecarga relevante que
impacta en su rendimiento.
Paralelamente, Simon Stewart creó el proyecto WebDriver en 2007. WebDriver y
Selenium RC eran equivalentes desde una perspectiva funcional, es decir, ambos
proyectos permiten a los programadores simular usuarios web usando un lenguaje de
programación. No obstante, WebDriver utiliza el soporte nativo de cada navegador para
llevar a cabo la automatización, y por lo tanto, sus capacidades y rendimiento son mucho
superiores a los de RC. En 2009, después de una reunión entre Jason Huggins y Simon
Stewart en la Conferencia de Automatización de Pruebas de Google, decidieron fusionar
Selenium y WebDriver en un solo proyecto. El nuevo proyecto se llamó Selenium
WebDriver o Selenium 2. Este nuevo proyecto utiliza un protocolo de comunicación
basado en HTTP combinado con el soporte de automatización nativo en el navegador.
Ese enfoque sigue siendo la base de Selenium 3 (lanzado en 2016) y Selenium 4 (lanzado
en 2021). Ahora nos referimos a Selenium RC y Core como “Selenium 1,” y su uso está
desaconsejado en favor de Selenium WebDriver. Este libro se centra en la versión más
reciente de Selenium WebDriver hasta la fecha, es decir, la versión 4.
El Apéndice A resume las novedades incluidas en Selenium 4. Este apéndice
también contiene una guía de migración para actualizar de Selenium 3 a 4.
Hoy en día, Selenium es un conjunto de herramientas bien conocido compuesto por tres
subproyectos: WebDriver, Grid e IDE. Las siguientes subsecciones presentan las
principales características de cada uno.
Selenium WebDriver
Selenium WebDriver es una biblioteca que permite el control automático de
navegadores web. Con este fin, proporciona una API multiplataforma en diferentes
lenguajes de programación. Los lenguajes de programación oficiales soportados por
Selenium WebDriver son Java, JavaScript, Python, Ruby y C#. Internamente, Selenium
WebDriver utiliza el soporte nativo implementado por cada navegador para llevar a cabo
el proceso de automatización. Por esta razón, necesitamos colocar un componente
llamado driver entre el script que utiliza la API de Selenium WebDriver y el navegador.
La Tabla 1-1 resume los navegadores y controladores oficialmente soportados por
Selenium WebDriver.
El nombre Selenium se usa ampliamente para referirse a la biblioteca para la
automatización de navegadores. Dado que este término también es el nombre
del proyecto paraguas, uso Selenium en este libro para identificar el conjunto de
herramientas de automatización de navegadores, que está compuesto por tres
componentes: Selenium WebDriver (biblioteca), Selenium Grid (infraestructura)
y Selenium IDE (herramienta).
Tabla 1-1. Navegadores y controladores soportados por Selenium WebDriver
Los controladores (por ejemplo, `chromedriver`, `geckodriver`, etc.) son archivos
binarios dependientes de la plataforma que reciben comandos de un script de
WebDriver y los traducen a un lenguaje específico del navegador. En los primeros
lanzamientos de Selenium WebDriver (es decir, en Selenium 2), estos comandos
(también conocidos como el protocolo Selenium) eran mensajes JSON sobre HTTP (el
llamado Protocolo JSON Wire). Hoy en día, esta comunicación (todavía JSON sobre
HTTP) sigue una especificación estándar llamada W3C WebDriver. Esta especificación es
el protocolo Selenium preferido a partir de Selenium 4.
La Figura 1-1 resume la arquitectura básica de Selenium WebDriver que hemos visto
hasta ahora. Como se puede ver, esta arquitectura tiene tres capas. Primero,
encontramos un script que utiliza la API de Selenium WebDriver (Java, JavaScript,
Python, Ruby o C#). Este script envía comandos W3C WebDriver a la segunda capa, en
la que encontramos los controladores. Esta figura muestra el caso específico de usar
`chromedriver` (para controlar Chrome) y `geckodriver` (para controlar Firefox).
Finalmente, la tercera capa contiene los navegadores web. En el caso de Chrome, el
navegador nativo sigue el Protocolo DevTools. DevTools es un conjunto de herramientas
para desarrolladores para navegadores basados en el motor de renderizado Blink, como
Chrome, Chromium, Edge u Opera. El Protocolo DevTools se basa en mensajes JSON-
RPC y permite inspeccionar, depurar y perfilar estos navegadores. En Firefox, el soporte
nativo de automatización utiliza el protocolo Marionette. Marionette es un protocolo
remoto basado en JSON que permite instrumentar y controlar navegadores web
basados en el motor Gecko (como Firefox).
Figura 1-1. Arquitectura de Selenium WebDriver
En general, Selenium WebDriver permite controlar navegadores web de la misma
manera que lo haría un usuario, pero de manera programática. Para ello, la API de
Selenium WebDriver ofrece una amplia variedad de características para navegar por
páginas web, interactuar con elementos web o simular acciones de usuario, entre
muchas otras capacidades. La aplicación objetivo es basada en la web, como sitios web
estáticos, aplicaciones web dinámicas, aplicaciones de una sola página (SPA), sistemas
empresariales complejos con interfaz web, etc.
Selenium Grid
El segundo proyecto de la familia Selenium es Selenium Grid. Philippe Hanrigou
comenzó el desarrollo de este proyecto en 2008. Selenium Grid es un grupo de hosts en
red que proporciona infraestructura de navegador para Selenium WebDriver. Esta
infraestructura permite la ejecución (paralela) de scripts de Selenium WebDriver con
navegadores remotos de diferente naturaleza (tipos y versiones) en múltiples sistemas
operativos.
Figura 1-2 muestra la arquitectura básica de Selenium Grid. Como se puede ver, un
grupo de nodos proporciona los navegadores utilizados por los scripts de Selenium.
Estos nodos pueden usar diferentes sistemas operativos (como vimos en la Tabla 1-1)
con diversos navegadores instalados. El punto de entrada central a esta Grid es el Hub
(también conocido como Selenium Server). Este componente del lado del servidor
mantiene un registro de los nodos y actúa como intermediario para las solicitudes de los
scripts de Selenium. Al igual que en Selenium WebDriver, la especificación W3C
WebDriver es el protocolo estándar para la comunicación entre estos scripts y el Hub.
Figura 1-2. Arquitectura hub-nodos de Selenium Grid
La arquitectura hub-nodos en Grid está disponible desde Selenium 2. Esta arquitectura
también está presente en Selenium 3 y 4. Sin embargo, esta arquitectura centralizada
puede llevar a cuellos de botella en el rendimiento si el número de solicitudes al Hub es
alto. Selenium 4 ofrece una versión completamente distribuida de Selenium Grid para
evitar este problema. Esta arquitectura implementa mecanismos avanzados de
balanceo de carga para evitar la sobrecarga de cualquier componente.
El Capítulo 6 describe cómo configurar Selenium Grid siguiendo el enfoque
clásico (basado en un hub y un conjunto de nodos). Este capítulo también
cubre el modo independiente (es decir, hub y nodo(s) alojados en la misma
máquina) y la arquitectura completamente distribuida.
Selenium IDE
Selenium IDE es el último componente central de la suite Selenium. Shinya Kasatani creó
este proyecto en 2006. Selenium IDE es una herramienta que implementa la técnica de
automatización llamada Record and Playback (R&P). Como su nombre indica, esta
técnica tiene dos pasos. Primero, en Selenium IDE, la parte de grabación captura las
interacciones del usuario con un navegador, codificando estas acciones como comandos
de Selenium. En segundo lugar, usamos el script de Selenium generado para ejecutar
automáticamente una sesión del navegador (reproducción).
Esta versión temprana de Selenium IDE era un complemento para Firefox que
incorporaba Selenium Core para grabar, editar y reproducir scripts de Selenium. Estas
versiones tempranas eran módulos XPI (es decir, una tecnología utilizada para crear
extensiones de Mozilla). A partir de la versión 55 (lanzada en 2017), Firefox migró el
soporte para complementos a la especificación W3C Browser Extension. Como
resultado, Selenium IDE fue descontinuado, y durante un tiempo, no fue posible
utilizarlo. El equipo de Selenium reescribió Selenium IDE siguiendo la recomendación de
Browser Extensions para solucionar este problema. Gracias a esto, ahora podemos usar
Selenium IDE en múltiples navegadores, como Chrome, Edge y Firefox.
Figura 1-3 muestra la nueva interfaz gráfica de usuario (GUI) de Selenium IDE. Usando
esta GUI, los usuarios pueden grabar interacciones con un navegador, editar y ejecutar
el script generado. Selenium IDE codifica cada interacción en diferentes partes: un
comando (es decir, la acción ejecutada en el navegador), un objetivo (es decir, el
localizador del elemento web) y un valor (es decir, los datos manejados).
Opcionalmente, podemos incluir una descripción del comando. Figura 1-3 muestra un
ejemplo grabado de estos pasos:
1. Abrir el sitio web (https://bonigarcia.dev/selenium-webdriver-java). Usaremos este
sitio web como sitio de práctica en el resto del libro.
2. Hacer clic en el enlace con el texto “GitHub”. Como resultado, la navegación se
desplaza al código fuente del repositorio de ejemplos.
3. Verificar que el título del libro (Hands-On Selenium WebDriver with Java) esté
presente en la página web.
4. Cerrar el navegador.
Figura 1-3. Selenium IDE mostrando un ejemplo de un script grabado
Una vez que hemos creado un script en Selenium IDE, podemos exportar este script
como una prueba de Selenium WebDriver. Por ejemplo, Figura 1-4 muestra cómo
convertir el ejemplo presentado en un caso de prueba de JUnit. Finalmente, podemos
guardar el proyecto en nuestra máquina local. El proyecto resultante para esta muestra
está disponible en el repositorio de ejemplos de GitHub.
El proyecto Selenium está adaptando Selenium IDE a Electron en el
momento de escribir este texto. Electron es un marco de código abierto
basado en Chromium y Node.js que permite el desarrollo de aplicaciones
de escritorio.
Figura 1-4. Exportando un script de Selenium IDE a un caso de prueba de JUnit
Ecosistema de Selenium
Los ecosistemas de software son colecciones de elementos que interactúan en un
mercado compartido, sustentadas por una base tecnológica común. En el caso de
Selenium, su ecosistema incluye los proyectos centrales oficiales y otros proyectos,
bibliotecas y actores relacionados. Esta sección revisa el ecosistema de Selenium,
dividido en las siguientes categorías: enlaces de lenguajes, gestores de controladores,
marcos de trabajo, infraestructura de navegadores y comunidad.
Enlaces de Lenguajes
Como ya sabemos, el proyecto Selenium mantiene varios enlaces de lenguajes para
Selenium WebDriver: Java, JavaScript, Python, Ruby y C#. No obstante, también están
disponibles otros lenguajes. Tabla 1-2 resume estos enlaces de lenguajes para Selenium
WebDriver mantenidos por la comunidad.
Tabla 1-2. Enlaces de lenguajes no oficiales para Selenium WebDriver
Gestores de Controladores
Los controladores son componentes obligatorios para controlar los navegadores web de
manera nativa con Selenium WebDriver (ver Figura 1-1). Por esta razón, antes de usar la
API de Selenium WebDriver, necesitamos gestionar estos controladores. La gestión de
controladores es el proceso de descargar, configurar y mantener el controlador
adecuado para un navegador específico. Los pasos habituales en el procedimiento de
gestión de controladores son:
1. Descarga
Cada navegador tiene su propio controlador. Por ejemplo, usamos `chromedriver` para
controlar Chrome o `geckodriver` para Firefox (ver Tabla 1-1). El controlador es un
archivo binario específico de la plataforma. Por lo tanto, necesitamos descargar el
controlador adecuado para un sistema operativo determinado (típicamente, Windows,
macOS o Linux). Además, debemos considerar la versión del controlador, ya que una
versión del controlador es compatible con una versión (o rango) específica del
navegador. Por ejemplo, para usar Chrome 91.x, necesitamos descargar `chromedriver`
91.0.4472.19. Generalmente, encontramos la compatibilidad del navegador y el
controlador en la documentación del controlador o en las notas de la versión.
2. Configuración
Una vez que tengamos el controlador adecuado, necesitamos hacerlo disponible en
nuestro script de Selenium WebDriver.
3. Mantenimiento
Los navegadores web modernos (por ejemplo, Chrome, Firefox o Edge) se actualizan
automáticamente y en silencio, sin solicitar al usuario. Por esta razón, y en lo que
respecta a Selenium WebDriver, necesitamos mantener la compatibilidad de la versión
del navegador y el controlador a lo largo del tiempo para estos llamados navegadores
siempre verdes.
Como puedes ver, el proceso de mantenimiento de los controladores puede ser que
consuma tiempo. Además, puede causar problemas para los usuarios de Selenium
WebDriver (por ejemplo, pruebas fallidas debido a incompatibilidad entre el navegador
y el controlador después de una actualización automática del navegador). Por esta
razón, los llamados gestores de controladores tienen como objetivo llevar a cabo el
proceso de gestión de controladores de manera automatizada hasta cierto punto.
Tabla 1-3 resume los gestores de controladores disponibles para diferentes enlaces de
lenguajes.
En este libro, recomiendo utilizar WebDriverManager porque automatiza
todo el proceso de mantenimiento de controladores (es decir, descarga,
configuración y mantenimiento). Consulta el Apéndice B para obtener más
información sobre la gestión de controladores automatizada y manual.
Herramientas de Localización
La API de Selenium WebDriver proporciona diferentes formas de localizar elementos
web (ver Capítulo 3): por atributo (id, nombre o clase), por texto del enlace (completo o
parcial), por nombre de etiqueta, por selector de CSS (Hojas de Estilo en Cascada) o por
Lenguaje de Ruta XML (XPath). Herramientas específicas pueden ayudar a identificar y
generar estos localizadores. Tabla 1-4 muestra algunas de estas herramientas.
Tabla 1-4. Resumen de herramientas de localización
Frameworks
En ingeniería de software, un framework es un conjunto de bibliotecas y herramientas
utilizadas como base conceptual y tecnológica para el desarrollo de software. Selenium
es la base para frameworks que envuelven, mejoran o complementan sus características
predeterminadas. Tabla 1-5 contiene algunos de estos frameworks y bibliotecas basadas
en Selenium.
Tabla 1-5. Frameworks y bibliotecas de pruebas basados en Selenium
Infraestructura del Navegador
Podemos utilizar Selenium WebDriver para controlar navegadores locales instalados en
la máquina que ejecuta el script de WebDriver. Además, Selenium WebDriver también
puede controlar navegadores web remotos (es decir, aquellos que se ejecutan en otros
hosts). En este caso, podemos usar Selenium Grid para soportar la infraestructura de
navegadores remotos. Sin embargo, esta infraestructura puede ser difícil de crear y
mantener.
Alternativamente, podemos usar un proveedor de nube para externalizar la
responsabilidad de mantener la infraestructura del navegador. En el ecosistema de
Selenium, un proveedor de nube es una empresa o producto que ofrece servicios
gestionados para pruebas automatizadas.
Estas empresas suelen ofrecer soluciones comerciales para pruebas web y móviles. Los
usuarios de un proveedor de nube solicitan navegadores bajo demanda de diferentes
tipos, versiones y sistemas operativos. Además, estos proveedores suelen ofrecer
servicios adicionales para facilitar las actividades de prueba y monitoreo, como acceso
a grabaciones de sesiones o capacidades de análisis, por mencionar algunos. Algunos de
los proveedores de nube más relevantes para Selenium en la actualidad son Sauce Labs,
BrowserStack, LambdaTest, CrossBrowserTesting, Moon Cloud, TestingBot, Perfecto y
Testinium.
Otra solución que podemos utilizar para apoyar la infraestructura del navegador para
Selenium es Docker. Docker es una tecnología de software de código abierto que
permite a los usuarios empaquetar y ejecutar aplicaciones como contenedores ligeros y
portátiles. La plataforma Docker tiene dos componentes principales: el Docker Engine,
una herramienta para crear y ejecutar contenedores, y el Docker Hub, un servicio en la
nube para distribuir imágenes de Docker. En el dominio de Selenium, podemos usar
Docker para empaquetar y ejecutar navegadores en contenedores. La
Tabla 1-6 presenta un resumen de proyectos relevantes que utilizan Docker en el
ecosistema de Selenium.
Tabla 1-6. Recursos de Docker para Selenium
Comunidad
Debido a su naturaleza colaborativa, el desarrollo de software requiere la organización
e interacción de muchos participantes. En el ámbito de código abierto, podemos medir
el éxito de un proyecto por la relevancia de su comunidad. Selenium cuenta con el apoyo
de una gran comunidad de muchos participantes diferentes en todo el mundo. La Tabla
1-7 presenta un resumen de varios recursos de Selenium agrupados en las siguientes
categorías: documentación oficial, desarrollo, soporte y eventos.
Tabla 1-7. Recursos de la comunidad de Selenium
Fundamentos de Pruebas de Software
Las pruebas de software (o simplemente pruebas) consisten en la evaluación dinámica
de una pieza de software, llamada Sistema Bajo Prueba (SUT, por sus siglas en inglés),
mediante un conjunto finito de casos de prueba (o simplemente pruebas), para emitir
un veredicto sobre él. La prueba implica la ejecución del SUT utilizando valores de
entrada específicos para evaluar el resultado o el comportamiento esperado.
A primera vista, distinguimos dos categorías separadas de pruebas de software: manual
y automatizada. Por un lado, en las pruebas manuales, una persona (típicamente un
ingeniero de software o el usuario final) evalúa el SUT. Por otro lado, en las pruebas
automatizadas, utilizamos herramientas de software específicas para desarrollar
pruebas y controlar su ejecución contra el SUT. Las pruebas automatizadas permiten la
detección temprana de defectos (generalmente llamados errores o bugs) en el SUT, al
tiempo que brindan una gran cantidad de beneficios adicionales (por ejemplo, ahorro
de costos, retroalimentación rápida, cobertura de pruebas o reutilización, por
mencionar algunos). Las pruebas manuales también pueden ser un enfoque valioso en
algunos casos, por ejemplo, en pruebas exploratorias (es decir, los testers humanos
investigan y evalúan libremente el SUT).
No existe una clasificación universal para las numerosas formas de
pruebas presentadas en esta sección. Estos conceptos están sujetos a una
continua evolución y debate, al igual que la ingeniería de software.
Considéralo como una propuesta que puede adaptarse a una gran
cantidad de proyectos.
Niveles de Pruebas
Dependiendo del tamaño del SUT, podemos definir diferentes niveles de pruebas. Estos
niveles definen varias categorías en las que los equipos de software dividen sus
esfuerzos de prueba. En este libro, propongo una disposición en capas para representar
los diferentes niveles (ver Figura 1-5). Los niveles inferiores de esta estructura
representan las pruebas destinadas a verificar pequeñas piezas de software (llamadas
unidades). A medida que ascendemos en la pila, encontramos otros niveles (por
ejemplo, integración, sistema, etc.) en los que el SUT integra más y más componentes.
Figura 1-5. Representación en capas de los diferentes niveles de pruebas
El nivel más bajo de esta pila es la prueba unitaria. En este nivel, evaluamos unidades
individuales de software. Una unidad es un elemento de comportamiento observable
en particular. Por ejemplo, en la programación orientada a objetos, las unidades suelen
ser métodos o clases, mientras que, en la programación funcional, son funciones. La
prueba unitaria tiene como objetivo verificar que cada unidad se comporte como se
espera. Las pruebas unitarias automatizadas suelen ejecutarse muy rápido ya que cada
prueba ejecuta una pequeña cantidad de código en aislamiento. Para lograr este
aislamiento, podemos utilizar duplicados de prueba, piezas de software que reemplazan
los componentes dependientes de una unidad dada. Por ejemplo, un tipo popular de
duplicado de prueba en la programación orientada a objetos es el objeto simulado
(mock). Un objeto simulado imita un objeto real utilizando algún comportamiento
programado.
El siguiente nivel en la Figura 1-5 es la prueba de integración. En este nivel, diferentes
unidades se combinan para crear componentes compuestos. La prueba de integración
tiene como objetivo evaluar la interacción entre las unidades involucradas y exponer
defectos en sus interfaces.
Luego, en los niveles de prueba del sistema y de extremo a extremo (E2E), probamos el
sistema de software en su totalidad. Necesitamos desplegar el SUT y verificar sus
características de alto nivel para llevar a cabo estos niveles. La diferencia entre las
pruebas de sistema/de extremo a extremo y las pruebas de integración es que las
primeras involucran todos los componentes del sistema y al usuario final (generalmente
representado). En otras palabras, las pruebas de sistema y de extremo a extremo
evalúan el SUT a través de la Interfaz de Usuario (UI). Esta UI puede ser gráfica (GUI) o
no gráfica (por ejemplo, basada en texto u otros tipos).
La Pirámide de Pruebas
La pirámide de pruebas es una representación clásica de los niveles de pruebas. Mike
Cohn acuñó este concepto en 2009. En su concepción original, Cohn recomendó una
gran cantidad de pruebas unitarias como la base de los esfuerzos de prueba. Los
niveles siguientes (por ejemplo, pruebas de integración) son menos numerosos en
cada etapa, pero suelen ser más costosos (en términos de esfuerzo de desarrollo y
mantenimiento) y lentos (en términos de tiempo de ejecución). Esta propuesta puede
resultar impráctica para muchos proyectos porque las pruebas unitarias no siempre
forman parte de una suite de pruebas completa. Por esta razón, otros autores definen
diferentes formas para los niveles de prueba, como el trofeo de pruebas (en el que la
capa intermedia, es decir, la prueba de integración, es la más grande). Dado que la
relevancia de las diferentes categorías de pruebas puede variar de un proyecto a otro,
utilizo una estructura básica de pila para representar los diferentes niveles de
pruebas.
La Figura 1-6 ilustra la diferencia entre pruebas de sistema y pruebas de extremo a
extremo. Como puedes ver, por un lado, las pruebas de extremo a extremo involucran
el sistema de software y sus subsistemas dependientes (por ejemplo, bases de datos o
servicios externos). Por otro lado, las pruebas de sistema comprenden únicamente el
sistema de software, y estas dependencias externas generalmente se simulan.
La Figura 1-6. Representación basada en componentes de los diferentes niveles de
prueba
Las pruebas de aceptación son el nivel superior de la pila presentada. En este nivel, el
usuario final participa en el proceso de prueba. El objetivo de las pruebas de aceptación
es decidir si el sistema de software cumple con las expectativas del usuario final. Como
se puede ver en la Figura 1-6, al igual que las pruebas de extremo a extremo, las pruebas
de aceptación validan todo el sistema y sus dependencias. Por lo tanto, las pruebas de
aceptación también utilizan la interfaz de usuario (UI) para llevar a cabo la validación del
sistema bajo prueba (SUT).
El propósito principal de Selenium WebDriver es implementar pruebas de
extremo a extremo. No obstante, podemos utilizar WebDriver para llevar a cabo
pruebas de sistema cuando se simulan las llamadas al backend realizadas por el
sitio web bajo prueba. Además, podemos usar Selenium WebDriver en conjunto
con una herramienta de Desarrollo Guiado por Comportamiento (BDD) para
implementar pruebas de aceptación (ver Capítulo 9).
Verificación y Validación
Los niveles inferiores de la pila de pruebas que hemos visto (pruebas unitarias, de
integración, de sistema y de extremo a extremo) pertenecen a las pruebas de
desarrollo. Las pruebas de desarrollo son un proceso llevado a cabo por el equipo que
produce el sistema de software (es decir, desarrolladores, testers, etc.) durante la fase
de construcción del ciclo de vida del desarrollo de software. Las pruebas de desarrollo
son un tipo de verificación, ya que evaluamos si el software cumple con sus requisitos
funcionales y no funcionales establecidos (es decir, su especificación). Usando la
definición clásica establecida por Barry Boehm en 1984, la verificación permite
responder a la siguiente pregunta: “¿Estamos construyendo el producto
correctamente?”
El nivel superior de la pila de pruebas representada en la Figura 1-5 (es decir, las
pruebas de aceptación) pertenece a las pruebas de usuario, ya que involucra al
usuario final en el proceso de prueba. Las pruebas de aceptación son un tipo de
validación porque su objetivo es demostrar que el sistema de software cumple con
las expectativas del usuario final. La validación es un proceso más general que la
verificación, ya que la especificación del sistema no siempre refleja los deseos o
necesidades reales del usuario. Así, según Boehm, la validación permite responder a
la pregunta: “¿Estamos construyendo el producto correcto?”
Tipos de Pruebas
Dependiendo de la estrategia para diseñar casos de prueba, podemos implementar
diferentes tipos de pruebas. Los dos tipos principales de pruebas son:
Pruebas funcionales (también conocidas como pruebas de comportamiento o pruebas
de caja cerrada)
Evalúan el cumplimiento de un software con el comportamiento esperado (es decir,
sus requisitos funcionales).
Pruebas estructurales (también conocidas como pruebas de caja clara)
Determinan si la estructura del código del programa es defectuosa. Para ello, los
testers deben conocer la lógica interna de un software.
La diferencia entre estos tipos de pruebas es que las pruebas funcionales se basan en la
responsabilidad, mientras que las pruebas estructurales se basan en la implementación.
Ambos tipos se pueden realizar en cualquier nivel de prueba (unitario, de integración,
de sistema, de extremo a extremo o de aceptación). No obstante, las pruebas
estructurales se realizan comúnmente en el nivel unitario o de integración, ya que estos
niveles permiten un control más directo del flujo de ejecución del código.
Las pruebas de caja negra y de caja blanca son otros nombres para las pruebas
funcionales y estructurales, respectivamente. No obstante, estas
denominaciones no son recomendadas ya que la industria tecnológica está
tratando de adoptar términos más inclusivos y utilizar una terminología neutral
en lugar de un lenguaje potencialmente dañino.
Existen diferentes variantes de las pruebas funcionales. Por ejemplo:
Pruebas de interfaz de usuario (conocidas como pruebas GUI cuando la interfaz es
gráfica)
Evalúan si los elementos visuales de una aplicación cumplen con la funcionalidad
esperada. Cabe destacar que las pruebas de interfaz de usuario son diferentes de
los niveles de pruebas de sistema y de extremo a extremo, ya que las primeras
prueban la interfaz en sí misma, mientras que las últimas evalúan el sistema
completo a través de la interfaz.
Pruebas negativas
Evalúan el SUT bajo condiciones inesperadas (por ejemplo, excepciones esperadas).
Este término es el contrapunto de las pruebas funcionales regulares (a veces
llamadas pruebas positivas), en las que se evalúa si el SUT se comporta como se
espera (es decir, su camino feliz).
Pruebas de compatibilidad entre navegadores
Específicas para aplicaciones web. Su objetivo es verificar la compatibilidad de sitios
web y aplicaciones en diferentes navegadores web (tipos, versiones o sistemas
operativos).
Un tercer tipo de prueba, las pruebas no funcionales, incluye estrategias de prueba que
evalúan los atributos de calidad de un sistema de software (es decir, sus requisitos no
funcionales). Los métodos comunes de pruebas no funcionales incluyen, pero no se
limitan a:
Pruebas de rendimiento
Evalúan diferentes métricas de los sistemas de software, como el tiempo de respuesta,
la estabilidad, la confiabilidad o la escalabilidad. El objetivo de las pruebas de
rendimiento no es encontrar errores, sino identificar cuellos de botella en el sistema.
Existen dos subtipos comunes de pruebas de rendimiento:
- Pruebas de carga: Aumentan el uso del sistema simulando múltiples usuarios
concurrentes para verificar si puede operar dentro de los límites definidos.
- Pruebas de estrés: Ponen a prueba un sistema más allá de su capacidad operativa
para identificar los límites reales en los que el sistema falla.
Pruebas de seguridad
Intentan evaluar problemas de seguridad, como la confidencialidad (protección de la
divulgación de información), la autenticación (aseguramiento de la identidad del
usuario) o la autorización (determinación de derechos y privilegios del usuario), entre
otros.
Pruebas de usabilidad
Evalúan qué tan amigable es una aplicación de software. Esta evaluación también se
llama pruebas de Experiencia del Usuario (UX). Un subtipo de pruebas de usabilidad es:
- Pruebas A/B: Compara diferentes variaciones de la misma aplicación para
determinar cuál es más efectiva para sus usuarios finales.
Pruebas de accesibilidad
Evalúan si un sistema es utilizable por personas con discapacidades.
Usamos Selenium WebDriver principalmente para implementar pruebas
funcionales (es decir, interactuando con una interfaz de usuario de una aplicación
web para evaluar el comportamiento de la aplicación). Es poco probable que
usemos WebDriver para implementar pruebas estructurales.
Además, aunque no es su uso principal, podemos usar WebDriver para implementar
pruebas no funcionales, por ejemplo, para pruebas de carga, seguridad, accesibilidad o
localización (evaluación de configuraciones específicas del entorno) (ver Capítulo 9).
Metodologías de Prueba
El ciclo de vida del desarrollo de software es el conjunto de actividades, acciones y tareas
requeridas para crear sistemas de software en ingeniería de software. El momento en
el que los ingenieros de software diseñan e implementan casos de prueba en el ciclo de
vida del desarrollo depende del proceso de desarrollo específico (como iterativo, en
cascada o ágil, por nombrar algunos). Dos de las metodologías de prueba más relevantes
son:
Desarrollo Guiado por Pruebas (TDD)
TDD es una metodología en la que diseñamos e implementamos pruebas antes del
diseño e implementación real del software. A principios del siglo XXI, TDD se popularizó
con el auge de las metodologías de desarrollo de software ágil, como Programación
Extrema (XP). En TDD, un desarrollador primero escribe una prueba automatizada
(inicialmente fallida) para una característica dada. Luego, el desarrollador crea un
fragmento de código para pasar esa prueba. Finalmente, el desarrollador refactora el
código para lograr o mejorar la legibilidad y mantenibilidad.
Desarrollo de Pruebas al Final (TLD)
TLD es una metodología en la que diseñamos e implementamos pruebas después de
implementar el SUT. Esta práctica es típica en procesos de desarrollo de software
tradicionales, como en cascada (secuencial), incremental (multi-cascada), espiral (multi-
cascada orientado al riesgo) o Proceso Unificado de Rational (RUP).
Otra metodología de prueba relevante es el Desarrollo Guiado por Comportamiento
(BDD). BDD es una práctica de prueba derivada de TDD, y en consecuencia, diseñamos
pruebas en las primeras etapas del ciclo de vida del desarrollo de software en BDD. Para
ello, se llevan a cabo conversaciones entre el usuario final y el equipo de desarrollo
(típicamente con el líder del proyecto, gerente o analistas). Estas conversaciones
formalizan un entendimiento común del comportamiento deseado y del sistema de
software. Como resultado, creamos pruebas de aceptación en términos de uno o más
escenarios siguiendo una estructura Dado-Cuando-Entonces:
- Dado (Given)
Contexto inicial al comienzo del escenario.
- Cuando (When)
Evento que desencadena el escenario.
- Entonces (Then)
Resultado esperado.
TLD es una práctica común utilizada para implementar Selenium WebDriver. En
otras palabras, los desarrolladores/testers no implementan una prueba de
WebDriver hasta que el SUT está disponible. No obstante, también son posibles
diferentes metodologías. Por ejemplo, BDD es un enfoque común cuando se usa
WebDriver con Cucumber (ver Capítulo 9).
Relacionado estrechamente con el dominio de las metodologías de prueba,
encontramos el concepto de Integración Continua (CI). CI es una práctica de desarrollo
de software donde los miembros de un proyecto de software construyen, prueban e
integran continuamente su trabajo. Grady Booch acuñó el término CI en 1991. Ahora es
una estrategia popular para crear software.
Como muestra la Figura 1-7, CI tiene tres etapas separadas. Primero, usamos un
repositorio de código fuente, una instalación de alojamiento para almacenar y compartir
el código fuente de un proyecto de software. Normalmente usamos un sistema de
control de versiones (VCS) para gestionar este repositorio. Un VCS es una herramienta
que mantiene un registro del código fuente, quién realizó cada cambio y cuándo (a veces
llamado parche).
Figura 1-7. Proceso genérico de CI
Git, desarrollado inicialmente por Linus Torvalds, es el sistema de control de versiones
(VCS) preferido en la actualidad. Otras alternativas incluyen el sistema de versiones
concurrentes (CVS) o Subversion (SVN). Encima de Git, varias plataformas de
alojamiento de código (como GitHub, GitLab o Bitbucket) ofrecen servicios de
alojamiento de repositorios en la nube colaborativa para desarrollar, compartir y
mantener software.
Los desarrolladores sincronizan una copia local del repositorio (o simplemente, repo) en
sus entornos locales. Luego, realizan el trabajo de codificación usando esa copia local,
comprometiendo nuevos cambios al repositorio remoto (típicamente a diario). La idea
básica de CI es que cada commit activa la construcción y prueba del software con los
nuevos cambios. El conjunto de pruebas ejecutado para evaluar que un parche no rompa
la construcción se llama prueba de regresión. Un conjunto de regresión puede contener
pruebas de diferentes tipos, incluyendo unitarias, de integración, de extremo a extremo,
etc.
Cuando el número de pruebas es demasiado grande para la prueba de regresión,
generalmente elegimos solo una parte de las pruebas relevantes del conjunto completo.
Existen diferentes estrategias para seleccionar estas pruebas, por ejemplo, pruebas de
humo (es decir, pruebas que aseguran la funcionalidad crítica) o pruebas de cordura (es
decir, pruebas que evalúan la funcionalidad básica). Finalmente, podemos ejecutar el
conjunto completo como una tarea programada (típicamente nocturna).
Necesitamos usar una infraestructura del lado del servidor llamada servidor de
construcción para implementar un pipeline de CI. El servidor de construcción
generalmente informa un problema al desarrollador original cuando las pruebas de
regresión fallan. La Tabla 1-8 proporciona un resumen de varios servidores de
construcción.
Tabla 1-8. Servidores de construcción
Utilizo un repositorio de GitHub
(https://github.com/bonigarcia/seleniumwebdriver-java) para publicar y
mantener los ejemplos de prueba presentados en este libro. GitHub Actions
es el servidor de construcción para este repositorio (ver Capítulo 2).
Podemos extender un pipeline de CI típico de dos maneras (ver Figura 1-8):
- Entrega Continua (CD)
Después de CI, el servidor de construcción despliega la versión a un entorno de staging
(es decir, una réplica de un entorno de producción para fines de prueba) y ejecuta las
pruebas de aceptación automatizadas (si las hay).
- Despliegue Continuo
El servidor de construcción despliega la versión de software al entorno de producción
como el paso final.
Figura 1-8. Pipeline de Integración Continua, Entrega Continua y Despliegue Continuo
Cercano a CI, el término DevOps (desarrollo y operaciones) ha ganado relevancia.
DevOps es una metodología de software que promueve la comunicación y colaboración
entre diferentes equipos en un proyecto de software para desarrollar y entregar
software de manera eficiente. Estos equipos incluyen desarrolladores, testers, QA
(aseguramiento de calidad), operaciones (infraestructura), etc.
Herramientas de Automatización de Pruebas
Necesitamos utilizar algunas herramientas para implementar, ejecutar y controlar
pruebas automatizadas de manera efectiva. Una de las categorías más relevantes para
las herramientas de prueba es el marco de pruebas unitarias. El marco original en la
familia de pruebas unitarias (también conocido como xUnit) es SmalltalkUnit (o SUnit).
SUnit es un marco de pruebas unitarias para el lenguaje Smalltalk creado por Kent Beck
en 1999. Erich Gamma portó SUnit a Java, creando JUnit. Desde entonces, JUnit ha sido
muy popular, inspirando otros marcos de pruebas unitarias.
La Tabla 1-9 resume los marcos de pruebas unitarias más relevantes en diferentes
lenguajes.
Tabla 1-9. Frameworks de pruebas unitarias
Una característica común importante de la familia xUnit es la estructura de pruebas,
compuesta por cuatro fases (ver Figura 1-9):
Configuración (Setup)
El caso de prueba inicializa el SUT (Sistema Bajo Prueba) para exhibir el comportamiento
esperado.
Ejecución (Exercise)
El caso de prueba interactúa con el SUT. Como resultado, la prueba obtiene un resultado
del SUT.
Verificación (Verify)
El caso de prueba decide si el resultado obtenido del SUT es el esperado. Para ese fin, la
prueba contiene una o más afirmaciones. Una afirmación (o predicado) es una función
que devuelve un valor booleano y verifica si una condición esperada es verdadera. La
ejecución de las afirmaciones genera un veredicto de prueba (típicamente, aprobado o
fallido).
Desmontaje (Teardown)
El caso de prueba devuelve el SUT al estado inicial.
Figura 1-9. Estructura genérica de una prueba unitaria
Podemos usar frameworks de pruebas unitarias en conjunto con otras
bibliotecas o utilidades para implementar cualquier tipo de prueba. Por
ejemplo, como se explica en el Capítulo 2, usamos JUnit y TestNG para
integrar la llamada a la API de Selenium WebDriver, implementando
pruebas de extremo a extremo para aplicaciones web.
Las etapas de configuración (setup) y desmontaje (teardown) son opcionales en un caso
de prueba unitaria. Aunque no es estrictamente obligatorio, la verificación es altamente
recomendada. Incluso si los frameworks de pruebas unitarias incluyen capacidades para
implementar afirmaciones, es común incorporar bibliotecas de afirmaciones de
terceros. Estas bibliotecas tienen como objetivo mejorar la legibilidad del código de
prueba al proporcionar un conjunto rico de afirmaciones fluidas. Además, estas
bibliotecas ofrecen mensajes de error mejorados para ayudar a los testers a entender la
causa de una falla. La Tabla 1-10 contiene un resumen de algunas de las bibliotecas de
afirmaciones más relevantes para Java.
Tabla 1-10. Bibliotecas de afirmaciones para Java.
Como puedes ver en la Figura 1-9, el SUT (Sistema Bajo Prueba) generalmente puede
consultar otro componente, denominado el Componente Dependiente (DOC). En
algunos casos (por ejemplo, en el nivel de pruebas unitarias o de sistema), podríamos
querer aislar el SUT de los DOC(s). Podemos encontrar una amplia variedad de
bibliotecas de mock para lograr este aislamiento.
La Tabla 1-11 muestra un resumen completo de algunas de estas bibliotecas de mock
para Java.
Tabla 1-11. Bibliotecas de mock para Java.
La última categoría de herramientas de prueba que analizamos en esta sección es BDD
(Desarrollo Basado en Comportamiento), un proceso de desarrollo que crea pruebas de
aceptación. Existen muchas alternativas para implementar este enfoque. Por ejemplo,
la Tabla 1-12 muestra un resumen condensado de los frameworks de BDD relevantes.
Tabla 1-12. Frameworks de BDD.
Resumen y Perspectivas
Selenium ha recorrido un largo camino desde su creación en 2004. Muchos
profesionales lo consideran la solución estándar de facto para desarrollar pruebas de
extremo a extremo para aplicaciones web, y es utilizado por miles de proyectos en todo
el mundo. En este capítulo, has visto los fundamentos del proyecto Selenium
(compuesto por WebDriver, Grid e IDE). Además, Selenium tiene un ecosistema rico y
una comunidad activa. WebDriver es el corazón del proyecto Selenium, y es una
biblioteca que proporciona una API para controlar diferentes navegadores web (por
ejemplo, Chrome, Firefox, Edge, etc.) de manera programática.
La Tabla 1-13 contiene una visión general completa de los usos primarios y secundarios
de Selenium WebDriver.
Tabla 1-13. Usos primarios y secundarios de Selenium WebDriver.
En el próximo capítulo, descubrirás cómo configurar un proyecto de Java usando Maven
o Gradle como herramientas de construcción. Este proyecto contendrá pruebas de
extremo a extremo para aplicaciones web utilizando JUnit y TestNG como frameworks
de pruebas unitarias y llamadas a la API de Selenium WebDriver. Además, aprenderás a
controlar diferentes navegadores web (por ejemplo, Chrome, Firefox o Edge) con un
caso de prueba básico (la versión "hola mundo" de Selenium WebDriver).
CAPÍTULO 2
Preparándose para las Pruebas
Este capítulo tiene como objetivo implementar tu primera prueba de extremo a extremo
usando Selenium WebDriver y el lenguaje Java. Para hacerlo, primero revisamos los
requisitos técnicos en términos de conocimientos previos, hardware y software. En
segundo lugar, este capítulo proporciona una visión general para configurar un proyecto
de Java que incluya pruebas de Selenium WebDriver. Puedes usar una herramienta de
construcción como Maven o Gradle para facilitar la configuración del proyecto.
Finalmente, aprenderás a implementar una prueba básica de extremo a extremo con
Selenium WebDriver, es decir, una prueba "hola mundo". Implementaremos esta
prueba en varias versiones, usando diferentes navegadores web (como Chrome, Edge o
Firefox) y frameworks de pruebas unitarias (JUnit y TestNG). Recuerda que cada ejemplo
de código en este libro está disponible en un repositorio de GitHub de código abierto.
Por lo tanto, puedes reutilizar el contenido y la configuración de este repositorio como
base para tus propias pruebas.
Requisitos
El primer requisito para comenzar a usar Selenium WebDriver con Java es comprender
el lenguaje Java y la programación orientada a objetos. No es necesario ser un experto,
pero se requiere un conocimiento básico. Luego, puedes usar Selenium WebDriver en
cualquier sistema operativo principal: Windows, Linux o macOS. Por lo tanto, puedes
seleccionar el tipo de computadora que prefieras. En principio, no hay requisitos
específicos sobre su hardware en términos de memoria, CPU, disco duro, etc., por lo que
cualquier computadora de gama media funcionará.
Máquina Virtual de Java
A continuación, necesitas tener instalada una Máquina Virtual de Java (JVM) en tu
computadora. Hay dos tipos de distribuciones para la JVM. La primera opción es el
Entorno de Ejecución de Java (JRE), que incluye la JVM y la API estándar de Java. La
segunda opción es el Kit de Desarrollo de Java (JDK), que es el JRE más un Kit de
Desarrollo de Software (SDK) para Java (como el compilador javac y otras herramientas).
Dado que estamos desarrollando en Java, recomiendo usar JDK (aunque algunos IDEs
también incorporan un SDK para Java). Para la versión de Java, recomiendo usar al
menos JDK 8, ya que es la versión de soporte a largo plazo comúnmente soportada en
muchos proyectos de Java en el momento de escribir esto.
Editor de Texto o IDE
Para codificar nuestras pruebas en Java, necesitamos un editor de texto o un IDE. Los
IDEs proporcionan una excelente experiencia para el desarrollo porque tienen un
entorno completo (para codificación, ejecución, depuración, autocompletado, etc.). Sin
embargo, puedes obtener una experiencia similar usando cualquier editor de texto que
te guste, utilizado junto con herramientas de línea de comandos (para ejecución,
depuración, etc.). En general, depende de tus preferencias personales elegir uno u otro.
Algunas alternativas populares para editores de texto son Sublime Text, Atom,
Notepad++ o Vim, entre otros. Los IDEs incluyen Eclipse, IntelliJ IDEA, NetBeans o Visual
Studio Code.
Navegadores y Drivers
Una forma inicial de realizar automatización con Selenium WebDriver es usar
navegadores locales. Considero los siguientes navegadores para este libro: Chrome,
Edge y Firefox. Me refiero a ellos como navegadores principales por varias razones.
Primero, son muy populares en todo el mundo, y dado que estamos probando
aplicaciones web con Selenium WebDriver, queremos usar el mismo navegador que
nuestros posibles usuarios. En segundo lugar, estos navegadores son evergreen (es
decir, se actualizan automáticamente). En tercer lugar, estos navegadores están
disponibles para los principales sistemas operativos: Windows, Linux y macOS (a
diferencia de Safari, que también es un navegador popular pero solo está disponible en
macOS). Por último, estos navegadores están disponibles en el entorno de Integración
Continua (CI) utilizado en el repositorio de GitHub (es decir, GitHub Actions).
El último requisito para controlar navegadores web con Selenium WebDriver son los
binarios de los drivers: chromedriver (para Chrome), msedgedriver (para Edge) y
geckodriver (para Firefox). Como se discutió en el Capítulo 1, la gestión de drivers
involucra tres pasos: descarga, configuración y mantenimiento. Para evitar los
problemas potenciales explicados en ese capítulo, recomiendo encarecidamente
automatizar este proceso con WebDriverManager.
Apéndice B proporciona detalles detallados sobre el proceso de gestión de
drivers automatizado realizado por WebDriverManager. Además, por si lo
necesitas por alguna razón, este apéndice explica cómo llevar a cabo la
gestión de drivers manualmente.
Herramientas de Construcción
Otro componente importante es la herramienta de construcción. Las herramientas de
construcción son utilidades de software utilizadas para automatizar la creación de
aplicaciones ejecutables a partir del código fuente. Estas herramientas facilitan la
gestión de proyectos en términos de gestión de dependencias, compilación,
empaquetado, ejecución de pruebas y despliegue. En general, las herramientas de
construcción son una forma conveniente de automatizar el desarrollo de proyectos de
software, tanto en servidores de construcción (por ejemplo, GitHub Actions) como en
computadoras de desarrolladores. Por lo tanto, recomiendo encarecidamente usar una
herramienta de construcción para configurar tu proyecto. Las alternativas que cubrimos
en este libro son:
- Maven
Una herramienta de automatización de construcción de código abierto mantenida por
la Apache Software Foundation. Se utiliza principalmente para proyectos de Java,
aunque también admite otros lenguajes como C#, Ruby o Scala.
- Gradle
Otra herramienta de automatización de construcción de código abierto para el
desarrollo de software. Admite Java y otros lenguajes como Kotlin, Groovy, Scala, C/C++
o JavaScript.
Las versiones recomendadas son Maven 3+ y Gradle 6+. Para mayor completitud, utilizo
ambas herramientas de construcción en el repositorio de ejemplos. Nuevamente, la
elección final de usar una u otra depende de tus preferencias.
Si planeas usar un IDE para desarrollar y ejecutar tus pruebas, una herramienta de
construcción no es estrictamente necesaria. Sin embargo, recomiendo instalar al
menos una de estas herramientas en tu computadora para replicar el entorno que
se usa típicamente en servidores de construcción (por ejemplo, Jenkins, GitHub
Actions, etc.).
Software Opcional
Además del software ya explicado, hay otros programas que son convenientes para
aprovechar al máximo este libro. Primero, puedes usar Git para la gestión del código
fuente. Dado que los ejemplos de prueba presentados en este libro están disponibles en
GitHub, puedes usar Git para hacer fork (o clonar) y actualizar este repositorio.
La segunda herramienta opcional es Docker. En este libro, te muestro cómo usar Docker
para ejecutar navegadores en contenedores (ver Capítulo 6). Por esta razón,
recomiendo encarecidamente que instales un Docker Engine en tu computadora (está
disponible para Linux, macOS y Windows 10).
Finalmente, puedes usar diferentes navegadores web si los necesitas. Además de los
navegadores principales (Chrome, Edge y Firefox), es posible usar otros navegadores con
Selenium WebDriver, como Safari en macOS, o Opera, Chromium y HtmlUnit (un
navegador sin cabeza, es decir, un navegador sin GUI) en cualquier sistema operativo.
Navegadores obsoletos
Además de los navegadores ya presentados, puedes usar incluso más navegadores
con Selenium WebDriver. Sin embargo, no recomiendo estos navegadores ya que
están obsoletos y ya no se mantienen. Estos navegadores son:
Internet Explorer
El navegador web de Microsoft para sistemas Windows, lanzado por primera vez en
1995 y descontinuado en 2022.
PhantomJS
Navegador sin interfaz gráfica (headless), lanzado por primera vez en 2011 y
descontinuado en 2018.
Configuración del Proyecto
Puedes encontrar todos los ejemplos de código de este libro en un repositorio de
GitHub. Este repositorio es de código abierto, publicado bajo los términos de la licencia
Apache 2.0. El repositorio tiene múltiples propósitos. Primero, es conveniente agrupar
todos los ejemplos en un solo sitio. Segundo, puedes usar su configuración (Maven o
Gradle) como base para tus proyectos.
Las siguientes subsecciones describen los requisitos generales para crear un
proyecto Java que contenga pruebas de Selenium WebDriver. El Apéndice C
proporciona detalles específicos sobre la configuración de los repositorios de
ejemplos.
Estructura del Proyecto
La estructura del proyecto es la organización de directorios utilizada para almacenar los
diferentes activos de un proyecto de software (por ejemplo, código fuente, archivos
binarios, recursos estáticos, etc.). Maven y Gradle usan una estructura equivalente para
proyectos Java. Podemos ejecutar el repositorio de ejemplos con ambas herramientas
de construcción, gracias a esto.
Como se ilustra en la Figura 2-1, el siguiente conjunto de carpetas (etiquetadas como
carpetas de andamiaje) son idénticas en ambas herramientas de construcción:
src/main/java
Código fuente de la aplicación (es decir, archivos Java).
src/main/resources
Archivos de recursos de la aplicación (es decir, propiedades, archivos de
configuración, etc.).
src/test/java
Código fuente de pruebas (es decir, archivos Java utilizados para pruebas).
src/test/resources
Archivos de recursos de pruebas (es decir, activos adicionales utilizados para
pruebas).
Figura 2-1. Estructura del proyecto en Maven y Gradle
El resto de la estructura del proyecto es diferente en ambas herramientas de
construcción. La primera diferencia es el archivo de configuración. Por un lado, este
archivo es único y se llama `pom.xml` (Project Object Model) en Maven. Por otro lado,
hay dos archivos en Gradle para la configuración, llamados `settings.gradle` y
`build.gradle`. La segunda diferencia entre Maven y Gradle es la carpeta de salida. En
ambos casos, las herramientas de construcción crean esta carpeta para guardar el
resultado de la compilación (es decir, clases compiladas, archivos empaquetados
resultantes, etc.). El nombre de esta carpeta es `target` en Maven y `build` en Gradle.
Finalmente, Gradle contiene un conjunto de carpetas y archivos para el llamado wrapper
de Gradle. Este wrapper es un archivo de script (llamado `gradlew` para sistemas tipo
Unix y `gradlew.bat` para Windows) que ofrece los siguientes beneficios:
- Construir un proyecto sin instalar Gradle en la máquina local.
- Requerir el uso de una versión específica (que puede ser diferente de la instancia de
Gradle instalada localmente).
- Actualizar a una nueva versión fácilmente cambiando los artefactos del wrapper (en la
carpeta `gradle/wrapper`).
A partir de la versión 4, Maven ha adoptado el concepto de wrapper usando el script
`mvnw`.
Explicar todas las características proporcionadas por Maven y Gradle está fuera
del alcance de este libro. No obstante, puedes encontrar más información sobre
su ciclo de vida de compilación y comandos típicos en el Apéndice C. Para obtener
más información, considera leer la documentación oficial de Maven y Gradle.
Dependencias
Las dependencias de un proyecto de software son las bibliotecas o complementos
necesarios. Entre otras características, las herramientas de construcción permiten la
gestión automatizada de las dependencias del proyecto. Para ello, necesitamos
especificar las coordenadas de dichas dependencias en el archivo de configuración del
proyecto (consulta las subsecciones siguientes para obtener detalles específicos sobre
Maven y Gradle). Las coordenadas de un proyecto Java son un grupo de tres etiquetas
que identifican de manera única este proyecto (por ejemplo, una biblioteca,
complemento, etc.), a saber:
groupId
Organización, empresa, persona, etc., que creó el proyecto.
artifactId
Nombre único que identifica el proyecto.
version
Versión particular del proyecto. Por defecto, te recomiendo usar la última
versión de cada lanzamiento.
Versionado Semántico
Una forma popular de seleccionar una versión de proyecto es el Versionado
Semántico, también llamado SemVer. Según el manifiesto de SemVer, una versión
tiene tres partes separadas por puntos, es decir, MAYOR.MENOR.PARCHE. En
resumen, MAYOR identifica cambios incompatibles, MENOR identifica cambios
compatibles hacia atrás, y PARCHE identifica correcciones de errores.
Esta sección explica las dependencias de Java que utilizo en el repositorio de ejemplos.
Primero, por supuesto, necesitamos Selenium WebDriver para llevar a cabo la
automatización del navegador. Esta dependencia es la única estrictamente obligatoria.
Luego, recomiendo usar dependencias adicionales para la gestión automática de
controladores, el marco de pruebas unitarias, las afirmaciones fluidas y el registro. El
resto de esta sección explica la motivación y el uso básico de cada una de estas
utilidades.
Selenium WebDriver
Uno de los conceptos más relevantes de Selenium WebDriver es la jerarquía de
WebDriver, que es una colección de clases destinadas a controlar diferentes
navegadores web. Como se puede ver en la Figura 2-2, esta jerarquía sigue el paradigma
de programación orientada a objetos. En la parte superior, encontramos la interfaz
WebDriver, que es la progenitora de toda la estructura. La parte inferior de la jerarquía
corresponde a las clases de Java que controlan navegadores individuales. Por ejemplo,
necesitamos usar una instancia de la clase ChromeDriver para controlar un navegador
Chrome local. La Tabla 2-1 muestra un resumen completo de las principales clases de la
jerarquía de WebDriver y sus navegadores objetivo correspondientes.
Tabla 2-1. Descripción de la jerarquía de WebDriver
Figura 2-2. Jerarquía del objeto WebDriver
Gestión automática de controladores
Es obligatorio resolver el controlador correspondiente antes de instanciar un objeto de
la jerarquía de WebDriver. Por ejemplo, para controlar Chrome con ChromeDriver,
primero necesitamos tener instalado este navegador en la máquina local. En segundo
lugar, necesitamos gestionar chromedriver. Para evitar los problemas potenciales
relacionados con la gestión manual de controladores (ver Capítulo 1), recomiendo
realizar todo el proceso de gestión de controladores (descarga, configuración y
mantenimiento) de manera automática. En cuanto a Java, la implementación de
referencia es WebDriverManager, una biblioteca auxiliar de Selenium WebDriver que
permite la gestión automática de controladores. Esta sección explica cómo usar
WebDriverManager como una dependencia de Java.
Una vez que la dependencia de WebDriverManager esté resuelta en nuestro proyecto
(ver Apéndice C para los detalles de configuración), podemos usar la API de
WebDriverManager para gestionar los controladores. Esta API proporciona un conjunto
de singletons (llamados gestores) para descargar, configurar y mantener los
controladores. Estos singletons son accesibles a través de la clase WebDriverManager.
Por ejemplo, necesitamos invocar el método `chromedriver()` para gestionar el
controlador requerido por Chrome, es decir, chromedriver, de la siguiente manera:
Tabla 2-2. Resumen de las llamadas básicas de WebDriverManager para todos los
navegadores compatibles. Además de estas llamadas básicas (es decir, el método
`setup()`), WebDriverManager expone una API fluida para una configuración avanzada.
Consulta el Apéndice B para obtener más detalles sobre la metodología de
WebDriverManager, las capacidades de configuración y otros usos, como una
herramienta de interfaz de línea de comandos (desde la terminal), como servidor
(usando una API RESTlike [REpresentational State Transfer]), como agente (usando
instrumentación en Java) o como contenedor Docker.
Tabla 2-2. Llamadas básicas de WebDriverManager
Frameworks de pruebas unitarias
Como se explicó en el Capítulo 1, los marcos de pruebas unitarias son la base para crear
diferentes tipos de pruebas. Este libro te enseñará cómo implementar pruebas de
extremo a extremo para aplicaciones web utilizando Selenium WebDriver. Por lo tanto,
sugiero integrar las llamadas a Selenium WebDriver dentro de pruebas creadas con un
marco de pruebas unitarias particular. La alternativa que recomiendo es una de estas
opciones: JUnit 4, JUnit 5 (solo o en conjunto con Selenium-Jupiter, que es una extensión
para Selenium WebDriver), o TestNG. Las siguientes subsecciones proporcionan más
detalles sobre estas alternativas. Mi consejo es concentrarte en el marco de pruebas
unitarias y la herramienta de construcción que prefieras para continuar practicando con
los ejemplos presentados en el resto del libro.
JUnit 4. JUnit es un marco de pruebas unitarias para Java creado por Erich Gamma y
Kent Beck en 1999. Se considera el marco estándar de facto para desarrollar pruebas en
Java. En JUnit, una prueba es un método dentro de una clase Java utilizado para pruebas.
A partir de JUnit 4, las anotaciones de Java son los bloques de construcción para
desarrollar pruebas en JUnit. La anotación fundamental de JUnit 4 es `@Test`, ya que
permite identificar el(los) método(s) que contienen la lógica de prueba (es decir, el
código utilizado para ejercitar y verificar una parte del software). Además, hay otras
anotaciones para identificar los métodos utilizados para la configuración (es decir, lo
que sucede antes de las pruebas) y la limpieza (es decir, lo que sucede después de las
pruebas).
- @BeforeClass se ejecuta una vez antes de todas las pruebas.
- @Before se ejecuta antes de cada prueba.
- @After se ejecuta después de cada prueba.
- @AfterClass se ejecuta una vez después de todas las pruebas.
La Figura 2-3 muestra una representación gráfica del ciclo de vida básico de las pruebas
en JUnit 4.
Figura 2-3. Ciclo de vida de las pruebas en JUnit 4
JUnit 5. Debido a varias limitaciones en JUnit 4 (como la arquitectura monolítica o los
ejecutores de JUnit imposibles de componer), el equipo de JUnit lanzó una nueva versión
principal (es decir, JUnit 5) en 2017. JUnit ha sido rediseñado completamente en la
versión 5, siguiendo una arquitectura modular que consta de tres componentes (ver
Figura 2-4). El primer componente es el JUnit Platform, la base de todo el marco. El
objetivo del JUnit Platform es doble:
- Permite el descubrimiento y la ejecución (secuencial o paralela) de pruebas en la JVM
a través de la API de lanzador de pruebas. Esta API es utilizada típicamente por clientes
programáticos como herramientas de construcción e IDEs.
- Define la API del motor de pruebas para ejecutar pruebas en el JUnit Platform. Esta API
es utilizada típicamente por marcos que proporcionan modelos de programación para
pruebas.
Figura 2-4. Arquitectura de JUnit 5
Gracias a la API del motor de pruebas, los marcos de pruebas de terceros pueden
ejecutar pruebas sobre el JUnit Platform. Algunos ejemplos de marcos de pruebas
existentes que han implementado motores de pruebas para JUnit 5 son TestNG,
Cucumber o Spock. Además, JUnit 5 proporciona dos implementaciones listas para usar
de la API del motor de pruebas. Estos motores son los componentes restantes de la
arquitectura de JUnit 5, a saber:
Vintage
Motor de pruebas que proporciona compatibilidad hacia atrás con pruebas JUnit
heredadas (es decir, versiones 3 y 4).
Jupiter
Motor de pruebas que proporciona un nuevo modelo de programación y extensión.
Jupiter es un componente relevante de JUnit 5, ya que proporciona una API
completamente nueva para desarrollar pruebas utilizando un modelo de programación
robusto. Algunas de las características de este modelo de programación son pruebas
parametrizadas, ejecución paralela, etiquetado y filtrado, pruebas ordenadas, pruebas
repetidas y anidadas, y capacidades avanzadas para desactivar (ignorar) pruebas. Al
igual que JUnit 4, Jupiter también utiliza anotaciones de Java para declarar casos de
prueba. Por ejemplo, la anotación para identificar métodos con lógica de prueba es
también `@Test`. El nombre del resto de las anotaciones para el ciclo de vida básico de
las pruebas es un poco diferente en Jupiter: `@BeforeAll`, `@BeforeEach`, `@AfterEach`,
y `@AfterAll`. Como puedes ver en la Figura 2-5, cada una de estas anotaciones sigue el
mismo flujo de trabajo que JUnit 4.
Figura 2-5. Ciclo de vida de las pruebas en JUnit 5
Así, la estructura de una prueba en Jupiter usando Selenium WebDriver y
WebDriverManager es bastante similar en JUnit 4 y JUnit 5. Además del cambio en los
nombres de las anotaciones de configuración y limpieza, los métodos de prueba (y su
ciclo de vida) no necesitan ser públicos en el modelo de programación Jupiter.
Este libro te enseñará lo básico de Jupiter aplicado a pruebas de extremo a
extremo con Selenium WebDriver. Consulta el ejemplo de "hola mundo" en
la siguiente sección para una prueba completa basada en JUnit 5. Por favor,
revisa la documentación de JUnit 5 para obtener más detalles.
JUnit 5 con Selenium-Jupiter. El modelo de extensión de Jupiter permite agregar
características personalizadas al modelo de programación predeterminado. Para ello,
Jupiter proporciona una API que los desarrolladores pueden extender (usando
interfaces llamadas puntos de extensión) para proporcionar funcionalidad
personalizada. Las categorías de estos puntos de extensión son:
Callbacks del ciclo de vida de las pruebas
Para incluir lógica personalizada en diferentes momentos del ciclo de vida de la
prueba.
Resolución de parámetros
Para implementar la inyección de dependencias (es decir, parámetros inyectados
en métodos de prueba o constructores).
Plantillas de prueba
Para repetir las pruebas basadas en un contexto dado.
Ejecución condicional de pruebas
Para habilitar o deshabilitar pruebas dependiendo de condiciones personalizadas.
Manejo de excepciones
Para gestionar excepciones de Java durante la prueba y su ciclo de vida.
Instancia de prueba
Para crear y procesar instancias de la clase de prueba.
Intercepción de invocaciones
Para interceptar llamadas al código de prueba (y decidir si estas llamadas deben
proceder o no).
Como desarrollador de Jupiter, puedes implementar tu extensión personalizada o usar
las existentes. La Tabla 2-3 muestra algunos ejemplos de extensiones de Jupiter.
Tabla 2-3. Extensiones de Jupiter
Selenium-Jupiter es una opción atractiva en el contexto de este libro, ya que permite
usar Selenium WebDriver en pruebas de Jupiter de manera fluida. Las bases de
Selenium-Jupiter son las siguientes (consulta la siguiente sección para una prueba de
"hola mundo" basada en Selenium-Jupiter):
Código de plantilla reducido en casos de prueba
Gracias a la característica de resolución de parámetros proporcionada por el modelo
de programación de Jupiter, Selenium-Jupiter permite declarar un objeto de la
jerarquía WebDriver (por ejemplo, ChromeDriver, FirefoxDriver, etc.) para controlar
los navegadores web desde las pruebas, ya sea como un parámetro de constructor o
de prueba.
Gestión automatizada de drivers a través de WebDriverManager
Gracias a los callbacks del ciclo de vida de las pruebas proporcionados por el modelo
de extensión, el uso de WebDriverManager es completamente transparente para los
usuarios de Selenium-Jupiter.
Capacidades avanzadas para pruebas de extremo a extremo
Esto incluye, por ejemplo, integración fluida con Docker, plantillas de prueba (para
pruebas cruzadas de navegadores) o capacidades de resolución de problemas y
monitoreo (por ejemplo, grabaciones de sesiones o capturas de pantalla
configurables).
TestNG. El último marco de pruebas unitarias que utilizo en este libro es TestNG. Algunas
de las características más significativas que ofrece TestNG son la ejecución paralela de
pruebas, la priorización de pruebas, pruebas basadas en datos utilizando anotaciones
personalizadas y la creación de informes detallados en HTML.
De manera similar a JUnit 4 y Jupiter, TestNG también utiliza anotaciones de Java para
declarar pruebas y su ciclo de vida (es decir, lo que sucede antes y después de cada
prueba). De nuevo, la anotación @Test se usa para designar métodos de prueba. Luego,
proporciona las anotaciones @BeforeClass y @BeforeMethod para especificar la
configuración de la prueba, y @AfterMethod y @AfterClass para la limpieza (ver Figura
2-6). Además, TestNG permite agrupar las pruebas contenidas en clases Java utilizando
la siguiente terminología:
• Suite consiste en una o más pruebas.
• Test consiste en una o más clases.
• Class es una clase Java con uno o más métodos de prueba, por ejemplo, anotados
con @Test.
Siguiendo esta notación, y como se representa en la Figura 2-6, TestNG proporciona
anotaciones adicionales para ejecutar lógica personalizada antes y después de la suite y
de la(s) prueba(s).
Figura 2-6. Ciclo de vida de las pruebas en TestNG
Aserciones fluidas
Como se introdujo en el Capítulo 1, existen diferentes bibliotecas para aserciones. Estas
bibliotecas suelen proporcionar un conjunto completo de aserciones fluidas y mensajes
de error detallados. Entre estas alternativas, utilizo la biblioteca AssertJ en el repositorio
de ejemplos. La razón es doble. Primero, podemos seleccionar los métodos disponibles
para afirmar datos rápidamente utilizando la función de autocompletado en los IDEs
(generalmente disponible usando Ctrl + espacio después del método estático
`assertThat`). La Figura 2-7 muestra un ejemplo de la inspección de este método usando
un IDE (Eclipse en este caso).
Figura 2-7. Inspección manual de los métodos de aserción disponibles en AssertJ usando
Eclipse
La segunda ventaja de AssertJ en comparación con otras opciones es que permite una
cadena de aserciones utilizando notación de puntos. Gracias a esto, podemos
concatenar varias condiciones para crear aserciones más legibles, por ejemplo:
Registro (Logging)
Finalmente, recomiendo usar una biblioteca de registro para rastrear tu código Java.
Como sabes, el registro es una forma sencilla en la que los programadores siguen
eventos cuando se ejecuta el software. El registro generalmente se realiza escribiendo
mensajes de texto en un archivo o en la salida estándar, y permite rastrear programas y
diagnosticar problemas. Hoy en día, es común usar bibliotecas específicas para realizar
registros de manera efectiva. Estas bibliotecas ofrecen diferentes beneficios, como el
nivel de granularidad de los mensajes (por ejemplo, depuración, advertencia o error), la
marcación de tiempo o las capacidades de configuración.
Hola Mundo
Estamos listos para juntar todas las piezas explicadas en este capítulo e implementar
nuestra primera prueba de extremo a extremo. Como sabes, un programa "Hola
Mundo" es un fragmento simple de código que muchos lenguajes de programación usan
para ilustrar la sintaxis básica. El Ejemplo 2-1 muestra la versión de Selenium WebDriver
de este clásico "Hola Mundo".
El siguiente ejemplo usa JUnit 5 como el marco de pruebas unitarias para
incrustar la llamada a Selenium WebDriver. Recuerda que puedes encontrar
los otros sabores (es decir, JUnit 4, JUnit 5 con Selenium-Jupiter y TestNG)
en el repositorio de ejemplos.
Ejemplo 2-1. Hola Mundo usando Chrome y JUnit 5
1. Declaramos un atributo en Java usando la interfaz `WebDriver`. Utilizamos esta
variable en las pruebas para controlar los navegadores web con Selenium
WebDriver.
2. En la configuración para todas las pruebas dentro de esta clase (es decir,
ejecutada una vez), llamamos a WebDriverManager para gestionar el
controlador necesario. En este ejemplo, dado que usamos Chrome como
navegador, necesitamos resolver `chromedriver`.
3. En la configuración de la prueba (ejecutada una vez por método de prueba),
instanciamos el objeto `WebDriver` para controlar Chrome. En otras palabras,
creamos un objeto del tipo `ChromeDriver`.
4. La lógica de la prueba utiliza la API de Selenium WebDriver a través de la variable
`driver`. Primero, la prueba ejercita el Sistema Bajo Prueba (SUT). Para ello,
abrimos el sitio de práctica usando el método `get()` de nuestra variable
`webdriver` (que representa un navegador Chrome, en este caso).
5. Obtenemos el título de la página web usando el método `getTitle()`.
6. Para fines de depuración, registramos ese título usando el nivel DEBUG.
7. La última parte de la prueba contiene una aserción de AssertJ. En este caso,
verificamos que el título de la página web sea el esperado.
8. Al final de cada prueba, necesitamos cerrar el navegador. Para ello, podemos
invocar el método `quit()` del objeto `driver` (consulta más información sobre
cómo cerrar los objetos WebDriver en el Capítulo 3).
Puedes ejecutar esta prueba de diferentes maneras. Recomiendo obtener una copia
local del repositorio de ejemplos. Puedes usar el sitio web de GitHub para descargar una
copia completa del código fuente. Alternativamente, puedes usar Git para clonar el
repositorio usando la línea de comandos, así:
git clone https://github.com/bonigarcia/selenium-webdriver-java
Luego, puedes usar Maven o Gradle (como se explica en el Apéndice C) para ejecutar las
pruebas desde la línea de comandos. Además, puedes importar los proyectos
Maven/Gradle clonados en un IDE. Los IDEs proporcionan capacidades integradas para
ejecutar la prueba desde su interfaz gráfica. Por ejemplo, la Figura 2-8 muestra una
captura de pantalla de la ejecución de la prueba de "Hola Mundo" anterior en Eclipse
(en este caso, usando el comando Run → Run As → JUnit Test). Observa que en la
consola integrada (en la parte inferior de la imagen), los primeros rastros corresponden
a la resolución del controlador por WebDriverManager. Luego, el navegador se inicia a
través de `chromedriver`, y finalmente, podemos ver los rastros de la prueba
(concretamente, el título de la página web).
Figura 2-8. Captura de pantalla de la ejecución del "Hola Mundo" de Selenium
WebDriver en Eclipse
Las versiones de "Hola Mundo" usando JUnit 4 y TestNG son casi idénticas a JUnit 5, pero
usan anotaciones diferentes para el ciclo de vida de la prueba (por ejemplo, @Before en
JUnit 4 en lugar de @BeforeEach en JUnit 5, etc.). En cuanto a JUnit 5 junto con
Selenium-Jupiter, el código es un poco más compacto. El Ejemplo 2-2 muestra esta
versión de "Hola Mundo". Como puedes ver, no es necesario declarar la configuración y
la limpieza. Simplemente necesitamos declarar el objeto WebDriver que queremos
como parámetro de prueba (en este caso, FirefoxDriver), y Selenium-Jupiter se encarga
de la gestión del controlador (también con WebDriverManager), la instanciación del
objeto y la eliminación del navegador.
Ejemplo 2-2. Hola mundo usando Firefox y Selenium-Jupiter
Usando Navegadores Adicionales
Además de los navegadores principales que estoy mencionando en este libro (es decir,
Chrome, Edge y Firefox), el repositorio de ejemplos contiene el test de hola mundo
utilizando otros navegadores: Opera, Chromium, Safari y HtmlUnitDriver (un
controlador compatible con Selenium WebDriver para el navegador sin interfaz
HtmlUnit). Estos tests, contenidos en el paquete `helloworld_otherbrowsers` de este
repositorio, son ligeramente diferentes de las versiones básicas de hola mundo. Por
ejemplo, el Ejemplo 2-3 muestra la configuración de la clase de JUnit 5 para el test de
hola mundo usando Opera. Dado que este navegador podría no estar disponible en la
máquina que ejecuta el test (por ejemplo, Opera no está disponible en GitHub Actions),
utilizo suposiciones para deshabilitar el test condicionalmente en tiempo de ejecución.
Ejemplo 2-3. Configuración de la clase usando Opera y JUnit 5
1. Usamos WebDriverManager para localizar la ruta del navegador.
2. Si esta ruta no existe, asumimos que el navegador no está instalado en el
sistema, por lo que el test se omite (usando una suposición de AssertJ).
Como de costumbre, puedes encontrar este test usando otros marcos de pruebas
unitarias en el repositorio de ejemplos. Las versiones de JUnit 5 y TestNG utilizan la
configuración de test equivalente al fragmento anterior. Sin embargo, hay una
diferencia al usar JUnit 5 junto con Selenium-Jupiter. Como puedes ver en el Ejemplo 2-
4, Selenium-Jupiter simplifica la lógica de suposiciones usando una anotación
personalizada (llamada EnabledIfBrowserAvailable) para deshabilitar los tests
dependiendo de la disponibilidad del navegador (Safari en este ejemplo).
Ejemplo 2-4. Hola mundo usando Safari y JUnit 5 junto con Selenium-Jupiter
Para controlar Safari con Selenium WebDriver, necesitamos configurar Safari
manualmente para autorizar la automatización remota. Para ello, primero mostramos
el menú de desarrollo haciendo clic en la opción del menú Safari → Preferencias →
pestaña Avanzado. Luego, activamos la casilla "Mostrar el menú de desarrollo". Después
de eso, debería aparecer el menú "Desarrollar". Finalmente, hacemos clic en la opción
"Permitir Automatización Remota" (ver Figura 2-9).
Figura 2-9. Habilitar la automatización remota en Safari en macOS
Resumen y Perspectivas
Este capítulo proporciona las bases para desarrollar pruebas end-to-end para
aplicaciones web usando Selenium WebDriver y Java. La primera decisión importante
que debes tomar es decidir en qué marco de pruebas unitarias integrar las llamadas de
Selenium WebDriver para implementar estas pruebas. Con el fin de ofrecer diversidad y
completitud, propongo cuatro opciones en este libro: JUnit 4, JUnit 5, JUnit 5 junto con
Selenium-Jupiter y TestNG. Todas son equivalentes para pruebas básicas de Selenium
WebDriver. Para usos más avanzados, el Capítulo 8 cubrirá las características específicas
de cada marco de pruebas que podrían ser relevantes para las pruebas de WebDriver
(por ejemplo, pruebas parametrizadas para pruebas cruzadas de navegadores). Otra
decisión que debes tomar es elegir una herramienta de construcción. En este libro,
propongo dos opciones: Maven y Gradle. Una vez más, ambos son similares para las
prácticas estándar de desarrollo.
La segunda parte de este libro se centra en la API de Selenium WebDriver y comienza a
continuación. Para empezar, el Capítulo 3 cubre las nociones fundamentales de la API
de Selenium WebDriver en términos de objetos WebDriver, localización de elementos
web, suplantación de usuarios (acciones de teclado y ratón) y estrategias de espera.
Como de costumbre, este capítulo está guiado por ejemplos de código disponibles en el
repositorio alojado en GitHub.
PARTE II
La API de Selenium WebDriver
Selenium WebDriver es una biblioteca de código abierto que permite controlar
navegadores web (por ejemplo, Chrome, Edge o Firefox, por nombrar algunos)
programáticamente, como lo haría un usuario real. Proporciona una API para múltiples
navegadores que puedes usar para implementar pruebas end-to-end para aplicaciones
web. Esta parte del libro presenta un resumen detallado de la API de Selenium
WebDriver. Los siguientes capítulos buscan ser muy prácticos. Por esta razón, explico
cada característica de la API de Selenium WebDriver usando pruebas listas para usar
disponibles en el repositorio de ejemplos en GitHub.
CAPÍTULO 3
Fundamentos de WebDriver
Este capítulo presenta los aspectos elementales de la API de Selenium WebDriver. Para
ello, primero revisaremos las diferentes maneras de crear instancias de la jerarquía de
WebDriver (por ejemplo, ChromeDriver, EdgeDriver, FirefoxDriver, etc.). También
exploraremos los principales métodos disponibles en estos objetos. Entre ellos, localizar
los diferentes elementos en una página web es esencial. Así, descubrirás los posibles
localizadores, es decir, estrategias para encontrar los elementos dentro de una página
web (llamados WebElement en la API de Selenium WebDriver), como por nombre de
etiqueta, texto del enlace, atributo HTML (identificador, nombre o clase), selector CSS o
XPath. Otro aspecto crítico de la API de Selenium WebDriver cubierto en este capítulo
es la suplantación de acciones de usuario (es decir, interacciones automatizadas con
páginas web utilizando el teclado y el ratón). La última parte de este capítulo presenta
la capacidad de esperar por elementos web. Esta característica es crítica debido a la
naturaleza dinámica y asíncrona de las aplicaciones web.
Uso Básico de WebDriver
Esta sección cubre tres aspectos fundamentales relacionados con los objetos
WebDriver. Primero, revisamos las diferentes formas de crearlos. En segundo lugar,
estudiamos sus operaciones básicas. Finalmente, analizamos las diferentes formas de
disponer de estos objetos (típicamente al final de una prueba, para cerrar el navegador).
Creación de WebDriver
Como se introdujo en el Capítulo 2, para controlar navegadores con Selenium WebDriver
en Java, el primer paso es crear instancias de WebDriver. Así, necesitamos crear un
objeto ChromeDriver al usar Chrome, EdgeDriver para Edge, FirefoxDriver para Firefox,
y así sucesivamente. La forma básica de crear instancias de estos tipos es usar el
operador `new` en Java. Por ejemplo, creamos un objeto ChromeDriver de la siguiente
manera:
El uso del operador new para crear instancias de WebDriver es perfectamente correcto,
y puedes usarlo en tus pruebas. Sin embargo, vale la pena revisar otras posibilidades
que pueden ofrecer beneficios adicionales dependiendo de los casos de uso específicos
para crear estos objetos. Estas alternativas son los constructores WebDriver y
WebDriverManager.
Constructor WebDriver
La API de Selenium WebDriver proporciona un método incorporado que sigue el patrón
de diseño builder (constructor) para crear instancias de WebDriver. Esta característica
está accesible a través del método estático builder() de la clase RemoteWebDriver y
proporciona una API fluida para crear objetos WebDriver. La Tabla 3-1 presenta los
métodos disponibles para este constructor.
El Ejemplo 3-1 muestra un esqueleto de prueba utilizando el constructor WebDriver.
Tabla 3-1. Métodos del constructor WebDriver
El Capítulo 5 explica los detalles sobre las capacidades específicas de los navegadores
(como ChromeOptions). En este punto, usamos estas clases solo para seleccionar un tipo
de navegador (por ejemplo, ChromeOptions para Chrome, EdgeOptions para Edge o
FirefoxOptions para Firefox).
Ejemplo 3-1. Esqueleto de prueba usando el constructor WebDriver
1. Como de costumbre, antes de la instanciación real de WebDriver, resolvemos el
controlador requerido (chromedriver en este ejemplo) usando
WebDriverManager.
2. Creamos la instancia de WebDriver usando el constructor WebDriver. Dado que
queremos usar Chrome en esta prueba, usamos un objeto ChromeOptions como
el argumento de capacidades (utilizando el método oneOf()).
Desde el punto de vista funcional, este ejemplo funciona de la misma manera que las
pruebas de hola mundo regulares presentadas en el Capítulo 2. Sin embargo, la API del
constructor WebDriver permite especificar fácilmente un comportamiento diferente.
Considera el siguiente fragmento como ejemplo. Este código cambia el método de
configuración y crea una instancia de `SafariDriver`. Supongamos que la instanciación de
este objeto no es posible (típicamente, cuando la prueba no se ejecuta en macOS, y por
lo tanto, Safari no está disponible en el sistema). En ese caso, usamos Chrome como
navegador alternativo.
Constructor de WebDriverManager
Otra posibilidad para crear objetos WebDriver es usar WebDriverManager. Además de
resolver los controladores, a partir de la versión 5, WebDriverManager proporciona una
utilidad de constructor de WebDriver. El Ejemplo 3-2 muestra un esqueleto de prueba
utilizando este constructor.
Ejemplo 3-2. Esqueleto de prueba usando el constructor de WebDriverManager
1. WebDriverManager resuelve el controlador requerido (chromedriver en este
caso) y crea una instancia del tipo adecuado de WebDriver (ChromeDriver en
este caso) en una sola línea.
Este enfoque tiene diferentes beneficios. Primero, permite pruebas menos verbosas ya
que la resolución del controlador y la instanciación de WebDriver son simultáneas.
Segundo, permite especificar el tipo de navegador (es decir, Chrome, Firefox, etc.)
simplemente seleccionando un administrador específico (es decir, `chromedriver()`,
`firefoxdriver()`, etc.). Además, podemos parametrizar fácilmente la selección de un
administrador para crear pruebas cruzadas entre navegadores (ver Capítulo 8).
Finalmente, WebDriverManager te permite especificar capacidades específicas del
navegador (ver Capítulo 5) y utilizar navegadores en un contenedor Docker sin esfuerzo
(ver Capítulo 6).
WebDriverManager mantiene una referencia a los objetos WebDriver creados usando
este enfoque. Además, lanza un "shutdown hook" para vigilar la correcta disposición de
las instancias de WebDriver. Si las sesiones de WebDriver están activas cuando la JVM
está apagándose, WebDriverManager cierra estos navegadores. Puedes experimentar
con esta característica eliminando el método `teardown()` del ejemplo anterior.
Aunque WebDriverManager cierra los objetos WebDriver automáticamente, te
recomiendo hacerlo explícitamente en cada prueba. De lo contrario, en el caso
típico de ejecutar una suite de pruebas, todos los navegadores permanecen
abiertos hasta el final de la ejecución de la suite de pruebas.
Métodos de WebDriver
La interfaz WebDriver proporciona un grupo de métodos que son la base de la API de
Selenium WebDriver. La Tabla 3-2 presenta un resumen de estos métodos. El Ejemplo
3-3 muestra una prueba básica utilizando varios de estos métodos.
Tabla 3-2. Métodos de WebDriver
A partir de ahora, ilustro los ejemplos mostrando solo la lógica de las pruebas. Estas
pruebas utilizan un objeto WebDriver creado antes de la prueba (en el método de
configuración) y cerrado después de la prueba (en el método de limpieza).
Como convención, muestro las pruebas de JUnit 5 en el libro (aunque también puedes
encontrarlas para JUnit 4, Selenium-Jupiter y TestNG en el repositorio de ejemplos).
Ejemplo 3-3. Prueba utilizando varios métodos básicos de la API de Selenium WebDriver
1. Abrimos el sitio web de práctica.
2. Verificamos que el título de la página sea el esperado.
3. Confirmamos que la URL actual sigue siendo la misma.
4. Comprobamos que el HTML fuente de la página contenga una etiqueta
determinada.
Convención para Nombres y Clases de Prueba en el Repositorio de Ejemplos
Para facilitar la localización de pruebas en el repositorio de ejemplos, sigo una
convención de nombres. El nombre de cada prueba presentada siempre comienza
con la palabra "test" seguida de una etiqueta descriptiva. Luego, utilizo esta
etiqueta como prefijo de la clase Java que contiene la prueba. Por ejemplo, puedes
encontrar la prueba anterior (llamada `testBasicMethods`) en las clases
`BasicMethodsJUnit4Test` (usando JUnit 4), `BasicMethodsJupiterTest` (usando
JUnit 5), `BasicMethodsSelJupTest` (usando JUnit 5 más Selenium-Jupiter) y
`BasicMethodsNGTest` (usando TestNG).
Identificador de Sesión
Cada vez que instanciamos un objeto WebDriver, el controlador subyacente (por
ejemplo, chromedriver, geckodriver, etc.) crea un identificador único llamado sessionId
para rastrear la sesión del navegador. Podemos usar este valor en nuestra prueba para
identificar de manera única una sesión de navegador. Para ello, necesitamos invocar el
método getSessionId() en nuestro objeto de controlador.
Nota que este método no está disponible en la Tabla 3-2, porque pertenece a la clase
RemoteWebDriver. En la práctica, los tipos que usamos para controlar navegadores (por
ejemplo, ChromeDriver, FirefoxDriver, etc.) heredan de esa clase. Por lo tanto,
simplemente necesitamos convertir el objeto WebDriver a RemoteWebDriver para
invocar el método getSessionId(). El Ejemplo 3-4 muestra una prueba básica que lo
utiliza.
Ejemplo 3-4. Prueba de lectura del sessionId
1. Convertimos el objeto driver a RemoteWebDriver y leemos su sessionId.
2. Verificamos que el sessionId tenga algún valor.
3. Registramos el sessionId en la salida estándar.
Eliminación de WebDriver
Como puedes ver en la Tabla 3-2, hay dos métodos para eliminar los objetos WebDriver,
llamados `close()` y `quit()`. Como regla general, utilizo `quit()` en los ejemplos, ya que
este método cierra el navegador y todas las ventanas asociadas. Por otro lado, el método
`close()` solo termina la ventana actual. Por lo tanto, solo utilizo `close()` en el caso de
manejar diferentes ventanas (o pestañas) en el mismo navegador, y quiero cerrar
algunas de las ventanas (o pestañas) y seguir utilizando el resto.
Ubicación de WebElements
Uno de los aspectos más relevantes de la API de Selenium WebDriver es la capacidad de
interactuar con los diferentes elementos de una página web. Estos elementos son
manejados por Selenium WebDriver utilizando la interfaz `WebElement`, una
abstracción para los elementos HTML. Como se introdujo en la Tabla 3-2, hay dos
métodos para ubicar un `WebElement` en una página web dada. Primero, el método
`findElement()` devuelve la primera ocurrencia (si la hay) de un nodo dado en el
Documento Objeto Modelo (DOM). Segundo, el método `findElements()` devuelve una
lista de nodos del DOM. Ambos métodos aceptan un parámetro `By`, que especifica la
estrategia de localización.
El Documento Objeto Modelo (DOM)
El DOM es una interfaz multiplataforma que permite representar documentos similares
a XML (por ejemplo, páginas web basadas en HTML) en una estructura de árbol. El
Ejemplo 3-5 muestra una pequeña página web; la estructura del árbol DOM asociada en
memoria está representada en la Figura 3-1. Como puedes ver, cada etiqueta HTML (por
ejemplo, `<html>`, `<head>`, `<body>`, `<a>`, etc.) produce un nodo (o elemento) en el
árbol. Luego, cada atributo HTML estándar (por ejemplo, charset, href, etc.) produce
una propiedad DOM equivalente. Además, el contenido de texto de las etiquetas HTML
está disponible en el árbol resultante. Lenguajes como JavaScript utilizan métodos del
DOM para acceder y modificar la estructura del árbol. Gracias a esto, las páginas web
son dinámicas y pueden cambiar su diseño y contenido en respuesta a eventos del
usuario.
Ejemplo 3-5. Página web básica
Figura 3-1. Estructura del DOM generada a partir del Ejemplo 3-5
Métodos de WebElement
La Tabla 3-3 contiene un resumen de los métodos disponibles en la clase WebElement.
Encontrarás ejemplos de cada método en las siguientes secciones de este capítulo.
Tabla 3-3. Métodos de WebElement
Estrategias de Ubicación
Selenium WebDriver proporciona ocho estrategias básicas de ubicación, resumidas en
la Tabla 3-4. Además, como se explica en las siguientes subsecciones, existen otras
estrategias avanzadas de ubicación, a saber, localizadores compuestos y relativos.
Especificamos los localizadores básicos utilizando la clase By en la API de Selenium
WebDriver. Las siguientes subsecciones muestran ejemplos de todas estas estrategias.
Utilizamos el formulario web de práctica para tal fin. La Figura 3-2 muestra una captura
de pantalla de este formulario.
Tabla 3-4. Resumen de las estrategias de ubicación en Selenium WebDriver
Figura 3-2. Formulario web de práctica utilizado en los ejemplos de localización
Ubicación por nombre de etiqueta HTML
Una de las estrategias más básicas para encontrar elementos web es por nombre de
etiqueta. El Ejemplo 3-6 muestra una prueba que utiliza esta estrategia. Esta prueba
ubica el área de texto disponible en el formulario web de práctica, cuyo marcado HTML
es el siguiente:
Ejemplo 3-6. Prueba utilizando una estrategia de localización por nombre de etiqueta
1. Usamos el localizador By.tagName("textarea") para encontrar este elemento. En
este caso, dado que es el único área de texto declarada en la página web,
podemos estar seguros de que el método findElement() localizará este elemento.
2. Aseguramos que el valor del atributo rows sea el mismo que el definido en el
marcado HTML.
Ubicación por atributos HTML (name, id, class)
Otra estrategia de localización directa es encontrar elementos web por un atributo
HTML, es decir, nombre, id o clase. Considera el siguiente texto de entrada disponible
en el formulario web de práctica. Observa que incluye los atributos estándar `class`,
`name`, `id`, y el atributo no estándar `myprop` (incluido para ilustrar la diferencia entre
varios métodos de WebDriver). El Ejemplo 3-7 muestra una prueba que utiliza esta
estrategia.
Ejemplo 3-7. Prueba utilizando localizadores por atributos HTML (name, id y class)
1. Ubicamos el campo de texto por nombre.
2. Afirmamos que el elemento está habilitado (es decir, el usuario puede escribir
en él).
3. Encontramos el mismo elemento de entrada de texto por id.
4. Esta afirmación (y las dos siguientes) devuelve el mismo valor ya que el atributo
type es estándar, y como se explicó anteriormente, se convierte en una
propiedad en el DOM.
5. Esta afirmación (y las dos siguientes) devuelve valores diferentes ya que el
atributo myprop no es estándar, y por lo tanto, no está disponible como una
propiedad del DOM.
6. Ubicamos una lista de elementos por clase.
7. Verificamos que la lista tenga más de un elemento.
8. Comprobamos que el primer elemento encontrado por clase es el mismo que el
campo de texto ubicado anteriormente.
Ubicación por texto de enlace
El último localizador básico es por texto de enlace. Esta estrategia tiene dos formas:
localizar por texto exacto y por texto parcial. Utilizamos un enlace en el formulario web
de práctica para ilustrar este localizador en el siguiente marcado HTML. Luego, el
Ejemplo 3-8 muestra una prueba utilizando estos localizadores.
Ejemplo 3-8. Prueba utilizando localizadores por texto de enlace
1. Localizamos un elemento por su texto de enlace completo.
2. Comprobamos que su nombre de etiqueta sea a.
3. Verificamos que su propiedad CSS cursor sea pointer (es decir, el estilo
típicamente utilizado para elementos clicables).
4. Encontramos un elemento por texto de enlace parcial. Este enlace será el mismo
que en el paso 1.
5. Verificamos que ambos elementos compartan la misma posición y tamaño.
Ubicación de WebElements por selectores CSS
Las estrategias que hemos visto hasta ahora son fáciles de aplicar, pero también tienen
algunas limitaciones. Primero, localizar por nombre de etiqueta puede ser complicado,
ya que es probable que la misma etiqueta aparezca muchas veces en una página web.
Además, encontrar elementos por atributos HTML (nombre, id o clase) es un enfoque
limitado, ya que estos atributos no siempre están disponibles. Además, los ids pueden
ser autogenerados y volátiles entre diferentes sesiones. Por último, la ubicación por
texto de enlace está limitada solo a enlaces. Para superar estas limitaciones, Selenium
WebDriver proporciona dos poderosas estrategias de localización: selector CSS y XPath.
Existen muchas posibilidades para crear selectores CSS. La Tabla 3-5 muestra un
resumen completo con los selectores CSS básicos.
Tabla 3-5. Selectores CSS básicos
El siguiente extracto de HTML muestra el campo de texto oculto disponible en el
formulario web de práctica. Luego, el Ejemplo 3-9 ilustra una forma posible de localizar
este elemento utilizando un selector CSS. Una ventaja de este localizador es que el
selector seguirá funcionando incluso cuando se cambie el nombre del atributo en el
marcado HTML.
Ejemplo 3-9. Prueba utilizando un localizador básico con un selector CSS
1. Usamos un selector CSS para localizar el campo de entrada oculto.
2. Verificamos que el campo oculto no sea visible.
Existen muchas posibilidades para crear selectores CSS avanzados. La Tabla 3-6 muestra
un resumen con algunos de ellos. La referencia completa de selectores CSS está
disponible en la recomendación oficial del W3C.
Tabla 3-6. Selectores CSS avanzados
Considera el siguiente fragmento de HTML (como de costumbre, contenido en el
formulario web de práctica). Como puedes ver, hay un par de casillas de verificación:
una de ellas está marcada y la otra no. Podemos determinar qué elemento está marcado
usando la API de Selenium WebDriver y selectores CSS. Con ese fin, el Ejemplo 3-10
utiliza una pseudoclase de CSS.
Ejemplo 3-10. Prueba utilizando localizadores avanzados con selectores CSS
1. Usamos la pseudoclase `checked` para localizar casillas de verificación marcadas.
2. Comprobamos que el ID del elemento es el esperado.
3. Confirmamos que el seleccionado está marcado.
4. Usamos la pseudoclase `checked` y el operador `not` para localizar casillas de
verificación predeterminadas.
5. Verificamos que el ID del elemento es el esperado.
6. Confirmamos que el seleccionado no está marcado.
Ubicación por XPath
XPath (XML Path Language) es una forma poderosa de navegar por el DOM de
documentos similares a XML, como las páginas HTML. Incluye más de doscientas
funciones integradas para crear consultas avanzadas para seleccionar nodos. Existen dos
tipos de consultas XPath. Primero, las consultas absolutas utilizan el símbolo barra (/)
para recorrer el DOM desde el nodo raíz. Por ejemplo, considerando la página HTML
básica en el Ejemplo 3-5, para seleccionar el elemento de enlace presente en esta página
usando este enfoque, necesitamos la siguiente consulta XPath:
/html/body/a
Las consultas XPath absolutas son fáciles de crear, pero tienen un inconveniente
relevante: cualquier cambio mínimo en el diseño de la página haría que un localizador
construido con esta estrategia falle. Por esta razón, como regla general, se desaconseja
el uso de XPaths absolutos. En su lugar, las consultas relativas son más convenientes.
La sintaxis general para las consultas XPath relativas es la siguiente:
//tagname[@attribute='value']
El Ejemplo 3-11 muestra una prueba con un localizador XPath para seleccionar el campo
oculto en la web de práctica.
Ejemplo 3-11. Prueba utilizando un localizador básico con XPath
1. Localizamos el campo oculto en la web de práctica.
2. Verificamos que este elemento no sea visible para el usuario.
El verdadero poder de XPath proviene de sus funciones integradas. La Tabla 3-7 contiene
algunas de las funciones XPath más relevantes. Puedes encontrar la referencia completa
de XPath en las Recomendaciones de XPath de W3C.
Tabla 3-7. Resumen de funciones integradas relevantes de XPath
El ejemplo 3-12 muestra cómo utilizar localizadores XPath para los botones de radio
disponibles en el formulario web de práctica. El marcado HTML para estos botones de
radio es:
Ejemplo 3-12. Prueba utilizando localizadores avanzados con XPath
1. Usamos XPath para localizar los botones de radio marcados.
2. Verificamos que el ID del elemento sea el esperado.
3. Confirmamos que el seleccionado está marcado.
4. Usamos XPath para localizar los botones de radio no marcados.
5. Verificamos que el ID del elemento sea el esperado.
6. Confirmamos que el seleccionado no está marcado.
La sección "¿Qué estrategia deberías usar?" en la página 80 proporciona una
comparación entre selectores CSS y XPath y da algunas pistas para
seleccionar una u otra estrategia de localización.
Encontrar localizadores en una página web
Como se introdujo en la Tabla 1-4 del Capítulo 1, hay diferentes herramientas que
podemos usar para ayudar a generar localizadores para nuestras pruebas de WebDriver.
Esta sección muestra cómo usar las principales características de las herramientas de
desarrollo integradas en los navegadores principales, es decir, Chrome DevTools para
navegadores basados en Chromium (por ejemplo, Chrome y Edge) y Firefox Developer
Tools (para Firefox).
Puedes abrir ambas herramientas de desarrollo haciendo clic derecho en la parte de la
interfaz de usuario de la página web que deseas probar y luego seleccionando la opción
"Inspeccionar". La Figura 3-3 muestra una captura de pantalla de Chrome DevTools
colocada en la parte inferior del navegador (puedes moverla si lo deseas).
Las herramientas de desarrollo proporcionan diferentes formas de ubicar elementos en
una página web. Primero, usamos el selector de elementos haciendo clic en el ícono
(una flecha sobre una caja) en la esquina superior izquierda del panel de herramientas
de desarrollo. Luego, podemos mover el mouse sobre la página para resaltar cada
elemento web e inspeccionar el panel de elementos para verificar su marcado,
atributos, etc.
Figura 3-3. Uso de Chrome DevTools mientras navegas por el sitio de práctica
En la misma vista, podemos usar la herramienta para copiar su selector CSS o XPath
haciendo clic derecho en el elemento y luego seleccionando la opción del menú
“Copiar”. Este mecanismo permite obtener el selector completo de CSS o XPath. Puede
ser el primer enfoque para generar un localizador rápidamente, aunque no recomiendo
usar estos localizadores directamente ya que tienden a ser frágiles (es decir, vinculados
al diseño actual de la página) y son difíciles de leer.
Para crear localizadores CSS o XPath robustos, necesitamos considerar las características
específicas de las páginas web con las que estamos trabajando y crear un selector
personalizado basado en ese conocimiento. De nuevo, las herramientas de desarrollo
pueden ayudarnos en esta tarea. Podemos presionar la combinación de teclas Ctrl + F
para buscar por cadena, selector CSS o XPath en Chrome DevTools.
Figura 3-4 muestra un ejemplo de esta función en acción.
Observa que estamos utilizando el formulario web de práctica y escribimos la cadena
`#my-text-id`, que corresponde al elemento con un ID dado utilizando un selector CSS.
DevTools encontró el elemento web en la página y lo resaltó.
Figura 3-4. Buscando selector CSS en Chrome DevTools
Podemos usar un enfoque similar en Firefox. Necesitamos usar el panel de la consola y
escribir $$("css-selector") para buscar por selector CSS o $x("xpath-query") para
consultas XPath. La Figura 3-5 muestra cómo localizar el primer elemento de entrada de
texto del formulario web de práctica por ID, utilizando un selector CSS y una consulta
XPath.
Figura 3-5. Buscando selector CSS y XPath en las herramientas de desarrollo de Firefox
Localizadores Compuestos
La API de Selenium WebDriver tiene varias clases de soporte que permiten la
composición de los diferentes tipos de localizadores que hemos visto. Estas clases son:
ByIdOrName(String idOrName)
Busca por ID, y si no está disponible, busca por nombre.
ByChained(By... bys)
Busca elementos en una secuencia (es decir, el segundo debe aparecer dentro del
primero, y así sucesivamente).
ByAll(By... bys)
Busca elementos que coincidan con una serie de estrategias de localización
(siguiendo una condición lógica "y" para estos localizadores).
El Ejemplo 3-13 muestra una prueba utilizando **ByIdOrName**. Esta prueba busca el
siguiente campo de selección de archivo disponible en el formulario web de práctica.
Observa que este campo especifica el atributo `name` (pero no `id`).
Ejemplo 3-13. Prueba utilizando el localizador compuesto por id o nombre
1. Usamos un localizador por id o nombre.
2. Verificamos que el elemento tenga el atributo name.
3. Confirmamos la ausencia del atributo name en el mismo elemento.
El Ejemplo 3-14 muestra dos pruebas que ilustran la diferencia entre ByChained y ByAll.
Ambos localizadores utilizan el formulario web de práctica nuevamente. Si inspeccionas
su código fuente, notarás que hay tres <div class="row"> individuales dentro del <form>.
Ejemplo 3-14. Prueba utilizando los localizadores compuestos by chained y by all
1. Usamos el localizador utilizando ByChained.
2. Encontramos un elemento, ya que solo un elemento row está dentro del
formulario.
3. Usamos el localizador utilizando ByAll.
4. Encontramos cinco elementos, ya que el localizador coincide con un elemento
<form> más cuatro <div class="row"> disponibles en la página.
Localizadores Relativos
La versión 4 de Selenium WebDriver incorpora una nueva forma de encontrar elementos
en una página web: los localizadores relativos. Estos nuevos localizadores tienen como
objetivo encontrar elementos web en relación con otro elemento conocido. Esta
característica se basa en el modelo de caja CSS. El modelo determina que cada elemento
de un documento web se representa utilizando una caja rectangular. La **Figura 3-6**
muestra un ejemplo de este modelo de caja para un elemento web dado en el
formulario de práctica.
Figura 3-6. Formulario de práctica mostrando el modelo de caja de un elemento web
Usando este modelo de caja, los localizadores relativos disponibles en la API de Selenium
WebDriver permiten encontrar elementos en relación con la posición de otro elemento
web. Para ello, primero necesitamos ubicar ese elemento web utilizando las estrategias
de localización estándar (por ejemplo, por id, nombre, atributo, etc.). Luego, debemos
especificar el tipo de localizador obtenido por proximidad al elemento web original
utilizando el método estático `with` de la clase `RelativeLocator`. Como resultado,
obtenemos un objeto `RelativeBy`, que extiende la clase abstracta `By`, utilizada en las
estrategias de localización estándar. Un objeto `RelativeBy` proporciona los siguientes
métodos para realizar la localización relativa:
above()
Encuentra el(los) elemento(s) ubicados arriba del elemento original.
below()
Encuentra el(los) elemento(s) ubicados debajo del elemento original.
near()
Encuentra el(los) elemento(s) ubicados cerca del elemento original. La distancia
por defecto para considerar un elemento como cercano a otro es de cien
píxeles. Este localizador está sobrecargado para especificar otra distancia.
toLeftOf()
Encuentra el(los) elemento(s) ubicados a la izquierda del elemento original.
toRightOf()
Encuentra el(los) elemento(s) ubicados a la derecha del elemento original.
Ejemplo 3-15. Prueba utilizando localizadores relativos
1. Ubicamos el enlace cuyo texto es Return to index.
2. Especificamos el tipo de localizador relativo, que será por nombre de etiqueta
input.
3. Usamos un localizador relativo para encontrar un elemento web (que debería
ser un campo de entrada) arriba del elemento web original (es decir, un enlace).
4. Verificamos que el elemento ubicado arriba del enlace de referencia es un campo
de solo lectura (consulta la Figura 3-2 para verificarlo).
Los localizadores relativos pueden ser útiles para encontrar elementos
basados en la posición relativa de otros elementos. Por otro lado, esta
estrategia puede ser muy sensible al diseño de la página. Por ejemplo,
debes tener cuidado al usar localizadores relativos en páginas responsivas,
ya que el diseño puede variar dependiendo del área de visualización.
Un ejemplo desafiante
Los ejemplos que hemos visto hasta ahora son razonablemente simples. Ahora, veamos
un caso de uso más complejo. Un elemento no predeterminado en el sitio de práctica
es el selector de fecha. Como su nombre sugiere, este elemento proporciona una forma
conveniente de seleccionar fechas utilizando una interfaz gráfica web.
Dado que el marco CSS utilizado en el sitio de práctica es Bootstrap, implementé el
selector de fecha usando `bootstrap-datepicker`. Este selector de fecha está adjunto a
un campo de entrada. Cuando el usuario hace clic en este campo, aparece un calendario
en la página web (ver Figura 3-7). El usuario puede seleccionar una fecha específica
haciendo clic en la fecha preferida al navegar por los diferentes días, meses y años.
Figura 3-7. Selector de fecha en el formulario web de práctica
Queremos implementar una prueba automatizada utilizando Selenium WebDriver que
seleccione el día y mes actuales pero el año anterior, interactuando con la interfaz
gráfica del selector de fechas. El Ejemplo 3-16 muestra la implementación resultante.
Para seguir este ejemplo, te recomiendo abrir el formulario web de práctica (ver
URL en el ejemplo de código) en tu navegador y utilizar las herramientas de
desarrollo para inspeccionar los elementos internos del selector de fechas,
prestando atención a las diferentes estrategias de selección utilizadas.
Ejemplo 3-16. Prueba interactuando con un selector de fechas
1. Obtén la fecha actual del reloj del sistema. Utilizamos la API estándar `java.time` para
esto.
2. Haz clic en el selector de fechas para abrir el calendario. Usamos un localizador por
nombre (`By.name("my-date")`).
3. Haz clic en el mes actual buscando por texto. Usamos una consulta XPath para este
localizador. Después de este paso, el resto de los meses del año aparece en la interfaz
gráfica del selector de fechas.
4. Haz clic en la flecha izquierda usando localizadores relativos (es decir, a la derecha del
elemento del mes). Después de este paso, el calendario se mueve al año anterior.
5. Haz clic en el mes actual de ese año. Usamos un selector CSS aquí.
6. Haz clic en el día actual de ese mes. Usamos una consulta XPath en este paso. Después
del clic, la fecha se selecciona y el valor aparece en el campo de texto de entrada.
7. Obtén la fecha final en el campo de texto. Usamos un localizador básico por atributo
aquí.
8. Asegúrate de que la fecha esperada sea igual a la seleccionada en el selector de
fechas. Calculamos la fecha esperada usando Java estándar y, como es habitual, AssertJ
para la afirmación.
¿Qué estrategia deberías usar?
En esta sección, revisamos las diferentes alternativas que la API de Selenium WebDriver
permite para localizar elementos en una página web. Este tema es una de las rutinas
más fundamentales para la automatización de navegadores con Selenium WebDriver.
Quizás te estés preguntando: ¿Cuál es la mejor estrategia que debería usar? Como diría
el Dr. Alfred Lanning (personaje de la novela y la película *I, Robot*): "Esa, detective, es
la pregunta correcta." En mi opinión, es una pregunta difícil y no tiene una respuesta
simple. En otras palabras, la respuesta a esta pregunta podría ser "depende". Esta
sección presenta varias sugerencias para identificar una estrategia de localización
adecuada para casos de uso comunes. Primero, la Tabla 3-8 compara las diferentes
estrategias de localización.
Tabla 3-8. Pros, contras y casos de uso típicos de las diferentes estrategias de
localización
Como puedes ver en esta tabla, los selectores CSS y XPath comparten los mismos pros,
contras y casos de uso. ¿Significa esto que estas estrategias son iguales? La respuesta es
no. Ambas son muy poderosas y permiten la creación de localizadores complejos. Sin
embargo, hay diferencias relevantes entre ellas. La Tabla 3-9 resume estas diferencias.
Tabla 3-9. Algunas diferencias entre XPath y el selector CSS
Para ilustrar mejor la diferencia entre XPath y los selectores CSS, la Tabla 3-10 compara
localizadores específicos utilizando ambas estrategias.
Tabla 3-10. Ejemplos comparando XPath y selector CSS
En conclusión, podemos ver que XPath proporciona la estrategia más general. No
obstante, hay algunos casos en los que los selectores CSS ofrecen una sintaxis más
amigable (por ejemplo, localizar por id o clase) y un mejor rendimiento general.
Acciones del teclado
Como se introdujo en la Tabla 3-3, dos métodos principales en los objetos de WebDriver
permiten simular acciones del teclado del usuario: `sendKeys()` y `clear()`. El Ejemplo 3-
17 muestra una prueba utilizando estos métodos.
Ejemplo 3-17. Prueba simulando eventos del teclado
1. Utilizamos el formulario web de práctica para localizar el campo de texto llamado
my-text.
2. Simulamos la escritura en él usando el método sendKeys().
3. Evaluamos que el valor del campo de entrada es el esperado.
4. Restablecemos su contenido usando clear().
5. Evaluamos que el valor del campo de entrada está vacío.
Espera Manual en el Teardown de la Prueba
Si inspeccionas el código completo de algunas pruebas en el repositorio de ejemplo,
descubrirás una pausa en el método de teardown usando Thread.sleep(). Como se
explica en "Estrategias de Espera" en la página 94, este tipo de espera suele
considerarse una mala práctica (es decir, una característica en el código fuente que
puede llevar a efectos indeseables). No obstante, me he tomado la libertad de usarla
en algunos ejemplos con fines instructivos, es decir, para facilitar la inspección manual
del navegador al ejecutar la prueba localmente. Recomiendo eliminar estas líneas si
planeas reutilizar los ejemplos en una suite de pruebas real.
Carga de Archivos
Hay varios casos de uso en los que necesitaremos simular acciones del teclado al
interactuar con páginas web a través de Selenium WebDriver. El primero de ellos es la
carga de archivos. El mecanismo estándar para cargar archivos en aplicaciones web es
utilizando elementos <input> con type="file". Por ejemplo, el formulario web de práctica
contiene uno de estos elementos:
La API de Selenium WebDriver no proporciona un mecanismo para manejar entradas de
archivos. En su lugar, debemos tratar los elementos de entrada para cargar archivos
como entradas de texto normales, por lo que necesitamos simular que el usuario los
está escribiendo. En particular, debemos escribir la ruta absoluta del archivo que se va
a cargar. El Ejemplo 3-18 ilustra cómo hacerlo.
Ejemplo 3-18. Prueba de carga de un archivo
1. Localizamos el campo de entrada usando una estrategia por nombre.
2. Creamos un archivo temporal utilizando Java estándar.
3. Escribimos su ruta absoluta en el campo de entrada.
4. Enviamos el formulario.
5. Verificamos que la página resultante (definida en el atributo de acción del
formulario) sea diferente de la página web inicial.
La ruta del archivo enviada al campo de entrada debe corresponder a un
archivo existente en la máquina que ejecuta la prueba. De lo contrario, la
prueba falla con una excepción `InvalidArgumentException`. Consulte
"Excepciones de WebDriver" en la página 142 del Capítulo 5 para obtener
más detalles sobre las excepciones.
Al cargar un archivo en un navegador remoto (como se explica en el Capítulo 6),
debemos cargar el archivo desde el sistema de archivos local explícitamente. La
siguiente línea muestra cómo especificar un detector de archivos local.
Controles deslizantes de rango
Una situación similar ocurre con los campos de formulario `<input type="range">`. Estos
elementos permiten a los usuarios seleccionar un número dentro de un rango utilizando
un control deslizante gráfico. Puedes encontrar un ejemplo en el formulario web de
práctica:
De nuevo, la API de Selenium WebDriver no proporciona ninguna utilidad particular para
manejar estos campos. Podemos interactuar con ellos emulando acciones del teclado
con Selenium WebDriver. El Ejemplo 3-19 muestra una prueba de interacción con estos
campos.
Ejemplo 3-19. Prueba de selección de un número con un control deslizante en el
formulario
Enviamos una tecla del teclado al campo de rango disponible en el formulario web de
práctica. Utilizamos la clase `Keys` disponible en la API de Selenium WebDriver para
manejar caracteres especiales del teclado. En particular, enviamos la tecla de flecha
derecha al control deslizante, y como resultado, se mueve hacia la derecha (es decir,
aumenta el número seleccionado dentro del rango). Afirmamos que el valor
seleccionado resultante es diferente del que estaba en la posición original.
Acciones del Ratón
Además del teclado, el otro dispositivo de entrada principal para interactuar con
aplicaciones web es el ratón. Primero, el clic simple (también conocido como clic
izquierdo o simplemente clic) es emulado por la API de Selenium WebDriver usando el
método click(), que es uno de los métodos disponibles por WebElement en Selenium
WebDriver. Esta sección muestra ejemplos de dos casos de uso típicos utilizando esta
función: navegación web e interacción con casillas de verificación y botones de radio en
formularios web.
Otras acciones comunes del ratón son el clic derecho (también conocido como clic de
contexto), el doble clic, el movimiento del cursor, arrastrar y soltar, o el mouseover.
Selenium WebDriver permite emular estas acciones utilizando una clase auxiliar llamada
Actions. Consulta la siguiente sección para obtener más detalles. Finalmente, el
desplazamiento es posible en WebDriver mediante la ejecución de JavaScript. Explico
esta función en “Ejecutando JavaScript” en la página 101.
Navegación Web
El Ejemplo 3-20 muestra una prueba que implementa la navegación web automatizada
con Selenium WebDriver. Esta prueba localiza enlaces utilizando XPath y hace clic en
ellos, invocando el método `click()`. Al final, lee el contenido de texto del cuerpo de la
página web y verifica que contiene una cadena esperada.
Ejemplo 3-20. Prueba de navegación haciendo clic en enlaces
Casillas de Verificación y Botones de Radio
El Ejemplo 3-21 muestra otro uso básico del método click() para manipular casillas de
verificación y botones de radio. Para verificar el estado esperado de estos elementos
después de la acción de clic, utilizamos una aserción basada en el resultado del método
isSelected().
Ejemplo 3-21. Prueba de interacción con casillas de verificación y botones de radio
Gestos del Usuario
Selenium WebDriver proporciona la clase `Actions`, un recurso poderoso para
automatizar diferentes acciones de usuario, tanto para el teclado como para el ratón.
Esta clase sigue el patrón de diseño builder. De esta manera, puedes encadenar varios
métodos (es decir, diferentes acciones) y ejecutar todos ellos al final llamando a `build()`.
La Tabla 3-11 resume los métodos públicos disponibles en esta clase. Revisamos estos
métodos a través de ejemplos en las siguientes subsecciones.
Tabla 3-11. Métodos de Actions
Clic Derecho y Doble Clic
Puedes encontrar una página de demostración que utiliza tres menús desplegables en
el sitio de práctica (consulta la Figura 3-8). En esta página, el primer menú desplegable
aparece al hacer clic en su botón, el segundo utiliza el clic derecho, y el tercero requiere
un doble clic. El Ejemplo 3-22 muestra una prueba utilizando esta página para emular
gestos de usuario a través de la clase `Actions` de WebDriver.
Figura 3-8. Página web de práctica con menús desplegables
Ejemplo 3-22. Prueba utilizando clic derecho y doble clic
1. Utilizamos `contextClick()` en el menú desplegable del medio.
2. Verificamos que el menú del medio se muestra correctamente.
3. Utilizamos `doubleClick()` en el menú desplegable de la derecha.
4. Verificamos que el menú de la derecha se muestra correctamente.
Mouseover
El segundo ejemplo que maneja `Actions` utiliza una página web de muestra que
implementa un mouseover. Esta página muestra cuatro imágenes. Cada una muestra
una etiqueta de texto debajo de la imagen cuando el puntero del ratón está sobre ella.
El Ejemplo 3-23 contiene una prueba que utiliza esta página. La Figura 3-9 muestra esta
página cuando el ratón está sobre la primera imagen.
Ejemplo 3-23. Prueba utilizando mouseover
1. Iteramos una lista de cadenas para localizar las cuatro imágenes de la página.
2. Utilizamos XPath para encontrar cada elemento `<img>`.
3. Usamos `moveToElement()` para mover el puntero del ratón al centro de cada
imagen.
4. Utilizamos localizadores relativos para encontrar la etiqueta mostrada.
5. Usamos aserciones para verificar que el texto sea el esperado.
Figura 3-9. Página web de práctica con imágenes de mouseover
Arrastrar y Soltar
El Ejemplo 3-24 ilustra el uso de arrastrar y soltar. Esta prueba utiliza la página web de
práctica mostrada en la Figura 3-10.
Ejemplo 3-24. Prueba utilizando arrastrar y soltar
1. Localizamos el elemento que se puede arrastrar.
2. Utilizamos `dragAndDropBy()` para mover este elemento un número fijo de
píxeles (100) cuatro veces (derecha, abajo, izquierda y arriba).
3. Afirmamos que la posición del elemento es la misma que al principio.
4. Encontramos un segundo elemento (que no se puede arrastrar esta vez). Usamos
`dragAndDrop()` para mover el elemento arrastrable al segundo elemento.
5. Afirmamos que la posición de ambos elementos es la misma.
Figura 3-10. Página web de práctica con un elemento arrastrable
Hacer Clic y Mantener
El siguiente ejemplo muestra gestos de usuario complejos, incluyendo clic y mantener.
Para ello, practicamos con la página web de la Figura 3-11.
Figura 3-11. Página web de práctica con un lienzo dibujable
Esta página utiliza una biblioteca JavaScript de código abierto llamada Signature Pad
para dibujar firmas en un lienzo HTML usando el ratón. El Ejemplo 3-25 muestra una
prueba utilizando esta biblioteca.
Ejemplo 3-25. Prueba de dibujo de una circunferencia en un lienzo
1. Localizamos el lienzo por nombre de etiqueta.
2. Movemos el ratón a este elemento con `moveToElement()` y luego añadimos la
acción `clickAndHold()` (para dibujar en el lienzo) a la secuencia de acciones.
3. Iteramos usando un número fijo de puntos, utilizando la ecuación para encontrar
los puntos en una circunferencia.
4. Usamos los puntos de la circunferencia (x e y) para mover el ratón por
desplazamiento (`moveByOffset()`). Dado que el clic se mantiene desde el paso
anterior, la acción compuesta resultante moverá el ratón mientras se mantiene
presionado el botón del ratón.
5. Liberamos el clic, construimos la acción y ejecutamos toda la cadena. Como
resultado, debería aparecer una circunferencia en el lienzo.
Copiar y Pegar
Este último ejemplo para gestos de usuario automatiza una acción de usuario común:
copiar y pegar usando el teclado. Aquí, usamos el formulario web disponible en el sitio
de práctica. El Ejemplo 3-26 muestra una prueba que emula copiar y pegar.
Ejemplo 3-26. Prueba emulando copiar y pegar
1. Localizamos dos elementos web: un campo de texto y un área de texto.
2. Usamos una tecla modificadora para enviar la combinación Ctrl + C para copiar
(en Windows y Linux) o Cmd + C para copiar (en macOS). Para ello, utilizamos la
clase `SystemUtils`, disponible en la biblioteca de código abierto Apache
Commons IO (esta dependencia se usa transitivamente en el proyecto
Maven/Gradle).
3. Implementamos la cadena de acciones compuesta por los siguientes pasos:
A. Enviamos la secuencia de caracteres "hello world" al campo de texto.
B. Presionamos la tecla modificadora (Ctrl o Cmd, dependiendo del sistema
operativo). Recuerda que esta tecla permanece presionada hasta que la
liberemos explícitamente.
C. Enviamos la tecla `a` al campo de texto. Dado que el modificador está
activo, la combinación resultante es Ctrl + A (o Cmd + A), y como
resultado, todo el texto presente en el campo de texto se selecciona.
D. Enviamos la tecla `c` al campo de texto. Nuevamente, dado que el
modificador está activo, la combinación es Ctrl + C (o Cmd + C), y el texto
del campo de texto se copia al portapapeles.
E. Enviamos la tecla `v` al área de texto. Esto significa enviar Ctrl + V (o Cmd
+ V), y el contenido del portapapeles se pega en el área de texto.
4. Afirmamos que el contenido de ambos elementos (campo de texto y área de
texto) es el mismo al final del texto.
Estrategias de Espera
Las aplicaciones web son servicios distribuidos cliente-servidor en los que los clientes
son los navegadores web y los servidores web suelen ser hosts remotos. La latencia de
la red intermedia podría afectar la fiabilidad de una prueba de WebDriver. Por ejemplo,
en el caso de redes con alta latencia o servidores sobrecargados, una respuesta lenta
podría afectar negativamente las condiciones esperadas de las pruebas de WebDriver.
Además, las aplicaciones web modernas tienden a ser dinámicas y asincrónicas. Hoy en
día, JavaScript permite ejecutar operaciones no bloqueantes (es decir, asincrónicas)
utilizando diferentes mecanismos, como callbacks, promesas o async/await. Además,
podemos recuperar datos de otros servidores de manera asincrónica, por ejemplo,
utilizando AJAX (JavaScript Asíncrono y XML) o servicios REST (Transferencia de Estado
Representacional).
En general, es de suma importancia tener mecanismos para pausar y esperar ciertas
condiciones en nuestras pruebas de WebDriver. Por esta razón, la API de Selenium
WebDriver proporciona diferentes mecanismos de espera. Las tres principales
estrategias de espera son espera implícita, explícita y fluida. Las siguientes subsecciones
explican y muestran ejemplos.
Para esperar en Java, podrías pensar en incluir comandos `Thread.sleep()` en
tu código. Por un lado, es una solución simple, pero por otro lado, se considera
una mala práctica (es decir, una señal débil) que podría llevar a pruebas poco
fiables (ya que las condiciones de retraso pueden cambiar). Como regla
general, desaconsejo enfáticamente su uso. En su lugar, considera usar las
estrategias de espera mencionadas anteriormente.
Espera Implícita
La primera estrategia de espera proporcionada por Selenium WebDriver se llama espera
implícita. Este mecanismo permite especificar una cantidad de tiempo antes de lanzar
una excepción al buscar un elemento. Por defecto, esta espera tiene un valor de cero
segundos (es decir, no espera en absoluto). Pero cuando definimos un valor de espera
implícita, Selenium WebDriver consulta el DOM durante el valor de espera implícita al
intentar encontrar un elemento. El tiempo de consulta es específico de la
implementación del controlador y frecuentemente es menos de quinientos ms. Si el
elemento está presente en el tiempo transcurrido, el script continúa. De lo contrario,
lanza una excepción.
El Ejemplo 3-27 ilustra esta estrategia. Esta prueba utiliza una página de práctica (ver
Figura 3-12) que carga dinámicamente varias imágenes en el DOM. Dado que estas
imágenes no están disponibles justo antes de que se cargue la página, necesitamos
esperar a que estas imágenes estén disponibles.
Figura 3-12. Práctica de carga de imágenes en la página web
Ejemplo 3-27. Prueba utilizando una espera implícita en la página de "carga de
imágenes"
1. Antes de interactuar con los elementos, especificamos una estrategia de espera
implícita. En este caso, configuramos un tiempo de espera de 10 segundos.
2. En las llamadas siguientes, usamos la API de Selenium WebDriver como de
costumbre.
Puedes probar esta característica eliminando la espera implícita de la prueba
(paso 1). Si haces eso, notarás que la prueba falla en el paso 2 debido a una
`NoSuchElementException`.
Aunque es compatible con la API de Selenium WebDriver, las esperas implícitas tienen
diferentes inconvenientes que debes conocer. Primero, una espera implícita solo
funciona al encontrar elementos. Segundo, no podemos personalizar su
comportamiento ya que su implementación es específica del controlador. Finalmente, y
dado que las esperas implícitas se aplican globalmente, verificar la ausencia de
elementos web generalmente aumenta el tiempo de ejecución de todo el script. Por
estas razones, las esperas implícitas generalmente se consideran una mala práctica en
la mayoría de los casos, y se prefieren las esperas explícitas y fluidas.
Espera Explícita
La segunda estrategia de espera, llamada explícita, permite pausar la ejecución de la
prueba un tiempo máximo hasta que ocurre una condición específica. Para usar esta
estrategia, necesitamos crear una instancia de `WebDriverWait`, utilizando el objeto
WebDriver como el primer argumento del constructor, y una instancia de `Duration`
como el segundo argumento (para especificar el tiempo de espera).
Selenium WebDriver proporciona un conjunto completo de condiciones esperadas
utilizando la clase `ExpectedConditions`. Estas condiciones son muy legibles, y no
requieren más explicaciones para entender su propósito. Te recomiendo que uses una
función de autocompletar en tu IDE favorito para descubrir todas las posibilidades. Por
ejemplo, la Figura 3-13 muestra esta lista en Eclipse.
Figura 3-13. Autocompletado en Eclipse para la clase `ExpectedConditions`
El Ejemplo 3-28 muestra una prueba utilizando una espera explícita. En el ejemplo,
usamos la condición `presenceOfElementLocated` para esperar hasta que una de las
imágenes esté disponible en la página web de práctica.
Ejemplo 3-28. Prueba usando una espera explícita en la página "cargando imágenes"
1. Creamos la instancia de espera. En este caso, el tiempo de espera seleccionado
es de 10 segundos.
2. Esperamos explícitamente una condición dada (en este caso, la presencia de un
elemento específico) invocando el método `until()` en el objeto
`WebDriverWait`. Para lograr una declaración más legible, también puedes
importar estáticamente esta condición esperada (`presenceOfElementLocated`).
En este libro, decidí mantener el nombre de la clase (`ExpectedConditions`) en
estas condiciones para facilitar la función de autocompletar en los IDE, como se
describió anteriormente.
El Ejemplo 3-29 muestra otra prueba utilizando esperas explícitas. Esta prueba utiliza
otra página web de práctica llamada "calculadora lenta", que contiene una interfaz
gráfica de usuario de una calculadora básica, configurada para esperar un tiempo
configurable para obtener el resultado de operaciones aritméticas básicas (por defecto,
cinco segundos). La Figura 3-14 muestra una captura de pantalla de esta página.
Ejemplo 3-29. Prueba utilizando una espera explícita en la página "calculadora lenta"
1. Usamos localizadores XPath para hacer clic en los botones correspondientes a la
operación 1 + 3.
2. Dado que la prueba debe esperar hasta que el resultado esté listo, esperamos
explícitamente para eso. En este caso, la condición es que el texto del elemento
con el nombre de clase `screen` sea igual a 4.
Figura 3-14. Página web de práctica con la demo de "calculadora lenta"
Espera Fluida
La última estrategia es una espera fluida. Este mecanismo es una generalización de las
esperas explícitas. En otras palabras, usamos esperas fluidas para pausar la prueba hasta
que se cumplan ciertas condiciones, pero, además, las esperas fluidas ofrecen
capacidades de configuración detalladas. La Tabla 3-12 resume los métodos disponibles
en `FluentWait`. Como su nombre sugiere, esta clase proporciona una API fluida, y por
lo tanto, podemos encadenar varias invocaciones en la misma línea. El Ejemplo 3-30
muestra una prueba utilizando espera fluida.
Tabla 3-12. Métodos de espera fluida
Ejemplo 3-30. Prueba utilizando una espera fluida
1. Como puedes ver, esta prueba es muy similar al Ejemplo 3-28, aunque al usar
una instancia de Espera Fluida, podemos especificar características adicionales.
En este caso, cambiamos el tiempo de sondeo a un segundo.
La clase `WebDriverWait` (presentada en la subsección anterior) extiende
la clase genérica `FluentWait`. Por lo tanto, también puedes usar todos los
métodos mostrados en la Tabla 3-12 para las esperas explícitas.
Características Relacionadas en Selenium WebDriver
Además de las estrategias de espera introducidas anteriormente, hay otras
características complementarias en Selenium WebDriver que debes tener en cuenta:
Estrategias de carga
Selenium WebDriver permite especificar diferentes enfoques para la carga de
páginas. Esta característica es accesible a través de capacidades específicas del
navegador (por ejemplo, usando `ChromeOptions`, `FirefoxOptions`, etc.). Por
esta razón, explico esta característica en "Estrategias de carga de páginas" en la
página 151.
Tiempos de espera
Selenium WebDriver permite especificar el tiempo máximo transcurrido para la
carga de páginas y scripts. Explico esta característica en "Tiempos de espera" en
la página 110.
Resumen y Perspectivas
Este capítulo presentó los fundamentos de la API de Selenium WebDriver. Primero,
aprendiste a crear y cerrar instancias de WebDriver. Estos objetos representan un
navegador controlado con Selenium WebDriver. De esta manera, usamos una instancia
de `ChromeDriver` para Chrome, `FirefoxDriver` para Firefox, etc. En segundo lugar,
vimos `WebElement`, una clase que representa diferentes elementos de la página web
(por ejemplo, enlaces, imágenes, campos de formulario, etc.). Selenium WebDriver
proporciona varias estrategias para localizar elementos web: por atributo HTML (id,
name o class), nombre de etiqueta, texto del enlace (completo o parcial), selector CSS y
XPath. También vimos una estrategia completamente nueva de Selenium WebDriver 4
llamada localizadores relativos. Luego, cubrimos la suplantación de acciones del usuario,
usando el teclado y el mouse. Puedes usar estas acciones desde acciones simples (por
ejemplo, hacer clic en un enlace, llenar un campo de texto, etc.) hasta gestos de usuario
complejos (por ejemplo, arrastrar y soltar, hacer clic y pasar el cursor, etc.). Finalmente,
examinamos la capacidad de espera en las pruebas de Selenium WebDriver. Esta
característica es crítica debido a la naturaleza distribuida, dinámica y asincrónica de las
aplicaciones web actuales. Hay tres estrategias principales de espera en Selenium
WebDriver: implícita (especificar un tiempo de espera general para esperar elementos),
explícita (pausar la ejecución de la prueba hasta una condición dada) y fluida (extensión
de una espera explícita con alguna configuración detallada).
El próximo capítulo continúa profundizando en la API de Selenium WebDriver. En
particular, el Capítulo 4 revisa aquellas características interoperables en diferentes
navegadores (Chrome, Edge, Firefox, etc.). Entre estas características, descubrirás cómo
ejecutar JavaScript, especificar escuchas de eventos, configurar tiempos de espera para
la carga de páginas y scripts, gestionar el historial del navegador, hacer capturas de
pantalla, manipular cookies, manipular listas desplegables (es decir, selects y data lists),
manejar objetivos de ventanas (es decir, pestañas, marcos y iframes) y cuadros de
diálogo (es decir, alertas, indicaciones, confirmaciones y ventanas emergentes
modales), usar almacenamiento web y comprender las excepciones de WebDriver.
CAPÍTULO 4
Características Independientes del Navegador
Este capítulo revisa aquellas características de Selenium WebDriver que son
interoperables en diferentes navegadores web. En este grupo, una característica
multipropósito relevante es la ejecución de JavaScript. Además, la API de Selenium
WebDriver permite configurar tiempos de espera para la carga de páginas y scripts. Otra
característica conveniente es hacer capturas de pantalla de la pantalla del navegador, o
solo de la porción correspondiente a un elemento dado. Luego, podemos gestionar
diferentes aspectos del navegador controlado usando WebDriver, como el tamaño y la
posición del navegador, el historial o las cookies. A continuación, WebDriver
proporciona varios recursos para controlar elementos web específicos, como listas
desplegables (es decir, campos select HTML y listas de datos), objetivos de navegación
(es decir, ventanas, pestañas, marcos e iframes) o cuadros de diálogo (es decir, alertas,
indicaciones, confirmaciones y diálogos modales). Finalmente, descubrimos cómo
manejar datos locales y de sesión usando almacenamiento web, implementar escuchas
de eventos y utilizar las excepciones proporcionadas por la API de Selenium WebDriver.
Ejecución de JavaScript
JavaScript es un lenguaje de programación de alto nivel compatible con todos los
principales navegadores. Podemos usar JavaScript en el lado del cliente de las
aplicaciones web para una amplia variedad de operaciones, como manipulación del
DOM, interacción con el usuario, manejo de solicitudes y respuestas de servidores
remotos, o trabajo con expresiones regulares, entre muchas otras funciones.
Afortunadamente para la automatización de pruebas, Selenium WebDriver permite
inyectar y ejecutar fragmentos arbitrarios de JavaScript. Para ello, la API de Selenium
WebDriver proporciona la interfaz `JavascriptExecutor`. La Tabla 4-1 presenta los
métodos públicos disponibles en esta interfaz, agrupados en tres categorías: scripts
síncronos, anclados y asíncronos. Las subsecciones siguientes proporcionan más detalles
e ilustran su uso a través de diferentes ejemplos.
Tabla 4-1. Métodos JavascriptExecutor
Cualquier objeto de driver que herede de la clase `RemoteWebDriver` también
implementa la interfaz `JavascriptExecutor`. Por lo tanto, al usar un navegador principal
(por ejemplo, `ChromeDriver`, `FirefoxDriver`, etc.) declarado usando la interfaz
genérica `WebDriver`, podemos convertirlo a `JavascriptExecutor` como se muestra en
el siguiente fragmento. Luego, podemos usar el ejecutor (usando la variable `js` en el
ejemplo) para invocar los métodos presentados en la Tabla 4-1.
Scripts Síncronos
El método `executeScript()` de un objeto `JavascriptExecutor` permite ejecutar un
fragmento de JavaScript en el contexto de la página web actual en una sesión de
WebDriver. La invocación de este método (en Java) bloquea el flujo de control hasta que
el script termina. Por lo tanto, normalmente usamos este método para ejecutar scripts
síncronos en una página web bajo prueba. El método `executeScript()` acepta dos
argumentos:
String script
Fragmento de JavaScript obligatorio que debe ser ejecutado. Este código se
ejecuta en el cuerpo de la página actual como una función anónima (es decir,
una función de JavaScript sin nombre).
Object... args
Argumentos opcionales para el script. Estos argumentos deben ser uno de los
siguientes tipos: número, booleano, cadena, `WebElement`, o una lista de estos
tipos (de lo contrario, WebDriver lanza una excepción). Estos argumentos están
disponibles en el script inyectado utilizando la variable incorporada `arguments`
en JavaScript.
Cuando el script devuelve algún valor (es decir, el código contiene una declaración
`return`), el método `executeScript()` de Selenium WebDriver también devuelve un valor
en Java (de lo contrario, `executeScript()` devuelve `null`). Los tipos posibles de retorno
son:
WebElement
Cuando se devuelve un elemento HTML.
Double
Para decimales.
Long
Para números no decimales.
Boolean
Para valores booleanos.
List<Object>
Para arreglos.
Map<String, Object>
Para colecciones de clave-valor.
String
Para todos los demás casos.
Las situaciones que requieren la ejecución de JavaScript con Selenium WebDriver son
muy variadas. Las siguientes subsecciones revisan dos casos en los que Selenium
WebDriver no proporciona características integradas, y en su lugar, necesitamos usar
JavaScript para automatizarlos: desplazamiento de una página web y manejo de un
selector de color en un formulario web.
Desplazamiento
Como se explicó en el Capítulo 3, Selenium WebDriver permite simular diferentes
acciones del mouse, incluidos clic, clic derecho o doble clic, entre otros. Sin embargo,
desplazarse hacia abajo o hacia arriba en una página web no es posible usando la API de
Selenium WebDriver. En su lugar, podemos lograr esta automatización fácilmente
ejecutando una simple línea de JavaScript. El Ejemplo 4-1 muestra un ejemplo básico
usando una página web de práctica (consulta la URL de esta página en la primera línea
del método de prueba).
Ejemplo 4-1. Prueba ejecutando JavaScript para desplazarse hacia abajo una cantidad
de píxeles
1. Abre una página web de práctica que contenga un texto muy largo (consulta la
Figura 4-1).
2. Convierte el objeto del driver a `JavascriptExecutor`. Usaremos la variable `js`
para ejecutar JavaScript en el navegador.
3. Ejecuta un fragmento de código JavaScript. En este caso, llamamos a la función
JavaScript `scrollBy()` para desplazar el documento una cantidad dada (en este
caso, 1,000 píxeles hacia abajo). Nota que este fragmento no utiliza `return`, y
por lo tanto, no recibimos ningún objeto devuelto en la lógica de Java. Además,
no estamos pasando ningún argumento al script.
Figura 4-1. Página web de práctica con contenido largo
El Ejemplo 4-2 muestra otra prueba usando desplazamiento y la misma página web de
ejemplo que antes. Esta vez, en lugar de mover un número fijo de píxeles, desplazamos
el documento hasta el último párrafo en la página web.
Ejemplo 4-2. Prueba ejecutando JavaScript para desplazarse hacia abajo hasta un
elemento dado
1. Para hacer que esta prueba sea robusta, especificamos un tiempo de espera
implícito. De lo contrario, la prueba podría fallar si la página no está
completamente cargada al ejecutar los comandos siguientes.
2. Ubicamos el último párrafo en la página web usando un selector CSS.
3. Definimos el script que se inyectará en la página. Nota que el script no devuelve
ningún valor, pero como novedad, usa el primer argumento de la función para
invocar la función de JavaScript `scrollIntoView()`.
4. Ejecutamos el script anterior, pasando el `WebElement` localizado como
argumento. Este elemento será el primer argumento para el script (es decir,
`arguments[0]`).
El último ejemplo de desplazamiento es el desplazamiento infinito. Esta técnica permite
la carga dinámica de más contenido cuando el usuario llega al final de la página web.
Automatizar este tipo de página web es un caso de uso instructivo ya que implica
diferentes aspectos de la API de Selenium WebDriver. Por ejemplo, puedes usar un
enfoque similar para rastrear páginas web usando Selenium WebDriver. El Ejemplo 4-3
muestra una prueba usando una página con desplazamiento infinito.
Ejemplo 4-3. Prueba ejecutando JavaScript en una página con desplazamiento infinito
1. Definimos una espera explícita ya que necesitamos pausar la prueba hasta que
el nuevo contenido se cargue.
2. Encontramos el número inicial de párrafos en la página.
3. Ubicamos el último párrafo de la página.
4. Desplazamos hacia abajo hasta este elemento.
5. Esperamos hasta que haya más párrafos disponibles en la página.
Selector de color
Un selector de color en HTML es un tipo de entrada que permite a los usuarios
seleccionar un color haciendo clic y arrastrando el cursor usando un área gráfica. El
formulario web de práctica contiene uno de estos elementos (consulta la Figura 4-2).
Figura 4-2. Selector de color en el formulario web de práctica
El siguiente código muestra el marcado HTML para el selector de color. Nota que
establece un valor de color inicial (de lo contrario, el color predeterminado es negro).
Ejemplo 4-4 ilustra cómo interactuar con este selector de color. Dado que la API de
Selenium WebDriver no proporciona ningún recurso para controlar selectores de color,
usamos JavaScript. Además, esta prueba también ilustra el uso de `Color`, una clase de
soporte disponible en la API de Selenium WebDriver para trabajar con colores.
Ejemplo 4-4. Prueba ejecutando JavaScript para interactuar con un selector de color
1. Localizamos el selector de color por nombre.
2. Leemos el valor inicial del selector de color (debería ser #563d7c).
3. Definimos un color con el que trabajar usando los siguientes componentes RGBA:
rojo=255 (valor máximo), verde=0 (valor mínimo), azul=0 (valor mínimo) y alfa=1
(valor máximo, es decir, completamente opaco).
4. Usamos JavaScript para cambiar el valor seleccionado en el selector de color.
Alternativamente, podemos cambiar el color seleccionado invocando la
instrucción `colorPicker.sendKeys(red.asHex());`.
5. Leemos el valor resultante del selector de color (debería ser #ff0000).
6. Afirmamos que el color es diferente del valor inicial, pero como se esperaba.
Scripts Anclados
La API de Selenium WebDriver permite anclar scripts en Selenium WebDriver 4. Esta
característica permite adjuntar fragmentos de JavaScript a una sesión de WebDriver,
asignando una clave única a cada fragmento y ejecutando estos fragmentos a demanda
(incluso en diferentes páginas web).
Ejemplo 4-5. Prueba ejecutando JavaScript como scripts anclados
1. Adjuntamos un fragmento de JavaScript para localizar un elemento en la página
web. Cabe señalar que podríamos hacer lo mismo con la API estándar de
WebDriver. No obstante, utilizamos este enfoque con fines demostrativos.
2. Adjuntamos otro fragmento de JavaScript que devuelve cualquier cosa que le
pasemos como primer parámetro.
3. Leemos el conjunto de scripts anclados.
4. Afirmamos que el número de scripts anclados es el esperado (es decir, 2).
5. Ejecutamos el primer script anclado. Como resultado, obtenemos el tercer
enlace en la página web como un WebElement en Java.
6. Hacemos clic en este enlace, que debería corresponder al enlace de la página de
práctica. Como resultado, el navegador debería navegar a esa página.
7. Afirmamos que la URL actual es diferente de la inicial.
8. Ejecutamos el segundo script anclado. Cabe señalar que es posible ejecutar el
script anclado incluso si la página ha cambiado en el navegador (ya que el script
está adjunto a la sesión y no a una sola página).
9. Afirmamos que el mensaje devuelto es el esperado.
10. Desanclamos uno de los scripts.
11. Verificamos que el número de scripts anclados es el esperado (es decir, 1 en este
momento).
Scripts Asíncronos
El método `executeAsyncScript()` de la interfaz `JavascriptExecutor` permite ejecutar
scripts de JavaScript en el contexto de una página web utilizando Selenium WebDriver.
De manera similar a `executeScript()` explicado anteriormente, `executeAsyncScript()`
ejecuta una función anónima con el código JavaScript proporcionado en el cuerpo de la
página actual. La ejecución de esta función bloquea el flujo de control de Selenium
WebDriver. La diferencia es que en `executeAsyncScript()`, debemos señalar
explícitamente la terminación del script invocando una función de retorno (callback)
llamada `done`. Este callback se inyecta en el script ejecutado como el último argumento
(es decir, `arguments[arguments.length - 1]`) en la función anónima correspondiente.
Ejemplo 4-6. Prueba ejecutando JavaScript asíncrono
1. Definimos un tiempo de pausa de 2 segundos.
2. Definimos el script que se ejecutará. En la primera línea, definimos una constante
para el callback (es decir, el último argumento del script). Luego, usamos la
función JavaScript `window.setTimeout()` para pausar la ejecución del script
durante un tiempo determinado.
3. Obtenemos el tiempo del sistema actual (en milisegundos).
4. Ejecutamos el script. Si todo funciona como se espera, la ejecución del test se
bloquea en esta línea durante el tiempo definido en el paso 1.
5. Calculamos el tiempo necesario para ejecutar la línea anterior.
6. Afirmamos que el tiempo transcurrido es el esperado (típicamente, algunos
milisegundos más que el tiempo de pausa definido).
Puedes encontrar un ejemplo adicional que ejecuta un script asíncrono en
“Notificaciones” en la página 162.
Timeouts
Selenium WebDriver permite especificar tres tipos de timeouts. Podemos usarlos
invocando el método `manage().timeouts()` en la API de Selenium WebDriver. El primer
timeout es la espera implícita, ya explicada en “Espera Implícita” en la página 94 (como
parte de las estrategias de espera). Las otras opciones son los timeouts de carga de
página y de carga de script, explicados a continuación.
Timeout de Carga de Página
El timeout de carga de página proporciona un límite de tiempo para interrumpir un
intento de navegación. En otras palabras, este timeout limita el tiempo en el que se
carga una página web. Cuando se excede este timeout (que tiene un valor
predeterminado de 30 segundos), se lanza una excepción.
Ejemplo 4-7. Prueba utilizando un timeout de carga de página
1. Especificamos el timeout de carga de página más bajo posible, que es de un
milisegundo.
2. Cargamos una página web. Esta invocación (implementada como una lambda en
Java) fallará ya que es imposible cargar esa página web en menos de un
milisegundo. Por esta razón, se espera que se lance la excepción
`TimeoutException` en la lambda, utilizando el método `assertThatThrownBy` de
AssertJ.
Puedes experimentar con esta prueba eliminando la declaración del timeout
(es decir, el paso 1). Si haces eso, la prueba fallará ya que se espera una
excepción, pero no se lanzará.
Timeout de Carga de Script
El timeout de carga de script proporciona un límite de tiempo para interrumpir un script
que está siendo evaluado. Este timeout tiene un valor predeterminado de trescientos
segundos. El Ejemplo 4-8 muestra una prueba utilizando un timeout de carga de script.
Ejemplo 4-8. Prueba usando un timeout de carga de script
1. Definimos un timeout de script de tres segundos. Esto significa que un script que
dure más de ese tiempo lanzará una excepción.
2. Ejecutamos un script asincrónico que pausa la ejecución durante cinco segundos.
3. El tiempo de ejecución del script es mayor que el timeout de script configurado,
resultando en una `ScriptTimeoutException`. Nuevamente, este ejemplo es una
prueba negativa, es decir, está diseñado para esperar esta excepción.
Capturas de pantalla
Selenium WebDriver se utiliza principalmente para realizar pruebas funcionales de
extremo a extremo de aplicaciones web. En otras palabras, lo usamos para verificar que
las aplicaciones web se comporten como se espera al interactuar con su interfaz de
usuario (es decir, utilizando un navegador web). Este enfoque es muy conveniente para
automatizar escenarios de usuario de alto nivel, pero también presenta diferentes
dificultades. Uno de los principales desafíos en las pruebas de extremo a extremo es
diagnosticar la causa subyacente de una prueba fallida. Suponiendo que la falla sea
legítima (es decir, no inducida por una prueba mal implementada), la causa raíz podría
ser diversa: el lado del cliente (por ejemplo, lógica de JavaScript incorrecta), el lado del
servidor (por ejemplo, excepción interna) o la integración con otros componentes (por
ejemplo, acceso inadecuado a la base de datos), entre otras razones. Uno de los
mecanismos más generalizados utilizados en Selenium WebDriver para el análisis de
fallos es realizar capturas de pantalla del navegador. Esta sección presenta los
mecanismos proporcionados por la API de Selenium WebDriver.
"Análisis de fallos" en la página 265 revisa las técnicas específicas del
framework para determinar cuándo una prueba ha fallado y llevar a cabo
diferentes técnicas de análisis de fallos, como capturas de pantalla,
grabaciones y recopilación de registros.
Selenium WebDriver proporciona la interfaz `TakesScreenshot` para hacer capturas de
pantalla del navegador. Cualquier objeto de driver que herede de `RemoteWebDriver`
(ver Figura 2-2) también implementa esta interfaz. Por lo tanto, podemos convertir un
objeto `WebDriver` que instancia uno de los principales navegadores (por ejemplo,
`ChromeDriver`, `FirefoxDriver`, etc.) de la siguiente manera:
La interfaz `TakesScreenshot` solo proporciona un método llamado
`getScreenshotAs(OutputType<X> target)` para hacer capturas de pantalla. El parámetro
`OutputType<X> target` determina el tipo de captura de pantalla y el valor retornado.
La Tabla 4-2 muestra las alternativas disponibles para este parámetro.
Tabla 4-2. Parámetros de OutputType
El método `getScreenshotAs()` permite tomar capturas de pantalla de la
ventana del navegador. Además, Selenium WebDriver 4 permite crear
capturas de pantalla de toda la página utilizando diferentes mecanismos (ver
"Captura de pantalla de página completa" en la página 183).
El Ejemplo 4-9 muestra una prueba para tomar una captura de pantalla del navegador
en formato PNG.
El Ejemplo 4-10 muestra otra prueba para crear una captura de pantalla como una
cadena Base64. La captura de pantalla resultante se muestra en la Figura 4-3.
Ejemplo 4-9. Prueba para tomar una captura de pantalla como un archivo PNG
1. Hacemos que la pantalla del navegador sea un archivo PNG.
2. Este archivo se encuentra en una carpeta temporal por defecto, por lo que lo
movemos a un nuevo archivo llamado `screenshot.png` (en la carpeta raíz del
proyecto).
3. Utilizamos Java estándar para mover el archivo de la captura de pantalla a la
nueva ubicación.
4. Usamos aserciones para verificar que el archivo de destino exista.
Figura 4-3. Captura de pantalla del navegador de la página de inicio del sitio de práctica
Ejemplo 4-10. Prueba de captura de pantalla en formato Base64
1. Hacemos una captura de pantalla del navegador en formato Base64.
2. Agregamos el prefijo `data:image/png;base64,` a la cadena Base64 y la
registramos en la salida estándar. Puedes copiar y pegar esta cadena resultante
en la barra de navegación de un navegador para mostrar la imagen.
3. Aseguramos que la cadena de la captura de pantalla tenga contenido.
Registrar la captura de pantalla en Base64, como se presentó en el ejemplo
anterior, puede ser muy útil para diagnosticar fallos al ejecutar pruebas en
servidores CI en los que no tenemos acceso al sistema de archivos (por
ejemplo, GitHub Actions).
Capturas de pantalla de WebElement
La interfaz `WebElement` extiende la interfaz `TakesScreenshot`. De esta manera, es
posible hacer capturas de pantalla parciales del contenido visible de un elemento web
dado (ver Ejemplo 4-11). Ten en cuenta que esta prueba es muy similar a la anterior que
usa archivos PNG, pero en este caso, invocamos el método `getScreenshotAs()`
directamente utilizando un elemento web. La Figura 4-4 muestra la captura de pantalla
resultante.
Ejemplo 4-11. Prueba de captura de pantalla parcial como archivo PNG
Figura 4-4. Captura parcial de pantalla del formulario web de práctica
Tamaño y posición de la ventana
La API de Selenium WebDriver permite manipular el tamaño y la posición del navegador
de manera muy sencilla utilizando la interfaz Window. Este tipo es accesible desde un
objeto driver utilizando la siguiente declaración. La Tabla 4-3 muestra los métodos
disponibles en esta interfaz. Luego, el Ejemplo 4-12 muestra una prueba básica sobre
esta función.
Tabla 4-3. Métodos de la ventana
Ejemplo 4-12. Prueba de lectura y cambio del tamaño y la posición del navegador
1. Leemos la posición de la ventana.
2. Leemos el tamaño de la ventana.
3. Maximizamos la ventana del navegador.
4. Verificamos que la posición maximizada (y el tamaño, en la línea siguiente) es
diferente de la ventana original.
Historial del navegador
Selenium WebDriver permite manipular el historial del navegador a través de la interfaz
Navigation. La siguiente declaración ilustra cómo acceder a esta interfaz desde un objeto
WebDriver. El uso de esta interfaz es bastante sencillo. La Tabla 4-4 muestra sus
métodos públicos, y el Ejemplo 4-13 muestra un ejemplo básico. Nota que esta prueba
navega a diferentes páginas web utilizando estos métodos y, al final de la prueba,
verifica que la URL de la página web sea la esperada.
Tabla 4-4. Métodos de navegación
Ejemplo 4-13. Prueba utilizando métodos de navegación
El Shadow DOM
Como se mencionó en "El Modelo de Objeto de Documento (DOM)" en la página 59, el
DOM es una interfaz de programación que nos permite representar y manipular una
página web utilizando una estructura en forma de árbol. El Shadow DOM es una
característica de esta interfaz de programación que permite la creación de subárboles
delimitados dentro del árbol DOM regular. El Shadow DOM permite la encapsulación de
un grupo de subárboles del DOM (llamado árbol sombra, como se representa en la
Figura 4-5) que puede especificar diferentes estilos CSS del DOM original. El nodo en el
DOM regular al cual se adjunta el árbol sombra se llama host sombra. El nodo raíz del
árbol sombra se llama raíz sombra. Como se representa en la Figura 4-5, el árbol sombra
se aplana en el DOM original en un solo árbol compuesto para ser renderizado en el
navegador.
Figura 4-5. Representación esquemática del Shadow DOM
El Shadow DOM es parte del conjunto de estándares (junto con las plantillas
HTML o los elementos personalizados) que permite la implementación de
componentes web (es decir, elementos personalizados reutilizables para
aplicaciones web).
El Shadow DOM permite la creación de componentes autónomos. En otras palabras, el
árbol sombra está aislado del DOM original. Esta característica es útil para el diseño y la
composición web, pero puede ser un desafío para las pruebas automatizadas con
Selenium WebDriver (ya que las estrategias de localización regulares no pueden
encontrar elementos web dentro del árbol sombra). Afortunadamente, Selenium
WebDriver 4 proporciona un método WebElement que permite el acceso al Shadow
DOM. El Ejemplo 4-14 demuestra este uso.
Ejemplo 4-14. Prueba de lectura del Shadow DOM
Abrimos la página web de práctica que contiene un árbol sombra. Puedes inspeccionar
el código fuente de esta página para verificar el método de JavaScript utilizado para
crear un árbol sombra.
Ubicamos el elemento host sombra.
Obtenemos la raíz sombra del elemento host. Como resultado, obtenemos una instancia
de SearchContext, una interfaz implementada por WebDriver y WebElement, que nos
permite encontrar elementos usando los métodos `findElement()` y `findElements()`.
Encontramos el primer elemento de párrafo en el árbol sombra.
Verificamos que el contenido de texto del elemento sombra sea el esperado.
Esta característica de la especificación W3C WebDriver es reciente al
momento de escribir esto, y por lo tanto podría no estar implementada en
todos los drivers (por ejemplo, chromedriver, geckodriver). Por ejemplo, está
disponible a partir de la versión 96 de Chrome y Edge.
Cookies
HTTP 1.x es un protocolo sin estado, lo que significa que el servidor no rastrea el estado
del usuario. En otras palabras, los servidores web no recuerdan a los usuarios entre
diferentes solicitudes. El mecanismo de cookies es una extensión de HTTP que permite
rastrear a los usuarios enviando pequeños fragmentos de texto llamados cookies desde
el servidor al cliente. Estas cookies deben ser enviadas de vuelta por los clientes, y de
esta manera, los servidores recuerdan a sus clientes. Las cookies te permiten mantener
sesiones web o personalizar la experiencia del usuario en el sitio web, entre otras
funciones.
Los navegadores web permiten gestionar las cookies del navegador manualmente.
Selenium WebDriver habilita una manipulación equivalente, pero de manera
programática. La API de Selenium WebDriver proporciona los métodos que se muestran
en la Tabla 4-5 para lograr esto. Estos son accesibles a través de la función `manage()`
de un objeto WebDriver.
Como muestra esta tabla, la clase `Cookie` proporciona una abstracción para una sola
cookie en Java. La Tabla 4-6 resume los métodos disponibles en esta clase. Además, esta
clase tiene varios constructores que aceptan posicionalmente los siguientes
parámetros:
String name
Nombre de la cookie (obligatorio)
String value
Valor de la cookie (obligatorio)
String domain
Dominio en el que la cookie es visible (opcional)
String path
Ruta en la que la cookie es visible (opcional)
Date expiry
Fecha de expiración de la cookie (opcional)
boolean isSecure
Si la cookie requiere una conexión segura (opcional)
boolean isHttpOnly
Si esta cookie es una cookie HTTP-only, es decir, la cookie no es accesible a
través de un script del lado del cliente (opcional)
String sameSite
Si esta cookie es una cookie same-site, es decir, la cookie está restringida a un
contexto de primera parte o same-site (opcional)
Tabla 4-6. Métodos de Cookie
Los siguientes ejemplos muestran diferentes pruebas para gestionar cookies web con la
API de Selenium WebDriver. Estos ejemplos utilizan una página web de práctica que
muestra las cookies del sitio en la interfaz gráfica (ver Figura 4-6):
- Ejemplo 4-15 ilustra cómo leer las cookies existentes de un sitio web.
- Ejemplo 4-16 muestra cómo agregar nuevas cookies.
- Ejemplo 4-17 explica cómo editar cookies existentes.
- Ejemplo 4-18 demuestra cómo eliminar cookies.
Figura 4-6. Página web de práctica para cookies web
Ejemplo 4-15. Prueba de lectura de cookies existentes
1. Obtenemos el objeto `Options` utilizado para gestionar cookies.
2. Leemos todas las cookies disponibles en esta página. Debería contener dos
cookies.
3. Leemos la cookie con el nombre `username`.
4. El valor de la cookie anterior debería ser `John Doe`.
5. La última afirmación no afecta la prueba. Invocamos este comando para verificar
las cookies en la interfaz gráfica del navegador.
Ejemplo 4-16. Prueba de adición de nuevas cookies
1. Creamos una nueva cookie.
2. Agregamos la cookie a la página actual.
3. Leemos el valor de la cookie recién agregada.
4. Verificamos que este valor sea el esperado.
Ejemplo 4-17. Prueba de edición de cookies existentes
1. Leemos una cookie existente.
2. Creamos una nueva cookie reutilizando el nombre de la cookie anterior.
3. Agregamos la nueva cookie a la página web.
4. Leemos la cookie recién agregada.
5. Verificamos que la cookie ha sido editada correctamente.
Ejemplo 4-18. Prueba de eliminación de cookies existentes
1. Leemos todas las cookies.
2. Leemos la cookie con el nombre `username`.
3. Eliminamos la cookie anterior.
4. Verificamos que el tamaño de las cookies sea el esperado.
Listas desplegables
Un elemento típico en los formularios web son las listas desplegables. Estos campos
permiten a los usuarios seleccionar uno o más elementos dentro de una lista de
opciones. Las etiquetas HTML clásicas utilizadas para renderizar estos campos son
`<select>` y `<option>`. Como de costumbre, el formulario web de práctica contiene uno
de estos elementos (ver Figura 4-7), definido en HTML de la siguiente manera:
Figura 4-7. Campo de selección en el formulario web de práctica
Estos elementos están muy extendidos en los formularios web. Por esta razón, Selenium
WebDriver proporciona una clase auxiliar llamada `Select` para simplificar su
manipulación. Esta clase envuelve un elemento WebElement de selección y proporciona
una amplia variedad de funciones. La Tabla 4-7 resume los métodos públicos disponibles
en la clase `Select`. Después de eso, el Ejemplo 4-19 muestra una prueba básica
utilizando esta clase.
Tabla 4-7. Métodos de Select
Ejemplo 4-19. Prueba de interacción con un campo de selección
Encontramos el elemento `select` por nombre y usamos el WebElement resultante para
instanciar un objeto `Select`.
Seleccionamos una de las opciones disponibles en este select, usando una estrategia de
selección por texto.
Verificamos que el texto de la opción seleccionada sea el esperado.
Elementos de lista de datos
Otra forma de implementar listas desplegables en HTML es utilizando listas de datos.
Aunque las listas de datos son muy similares a los elementos `select` desde un punto de
vista gráfico, existe una clara distinción entre ellas. Por un lado, los campos `select`
muestran una lista de opciones, y los usuarios eligen una (o varias) de las opciones
disponibles. Por otro lado, las listas de datos muestran una lista de opciones sugeridas
asociadas con un campo de formulario de entrada (texto), y los usuarios tienen la
libertad de seleccionar uno de esos valores sugeridos o escribir un valor personalizado.
El formulario web de práctica contiene una de estas listas de datos. Puedes encontrar
su marcado en el siguiente fragmento y una captura de pantalla en la Figura 4-8.
Figura 4-8. Campo de lista de datos en el formulario web de práctica
Selenium WebDriver no proporciona una clase auxiliar personalizada para manipular
listas de datos. En su lugar, necesitamos interactuar con ellas como textos de entrada
estándar, con la distinción de que sus opciones se muestran al hacer clic en el campo de
entrada. El Ejemplo 4-20 muestra una prueba que ilustra esto.
Ejemplo 4-20. Prueba de interacción con un campo de lista de datos
1. Localizamos el campo de entrada utilizado para la lista de datos.
2. Hacemos clic en él para mostrar sus opciones.
3. Encontramos la segunda opción.
4. Leemos el valor de la opción localizada.
5. Escribimos ese valor en el campo de entrada.
6. Aseguramos que el valor de la opción sea el esperado.
Objetivos de navegación
Al navegar por páginas web usando un navegador, por defecto usamos una sola página
correspondiente a la URL en la barra de navegación. Luego, podemos abrir otra página
en una nueva pestaña del navegador. Esta segunda pestaña puede abrirse
explícitamente cuando un enlace define el atributo `target`, o el usuario puede forzar la
navegación a una nueva pestaña, típicamente usando la tecla modificadora Ctrl (o Cmd
en macOS) junto con el clic del mouse en un enlace web.
Otra posibilidad es abrir páginas web en nuevas ventanas. Para esto, las páginas web
típicamente usan el comando JavaScript `window.open(url)`. Otra forma de mostrar
diferentes páginas al mismo tiempo es utilizando `frames` e `iframes`. Un `frame` es un
tipo de elemento HTML que define un área particular (dentro de un conjunto llamado
`frameset`) donde se puede mostrar una página web. Un `iframe` es otro elemento
HTML que permite incrustar una página HTML en la actual.
El uso de `frames` no es recomendado ya que estos elementos tienen muchos
inconvenientes, como problemas de rendimiento y accesibilidad. Explico
cómo usarlos a través de Selenium WebDriver por razones de compatibilidad.
No obstante, recomiendo encarecidamente evitar los `frames` en
aplicaciones web nuevas.
La API de Selenium WebDriver proporciona la interfaz `TargetLocator` para tratar con
los objetivos mencionados anteriormente (es decir, pestañas, ventanas, `frames` e
`iframes`). Esta interfaz permite cambiar el enfoque de los comandos futuros de un
objeto WebDriver (a una nueva pestaña, ventana, etc.). Esta interfaz es accesible
invocando el método `switchTo()` en un objeto WebDriver. La Tabla 4-8 describe sus
métodos públicos.
Tabla 4-8. Métodos de TargetLocator
Pestañas y Ventanas
El Ejemplo 4-21 muestra una prueba donde abrimos una nueva pestaña para navegar a
una segunda página web. El Ejemplo 4-22 muestra un caso equivalente pero para abrir
una nueva ventana para la segunda página web. Nota que la diferencia entre estos
ejemplos es solo el parámetro `WindowType.TAB` y `WindowType.WINDOW`.
Ejemplo 4-21. Prueba de apertura de una nueva pestaña
1. Navegamos a una página web.
2. Obtenemos el identificador de la ventana actual.
3. Abrimos una nueva pestaña y cambiamos el enfoque a ella.
4. Abrimos otra página web (dado que el enfoque está en la segunda pestaña, la
página se abre en la segunda pestaña).
5. Verificamos que el número de identificadores de ventana en este momento es
2.
6. Cambiamos el enfoque a la ventana inicial (usando su identificador).
7. Cerramos solo la ventana actual. La segunda pestaña permanece abierta.
8. Verificamos que el número de identificadores de ventana ahora es 1.
Ejemplo 4-22. Prueba de apertura de una nueva ventana
1. Esta línea es diferente en los ejemplos. En este caso, abrimos una nueva ventana
(en lugar de una pestaña) y nos enfocamos en ella.
Frames e Iframes
El Ejemplo 4-23 muestra una prueba en la que la página web bajo prueba contiene un
`iframe`.
El Ejemplo 4-24 muestra el caso equivalente pero utilizando un `frameset`.
Ejemplo 4-23. Prueba de manejo de iframes
2. Abrimos una página web que contiene un `iframe` (ver Figura 4-9).
3. Utilizamos una espera explícita para esperar el `frame` y cambiar a él.
4. Utilizamos otra espera explícita para pausar hasta que los párrafos contenidos
en el `iframe` estén disponibles.
5. Aseguramos que el número de párrafos sea el esperado.
Figura 4-9. Página web de práctica utilizando un iframe
Ejemplo 4-24. Prueba de manejo de frames
1. Abrimos una página web que contiene un `frameset` (ver Figura 4-10).
2. Esperamos a que el `frame` esté disponible. Nota que los pasos 2 y 3 en el
Ejemplo 4-23 son equivalentes a este paso.
3. Cambiamos el enfoque a este `frame`.
Figura 4-10. Página web de práctica utilizando frames
Cuadros de diálogo
JavaScript proporciona diferentes cuadros de diálogo (a veces llamados pop-ups) para
interactuar con el usuario, a saber:
Alerta (Alert)
Para mostrar un mensaje y esperar a que el usuario presione el botón OK (la única opción
en el diálogo). Por ejemplo, el siguiente código abrirá un diálogo que muestra “¡Hola,
mundo!” y espera a que el usuario presione el botón OK.
Confirmación (Confirm)
Para mostrar un cuadro de diálogo con una pregunta y dos botones: OK y Cancelar. Por
ejemplo, el siguiente código abrirá un diálogo mostrando el mensaje “¿Es esto
correcto?” y pedirá al usuario que haga clic en OK o Cancelar.
Aviso (Prompt)
Para mostrar un cuadro de diálogo con un mensaje de texto, un campo de entrada de
texto y los botones OK y Cancelar. Por ejemplo, el siguiente código muestra un pop-up
que indica “Por favor, ingresa tu nombre,” un cuadro de diálogo en el que el usuario
puede escribir, y dos botones (OK y Cancelar).
Además, CSS permite implementar otro tipo de cuadro de diálogo llamado ventana
modal. Este diálogo desactiva la ventana principal (pero la mantiene visible) mientras
superpone un pop-up hijo, que generalmente muestra un mensaje y algunos botones.
Puedes encontrar una página de muestra en la página web de práctica que contiene
todos estos cuadros de diálogo (alerta, confirmación, aviso y modal). La Figura 4-11
muestra una captura de pantalla de esta página cuando el diálogo modal está activo.
Figura 4-11. Página web de práctica con cuadros de diálogo (alerta, confirmación, aviso
y modal)
Alertas, Confirmaciones y Avisos
La API de Selenium WebDriver proporciona la interfaz `Alert` para manipular diálogos
de JavaScript (es decir, alertas, confirmaciones y avisos). La Tabla 4-9 describe los
métodos proporcionados por esta interfaz. Luego, el Ejemplo 4-25 muestra una prueba
básica interactuando con una alerta.
Ejemplo 4-25. Prueba de manejo de un cuadro de diálogo de alerta
Ejemplo 4-25. Prueba de manejo de un cuadro de diálogo de alerta
1. Abrimos la página web de práctica que lanza cuadros de diálogo.
2. Hacemos clic en el botón de la izquierda para lanzar una alerta de JavaScript.
3. Esperamos hasta que el cuadro de diálogo de alerta se muestre en la pantalla.
4. Cambiamos el enfoque al pop-up de alerta.
5. Verificamos que el texto de la alerta sea el esperado.
6. Hacemos clic en el botón OK del cuadro de diálogo de alerta.
Podemos reemplazar los pasos 3 y 4 con una sola declaración de espera explícita, como
sigue (puedes encontrarla en una segunda prueba en la misma clase en el repositorio de
ejemplos):
El siguiente test (Ejemplo 4-26) ilustra cómo manejar un cuadro de diálogo de
confirmación. Ten en cuenta que este ejemplo es bastante similar al anterior, pero en
este caso, podemos invocar el método `dismiss()` para hacer clic en el botón Cancelar
disponible en el cuadro de diálogo de confirmación.
Finalmente, el Ejemplo 4-27 muestra cómo gestionar un cuadro de diálogo de aviso. En
este caso, podemos escribir una cadena de texto en el campo de entrada.
Ejemplo 4-26. Prueba de manejo de un cuadro de diálogo de confirmación
Ejemplo 4-27. Prueba de manejo de un cuadro de diálogo de aviso
Ventanas Modales
Las ventanas modales son cuadros de diálogo construidos con CSS y HTML básicos. Por
esta razón, Selenium WebDriver no proporciona ninguna utilidad específica para
manipularlas. En su lugar, utilizamos la API estándar de WebDriver (localizadores,
esperas, etc.) para interactuar con las ventanas modales. El Ejemplo 4-28 muestra una
prueba básica utilizando la página web de práctica que contiene cuadros de diálogo.
Ejemplo 4-28. Prueba de manejo de un cuadro de diálogo modal
Almacenamiento Web
La API de Almacenamiento Web permite a las aplicaciones web almacenar datos
localmente en el sistema de archivos del cliente. Esta API proporciona dos objetos de
JavaScript:
window.localStorage
Para almacenar datos de forma permanente.
window.sessionStorage
Para almacenar datos durante el tiempo de la sesión (los datos se eliminan
cuando se cierra la pestaña del navegador).
Selenium WebDriver proporciona la interfaz `WebStorage` para manipular la API de
Almacenamiento Web. La mayoría de los tipos de WebDriver compatibles con Selenium
WebDriver heredan esta interfaz: ChromeDriver, EdgeDriver, FirefoxDriver, OperaDriver
y SafariDriver. De esta manera, podemos utilizar esta característica en estos
navegadores. El Ejemplo 4-29 demuestra este uso en Chrome. Esta prueba utiliza ambos
tipos de almacenamiento web (local y de sesión).
Ejemplo 4-29. Prueba utilizando almacenamiento web
1. Convertimos el objeto `driver` a `WebStorage`.
2. Registramos el número de elementos en el almacenamiento local.
3. Registramos el almacenamiento de sesión (debería contener dos elementos).
4. Después de agregar un nuevo elemento, debería haber tres elementos en el
almacenamiento de sesión.
Escuchadores de Eventos
La API de Selenium WebDriver permite crear escuchadores que notifican eventos que
ocurren en WebDriver y objetos derivados. En versiones anteriores de Selenium
WebDriver, esta función estaba accesible a través de la clase `EventFiringWebDriver`.
Esta clase está obsoleta a partir de Selenium WebDriver 4, y en su lugar, deberíamos
usar lo siguiente:
EventFiringDecorator
Clase envolvente para WebDriver y objetos derivados (por ejemplo,
WebElement, TargetLocator, etc.). Permite registrar uno o más escuchadores (es
decir, instancias de `WebDriverListener`).
WebDriverListener
Interfaz que deben implementar los escuchadores registrados en el decorador.
Admite tres tipos de eventos:
Eventos antes
Lógica insertada justo antes de que comience algún evento.
Eventos después
Lógica insertada justo después de que termine algún evento.
Eventos de error
Lógica insertada antes de que se lance una excepción.
Para implementar un escuchador de eventos, primero debemos crear una clase de
escuchador. En otras palabras, necesitamos crear una clase que implemente
`WebDriverListener`. Esta interfaz define todos sus métodos usando la palabra clave
`default`, por lo tanto, es opcional sobrescribir sus métodos. Gracias a esa característica
(disponible a partir de Java 8), nuestra clase solo debería implementar el método que
necesitamos. Hay muchos métodos de escuchador disponibles, por ejemplo, `afterGet()`
(ejecutado después de llamar al método `get()` en una instancia de WebDriver) o
`beforeQuit()` (ejecutado antes de llamar al método `quit()` en una instancia de
WebDriver), por mencionar algunos. Mi recomendación para revisar todos estos
escuchadores es utilizar tu IDE favorito para descubrir los posibles métodos a
sobrescribir/implementar. La Figura 4-12 muestra el asistente para hacer esto en
Eclipse.
Figura 4-12. Métodos de `WebDriverListener` en Eclipse
Una vez que hemos implementado nuestro listener, necesitamos crear la clase
decoradora. Hay dos formas de hacerlo. Si queremos decorar un objeto `WebDriver`,
podemos crear una instancia de `EventFiringDecorator` (pasando el listener como
argumento al constructor) y luego invocar el método `decorate()` para pasar el objeto
`WebDriver`. Por ejemplo:
La segunda forma es decorar otros objetos de la API de Selenium WebDriver, como
`WebElement`, `TargetLocator`, `Navigation`, `Options`, `Timeouts`, `Window`, `Alert` o
`VirtualAuthenticator`. En este caso, necesitamos invocar el método `createDecorated()`
en un objeto `EventFiringDecorator` para obtener una clase genérica `Decorated<T>`. El
siguiente fragmento muestra un ejemplo usando un `WebElement` como parámetro:
Veamos un ejemplo completo. Primero, el Ejemplo 4-30 muestra la clase que
implementa la interfaz `WebDriverListener`. Observa que esta clase implementa dos
métodos: `afterGet()` y `beforeQuit()`. Ambos métodos llaman a `takeScreenshot()` para
capturar una captura de pantalla del navegador. En resumen, estamos recopilando
capturas de pantalla del navegador justo después de cargar una página web
(típicamente al inicio de la prueba) y antes de salir (típicamente al final de la prueba).
Luego, el Ejemplo 4-31 muestra la prueba que utiliza este listener.
Ejemplo 4-30. Listener de eventos que implementa los métodos `afterGet()` y
`beforeQuit()`
1. Sobrescribimos este método para ejecutar lógica personalizada después de
cargar páginas web con el objeto WebDriver.
2. Sobrescribimos este método para ejecutar lógica personalizada antes de salir del
objeto WebDriver.
3. Utilizamos un nombre único para las capturas de pantalla en formato PNG. Para
ello, obtenemos la fecha del sistema (fecha y hora) junto con el identificador de
sesión.
Ejemplo 4-31. Prueba utilizando `EventFiringDecorator` y el listener anterior
1. Creamos un objeto WebDriver decorado utilizando una instancia de
MyEventListener. Utilizamos el driver resultante para controlar el navegador en
la lógica de `@Test`.
2. Hacemos clic en un enlace web para cambiar de página. Las dos capturas de
pantalla resultantes tomadas en el listener deberían ser diferentes.
Excepciones de WebDriver
Todas las excepciones proporcionadas por la API de WebDriver heredan de la clase
`WebDriverException` y son excepciones no comprobadas (consulta el recuadro
siguiente si no estás familiarizado con esta terminología). La Figura 4-13 muestra estas
excepciones en Selenium WebDriver 4. Como se muestra en esta imagen, existen
muchos tipos de excepciones diferentes. La Tabla 4-10 resume algunas de las causas
más comunes.
Figura 4-13. Excepciones de Selenium WebDriver
Excepciones en Java
En Java, una excepción es un evento que interrumpe la ejecución de un programa. Se
utiliza una jerarquía de clases para modelar los diferentes tipos de excepciones en la
API estándar de Java. La raíz de esta jerarquía es la clase `Throwable`. Tiene dos
subtipos:
Error
Problemas irrecuperables. Como regla general, las aplicaciones se bloquean en
lugar de manejar estos errores graves. Algunos ejemplos son
`OutOfMemoryError` o `StackOverflowError`.
Exception
Problemas recuperables. Las aplicaciones pueden manejar estas excepciones
utilizando bloques `try-catch`. Hay dos tipos de clases de `Exception`:
Excepciones comprobadas
Clases que heredan directamente de la clase `Throwable` (excepto
`RuntimeException` y `Error`). Estas excepciones se validan en tiempo de
compilación, por lo que debemos manejarlas con bloques `try-catch` o volver a
lanzar usando `throws`. Ejemplos de excepciones comprobadas son
`IOException` o `MalformedURLException`.
Excepciones no comprobadas
Clases que heredan de la clase `RuntimeException`, que es un subtipo de
`Exception`. Estas excepciones no requieren ser manejadas con `try-catch` o
volver a lanzar con `throws`. Algunos ejemplos de excepciones no comprobadas
son `NullPointerException` o `ArrayIndexOutOfBoundsException`.
Tabla 4-10. Excepciones comunes de WebDriver y causas habituales
Resumen y Perspectivas
Este capítulo proporcionó una revisión completa de las características de la API de
WebDriver interoperables en diferentes navegadores web. Entre ellas, descubriste
cómo ejecutar JavaScript con Selenium WebDriver, con scripts sincrónicos, fijos (es
decir, adjuntos a una sesión de WebDriver) y asincrónicos. Luego, aprendiste sobre los
tiempos de espera, utilizados para especificar un intervalo de límite de tiempo para la
carga de páginas y la ejecución de scripts. También viste cómo gestionar varios aspectos
del navegador, como el tamaño y la posición, el historial de navegación, el shadow DOM
y las cookies. A continuación, descubriste cómo interactuar con elementos web
específicos, como listas desplegables (listas select y de datos), objetivos de navegación
(ventanas, pestañas, marcos e iframes) y cuadros de diálogo (alertas, mensajes,
confirmaciones y modales). Finalmente, revisamos el mecanismo para implementar el
almacenamiento web y los oyentes de eventos en Selenium WebDriver 4, así como las
excepciones más relevantes de WebDriver (y sus causas comunes).
El próximo capítulo continúa exponiendo las características de la API de Selenium
WebDriver. El capítulo explica aquellos aspectos específicos de un navegador dado (por
ejemplo, Chrome, Firefox, etc.), incluyendo capacidades del navegador (por ejemplo,
ChromeOptions, FirefoxOptions, etc.), el Protocolo Chrome DevTools (CDP),
interceptación de red, simulación de coordenadas de geolocalización, el protocolo
WebDriver BiDirectional (BiDi), mecanismos de autenticación, o la impresión de páginas
web en PDF, entre otras características.
CAPÍTULO 5
Manipulación Específica de Navegadores
Como has visto hasta ahora, muchas características de la API de Selenium WebDriver
son compatibles entre navegadores, es decir, podemos usar Selenium WebDriver para
controlar diferentes tipos de navegadores de manera programática. Sin embargo, otras
partes de la API de Selenium WebDriver no son interoperables entre navegadores. En
otras palabras, existen algunas características de WebDriver disponibles para ciertos
navegadores (por ejemplo, Chrome o Edge) que no están disponibles (o son diferentes)
para otros (por ejemplo, Firefox). Este capítulo revisa estas características específicas de
cada navegador.
Capacidades del Navegador
Selenium WebDriver permite especificar aspectos específicos del navegador mediante
el uso de capacidades. Ejemplos de capacidades son el modo sin cabeza (headless),
estrategias de carga de páginas, uso de extensiones web o gestión de notificaciones
push, entre muchas otras. Como muestra la Figura 5-1, la API de Selenium WebDriver
proporciona un conjunto de clases en Java para definir estas capacidades. La interfaz
Capabilities está en la parte superior de esta jerarquía. Internamente, la interfaz de
capacidades maneja datos usando pares clave-valor que encapsulan aspectos
específicos de un navegador. Luego, diferentes clases de Java implementan esta interfaz
para especificar capacidades para navegadores web (Chrome, Edge, Firefox, etc.). La
Tabla 5-1 resume las principales clases de la jerarquía de Capacidades y sus navegadores
objetivo correspondientes.
Figura 5-1. Jerarquía de Capacidades
Tabla 5-1. Descripción de la jerarquía de Capacidades
Las siguientes subsecciones revisan las capacidades más relevantes para los principales
navegadores web discutidos en este libro, es decir, Chrome, Edge y Firefox. Como
Chrome y Edge son navegadores basados en Chromium, las capacidades disponibles
para ambos navegadores son equivalentes. Este hecho se refleja en la Figura 5-1, que
muestra que las clases de capacidades ChromeOptions y EdgeOptions ambas heredan
de la misma clase padre (llamada ChromiumOptions).
Navegador Sin Cabeza
Los navegadores que no requieren una interfaz gráfica de usuario (GUI) para interactuar
con aplicaciones web se conocen como navegadores sin cabeza. Uno de los usos
principales de estos navegadores es la prueba de extremo a extremo, es decir, la
interacción automatizada con aplicaciones web. Los navegadores web actuales como
Chrome, Edge o Firefox pueden operar como navegadores sin cabeza. La API de
Selenium WebDriver permite iniciar estos navegadores en modo sin cabeza utilizando
capacidades. Para ello, primero necesitas crear una instancia de las capacidades del
navegador. En los principales navegadores, estos objetos son instancias de
ChromeOptions, EdgeOptions o FirefoxOptions, respectivamente. Luego, debes habilitar
el modo sin cabeza invocando el método `setHeadless(true)` en el objeto de capacidades
del navegador. Finalmente, debes establecer estas capacidades al crear un objeto
WebDriver.
Como se explicó en "Creación de WebDriver" en la página 53, tenemos diferentes
maneras de crear objetos WebDriver. Primero, podemos usar un constructor de
WebDriver (por ejemplo, `new ChromeDriver()`). También podemos usar un constructor
proporcionado por la API de Selenium WebDriver (es decir,
`RemoteWebDriver.builder()`). Finalmente, podemos usar el constructor de
WebDriverManager para resolver el driver y crear la instancia de WebDriver en una sola
línea de código. Los siguientes ejemplos muestran estas alternativas, utilizadas en
combinación con capacidades del navegador para habilitar el modo de navegador sin
cabeza, a saber:
• Ejemplo 5-1 utiliza Chrome en modo sin cabeza. Este ejemplo crea una instancia de
WebDriver utilizando el constructor requerido (ChromeDriver en este caso).
• Ejemplo 5-2 utiliza Edge en modo sin cabeza. Este ejemplo crea una instancia de
WebDriver utilizando el constructor disponible en la API de Selenium WebDriver.
• Ejemplo 5-3 utiliza Firefox en modo sin cabeza. Este ejemplo crea una instancia de
WebDriver utilizando WebDriverManager. Nota que el método de configuración no es
necesario en este caso ya que WebDriverManager resuelve el driver en la misma línea
que la instanciación de WebDriver.
• Ejemplo 5-4 utiliza Chrome en modo sin cabeza a través de Selenium-Jupiter. Este
ejemplo usa el mecanismo de resolución de parámetros proporcionado por Selenium-
Jupiter, y por lo tanto, simplemente declaramos un parámetro `ChromeDriver` en el
método de prueba. Luego, decoramos este parámetro usando la anotación
`@Arguments` para especificar el modo sin cabeza para este navegador.
Ejemplo 5-1. Prueba utilizando Chrome en modo sin cabeza
1. Re solucionamos el controlador requerido (chromedriver en este caso).
2. Creamos las capacidades del navegador usando el constructor `ChromeOptions`.
3. Activamos el modo sin cabeza. Esta línea es equivalente a
`options.addArguments("--headless");`.
4. Configuramos las capacidades del navegador pasando las opciones como
parámetro del constructor en el constructor de `ChromeDriver`.
Ejemplo 5-2. Prueba utilizando Edge en modo sin cabeza
1. Como de costumbre, necesitamos resolver el controlador requerido
(msedgedriver en este caso).
2. Dado que queremos usar Edge, necesitamos crear una instancia de
`EdgeOptions` para especificar las capacidades.
3. Activamos el modo sin cabeza. Nuevamente, esta línea es equivalente a
`options.addArguments("--headless");`.
4. Usamos el constructor de `WebDriver` para crear el objeto `WebDriver`, pasando
las opciones como parámetro.
Ejemplo 5-3. Prueba utilizando Firefox en modo sin cabeza
1. Usamos Firefox en esta prueba, por lo que creamos un objeto `FirefoxOptions`
para especificar las capacidades.
2. De la misma manera que en los ejemplos anteriores, activamos el modo sin
cabeza.
3. En este ejemplo, usamos `WebDriverManager` para resolver el controlador
requerido y crear el objeto `WebDriver`, especificando las capacidades del
navegador previamente creadas.
La estrategia utilizada para crear los objetos `WebDriver` en estos ejemplos
es intercambiable. En otras palabras, por ejemplo, también podemos usar el
constructor de `WebDriverManager` para cada navegador en modo sin
cabeza.
Ejemplo 5-4. Prueba utilizando Chrome en modo sin cabeza con Selenium-Jupiter
1. Usamos la anotación `@Arguments` para especificar el modo sin cabeza en el
navegador (Chrome en este caso).
Estrategias de Carga de Páginas
Selenium WebDriver permite configurar diferentes enfoques para la carga de páginas
web. Para ello, la API de Selenium WebDriver proporciona la enumeración
`PageLoadStrategy`. La Tabla 5-2 describe los posibles valores de esta enumeración y sus
propósitos. Selenium WebDriver utiliza internamente la propiedad
`document.readyState` de la API del DOM para verificar el estado de carga de la página
web.
Necesitamos invocar el método `setPageLoadStrategy()` de las capacidades del
navegador (por ejemplo, `ChromeOptions`, `FirefoxOptions`, etc.) para configurar estas
estrategias (NORMAL, EAGER o NONE). El Ejemplo 5-5 muestra una prueba usando
Chrome y la estrategia NORMAL. En el repositorio de ejemplos, puedes encontrar
ejemplos equivalentes para Edge y Firefox utilizando las otras estrategias (EAGER y
NONE). En estos ejemplos, además de especificar una estrategia de carga en la
configuración de la prueba, la lógica de la prueba calcula el tiempo requerido para cargar
la página, mostrando este valor en la salida estándar.
Ejemplo 5-5. Prueba utilizando una estrategia de carga de página normal en Chrome
1. Dado que usamos Chrome en esta prueba, instanciamos `ChromeOptions` para
especificar las capacidades.
2. Configuramos la estrategia de carga de la página a NORMAL.
3. Usamos `WebDriverManager` para resolver el controlador, crear la instancia de
`WebDriver` y especificar las capacidades.
4. Obtenemos la marca de tiempo del sistema antes de cargar la página.
5. Obtenemos la marca de tiempo del sistema después de cargar la página.
6. Leemos las capacidades del objeto `WebDriver`.
7. Leemos la estrategia de carga de la página utilizada.
8. Medimos el tiempo requerido para cargar la página web.
9. Verificamos que la estrategia de carga sea la que se configuró inicialmente.
Emulación de Dispositivos
Los principales navegadores web utilizan herramientas de desarrollo (es decir, DevTools
en navegadores basados en Chromium y Herramientas de Desarrollo en Firefox) para
simular dispositivos móviles de las siguientes maneras:
Simulación de un área de visualización móvil
Para reducir el área visible de una página web utilizando el ancho y alto de un
dispositivo móvil determinado.
Limitación de la red
Para reducir la velocidad de conectividad y simular redes móviles (por ejemplo,
3G).
Limitación de la CPU
Para reducir el rendimiento de procesamiento.
Simulación de geolocalización
Para establecer coordenadas personalizadas del Sistema de Posicionamiento
Global (GPS).
Configuración de la orientación
Para rotar la pantalla.
Figura 5-2 muestra una captura de pantalla de Chrome utilizando la emulación móvil a
través de DevTools.
Figura 5-2. Emulación móvil en Chrome usando DevTools
Hasta el momento de escribir esto, la emulación de dispositivos móviles se puede
automatizar a través de la API de Selenium WebDriver en navegadores basados en
Chromium (Chrome y Edge), pero no en Firefox (ya que no está implementado en
geckodriver). Para ello, necesitamos configurar la opción experimental
`mobileEmulation` en `ChromeOptions` o `EdgeOptions`.
Luego, hay dos alternativas para especificar el dispositivo móvil que se desea emular.
Primero, podemos especificar un dispositivo móvil en particular (por ejemplo, Pixel 2,
iPad Pro o Galaxy Fold, por nombrar algunos). Dado que esta lista se actualiza en cada
versión de Chromium, la mejor manera de comprobar las posibilidades es inspeccionar
los dispositivos disponibles en DevTools (por ejemplo, iPhone X está seleccionado en la
Figura 5-2).
Ejemplo 5-6. Configuración de prueba utilizando la emulación móvil especificando un
dispositivo
1. Necesitamos crear un objeto HashMap para especificar las opciones de
emulación móvil.
2. Luego, solo necesitamos seleccionar el nombre del dispositivo (iPhone 6/7/8 en
este caso).
3. Configuramos la emulación de dispositivos utilizando opciones experimentales.
4. Como de costumbre, creamos un objeto WebDriver especificando estas
opciones.
La segunda alternativa para configurar la emulación móvil es especificar los atributos
individuales del dispositivo emulado. Estos atributos son:
width
Ancho de la pantalla del dispositivo (en píxeles).
height
Altura de la pantalla del dispositivo (en píxeles).
pixelRatio
Proporción entre píxeles físicos y píxeles lógicos.
touch
Si se deben emular eventos táctiles; el valor predeterminado es true.
Además de estos atributos, podemos especificar el agente de usuario del dispositivo
emulado. En HTTP, el agente de usuario es una cadena especificada en los encabezados
de las solicitudes que identifica de manera única el tipo de navegador web. Contiene el
nombre del código de desarrollo, versión, plataforma y otra información.
Ejemplo 5-7. Configuración de prueba utilizando la emulación de dispositivos
especificando atributos individuales
1. Creamos un `hashmap` para almacenar los atributos individuales de un
dispositivo móvil emulado, a saber, ancho, altura, `pixelRatio` y `touch`.
2. Establecemos estos atributos configurando la etiqueta `deviceMetrics` en el
mapa de emulación móvil.
3. Establecemos un agente de usuario personalizado para un Chrome Mobile 18 en
un dispositivo Nexus 5.
Extensiones Web
Las extensiones web (también llamadas complementos o plug-ins) son programas que
pueden modificar o mejorar la operación predeterminada de un navegador web. Los
usuarios suelen instalar extensiones web a través de tiendas en línea. Estas tiendas son
aplicaciones web soportadas por los mantenedores del navegador para alojar
extensiones web públicas. La Tabla 5-3 resume las tiendas web para Chrome, Edge y
Firefox.
Podemos instalar extensiones web en una sesión de WebDriver utilizando capacidades.
Para ello, en los navegadores basados en Chromium, como Chrome y Edge, usamos el
método `addExtensions()` de un objeto `ChromeOptions` o `EdgeOptions`. El Ejemplo 5-
8 muestra una configuración de prueba para instalar una extensión local en Chrome.
Ejemplo 5-8. Configuración de prueba para instalar una extensión web en Chrome
1. Instalamos una extensión web empaquetada como un archivo de extensión de
Chrome (CRX). Este archivo es un recurso de prueba (ubicado en la carpeta
`src\test\resources` del proyecto Java). Esta extensión cambia el aspecto del sitio
web para usar texto claro sobre un fondo oscuro. La Figura 5-3 muestra una
captura de pantalla del sitio web de práctica cuando se carga mediante una
prueba de WebDriver utilizando esta extensión.
2. Agregamos la extensión en las opciones de Chrome, pasando la extensión como
un archivo Java.
Figura 5-3. Sitio de práctica cuando se carga utilizando la extensión dark-bg.crx
Firefox también permite cargar extensiones web cuando se controla con WebDriver. Sin
embargo, la sintaxis es diferente. El Ejemplo 5-9 ilustra esto.
1. Usamos la misma extensión que en Chrome/Edge, pero en este caso, el
empaquetado es específico para Firefox. Nota que el archivo es diferente. Esta
vez, está empaquetado como un archivo XPInstall, es decir, un archivo
comprimido que contiene el código fuente de la extensión web, recursos (por
ejemplo, imágenes) y metadatos.
2. Necesitamos crear un perfil personalizado de Firefox (es decir, el almacén donde
se configuran las configuraciones personalizadas).
3. Añadimos la extensión como un archivo Java al perfil de Firefox.
4. Configuramos el perfil en las opciones de Firefox.
Los navegadores basados en Chromium (por ejemplo, Chrome, Edge) también permiten
cargar una extensión desde su código fuente (es decir, no empaquetada como un
archivo CRX). Esta característica puede ser muy conveniente para las pruebas
automatizadas de extensiones web durante su desarrollo.
El Ejemplo 5-10 muestra una configuración de prueba que ilustra esta característica.
1. La extensión utilizada en este ejemplo se encuentra en la carpeta `web-
extension`; es una carpeta de recursos de prueba (almacenada en
`src\test\resources` del proyecto Java). Esta extensión sigue la API de
Extensiones de Navegador. Utiliza JavaScript para cambiar el contenido de los
encabezados de primer nivel (etiquetas `h1`) con un mensaje personalizado. La
Figura 5-4 muestra una captura de pantalla del sitio web de práctica al usar esta
extensión.
2. Especificamos la ruta de la extensión usando el argumento `--load-extension`.
Selenium WebDriver crea un nuevo perfil de navegador en cada ejecución.
Por esta razón, la instalación de extensiones web a través de Selenium
WebDriver no es permanente en los navegadores de destino.
Figura 5-4. Sitio de práctica cuando se carga usando la extensión local
A partir de Selenium 4.1, Firefox también permite instalar extensiones web desde su
código fuente. Para ello, `FirefoxDriver` extiende la interfaz `HasExtensions`,
proporcionando el método `installExtension`. El Ejemplo 5-11 muestra una
configuración de prueba utilizando esta función.
Ejemplo 5-11. Configuración de prueba instalando una extensión web desde su código
fuente en Firefox
Geolocalización
La API de Geolocalización es una especificación del W3C que permite acceder a la
información de ubicación geográfica asociada con el dispositivo anfitrión (por ejemplo,
una laptop o un móvil) del navegador web. Las fuentes habituales de datos de
geolocalización incluyen datos GPS y la ubicación inferida de la red, como la dirección
IP. La API de Geolocalización está disponible en un navegador web llamando al objeto
JavaScript `navigator.geolocation`. Al utilizar esta declaración, y por razones de
privacidad, se muestra un aviso emergente solicitando al usuario permiso para informar
los datos de ubicación.
El sitio de práctica contiene una página web que utiliza la geolocalización. La Figura 5-5
muestra una captura de pantalla de esta página. Esta figura muestra el aviso de permiso
que se muestra al usuario cuando hace clic en el botón "Obtener coordenadas". Para
manejar este diálogo utilizando la API de Selenium WebDriver, usamos capacidades. Al
igual que en otras ocasiones, las capacidades necesarias para conceder acceso a los
datos de geolocalización son diferentes en Chrome/Edge que en Firefox.
A continuación, se muestran los fragmentos de código que muestran la diferencia.
Primero, el Ejemplo 5-12 muestra una configuración de prueba donde se concede acceso
a la geolocalización en Chrome. La misma preferencia experimental
(`profile.default_content_setting_values.geolocation`) se utilizaría en Edge (como es
habitual, puede encontrar la prueba completa en el repositorio de ejemplos). Luego, el
Ejemplo 5-13 muestra la configuración de prueba equivalente, pero utilizando Firefox.
Figura 5-5. Sitio de práctica mostrando el aviso de permiso de geolocalización
Ejemplo 5-12. Configuración de prueba para permitir la geolocalización en Chrome
1. Creamos un `HashMap` para las opciones experimentales.
2. Establecemos en 1 la opción experimental
`profile.default_content_setting_values.geolocation` para permitir el acceso a la
posición de geolocalización. Los otros valores posibles son: 0 para el
comportamiento predeterminado y 2 para bloquear el acceso a los datos de
geolocalización.
3. Configuramos las opciones experimentales usando la etiqueta `prefs` en las
opciones de Chrome.
Supongamos que necesitas acceder a las coordenadas de geolocalización
usando Chrome o Edge en una máquina con macOS. En ese caso, también
tendrás que habilitar los servicios de ubicación para estos navegadores en las
preferencias de macOS (Preferencias del Sistema → Seguridad y Privacidad
→ Servicios de Ubicación). La Figura 5-6 muestra esta configuración.
Figura 5-6. Habilitación de los servicios de ubicación para Chrome y Edge en macOS
Ejemplo 5-13. Configuración de prueba para permitir la geolocalización en Firefox
1. Para habilitar la API de Geolocalización:
2. Conceda acceso a los datos de geolocalización (es decir, haga clic en permitir en
el mensaje emergente de acceso).
3. Reúna datos utilizando todos los componentes disponibles en el dispositivo,
como GPS, WiFi o Bluetooth.
Notificaciones
La API de Notificaciones es una API web estándar que permite a los sitios web enviar
notificaciones que se muestran en el escritorio del sistema operativo. Esta API está
disponible a través del objeto JavaScript `Notification`. Antes de que un sitio web pueda
enviar notificaciones, el usuario debe otorgar permiso. Este consentimiento se solicita
al usuario mediante un cuadro de diálogo emergente similar a los datos de
geolocalización. La página de práctica contiene una página web que utiliza la API de
Notificaciones. La Figura 5-7 muestra una captura de pantalla del mensaje emergente
de permiso de notificación para esta página. La Figura 5-8 muestra el mensaje enviado
por esta página web en un host Linux.
Figura 5-7. Sitio de práctica mostrando el mensaje emergente de permiso para
notificaciones
Figura 5-8. Sitio de práctica mostrando una notificación en un escritorio Linux
La API de Selenium WebDriver permite habilitar notificaciones mediante el uso de
capacidades. Al igual que en otras funciones, la sintaxis de estas capacidades es
diferente en Chrome/Edge y Firefox. El Ejemplo 5-14 muestra la configuración de prueba
para habilitar notificaciones en Chrome. Usamos la misma preferencia
(profile.default_content_setting_values.notifications) para permitir notificaciones en
Edge. El Ejemplo 5-15 muestra la configuración de prueba equivalente para Firefox. La
etiqueta de preferencia (permissions.default.desktopnotifications) es diferente en este
caso, aunque su valor (1) es el mismo para permitir notificaciones. El otro valor posible
es 2, que se usa para bloquear notificaciones (tanto en Chrome/Edge como en Firefox).
Ejemplo 5-14. Configuración de prueba para permitir notificaciones en Chrome
Ejemplo 5-15. Configuración de prueba para permitir notificaciones en Firefox
El Ejemplo 5-16 muestra la lógica de prueba utilizada con la configuración anterior.
Como de costumbre, puedes encontrar el caso de prueba completo en el repositorio de
ejemplos. Esta prueba es un ejemplo de ejecución de scripts asíncronos. Este script
reemplaza el objeto Notification original de JavaScript. La nueva implementación de este
objeto obtiene el título del mensaje de notificación, que se devuelve en el callback del
script a la prueba de WebDriver.
Ejemplo 5-16. Prueba manejando notificaciones
1. Como es habitual en la ejecución de scripts asíncronos, el último argumento es
la función de callback utilizada para señalar la terminación del script.
2. Almacenamos una copia del constructor original de `Notification`.
3. Creamos un nuevo constructor para las notificaciones.
4. Pasamos el título del mensaje como argumento en el callback. Como resultado,
el título se devuelve a la llamada de WebDriver (Java en este caso).
5. Usamos el antiguo constructor para crear un objeto `Notification` original.
6. Hacemos clic en el botón que desencadena la notificación en la página web.
7. Obtenemos el objeto devuelto después de la ejecución del script.
8. Verificamos que el título de la notificación sea el esperado.
Ruta del binario del navegador
Selenium WebDriver detecta automáticamente la ruta de los navegadores web
controlados (Chrome, Firefox, etc.). No obstante, podemos especificar una ruta
personalizada para el archivo ejecutable del navegador usando capacidades. Esta
característica puede ser útil cuando la ruta de instalación del navegador no es estándar
(por ejemplo, en el caso de navegadores beta/desarrollo/canary).
Usamos la misma sintaxis de capacidades para especificar la ruta binaria para Chrome,
Edge y Firefox. El Ejemplo 5-17 muestra una configuración de prueba utilizando Chrome
beta.
Ejemplo 5-17. Configuración de prueba estableciendo una ruta binaria personalizada
para Chrome
1. Usamos un `Path` de Java para obtener la ruta del binario del navegador (en este
caso, Chrome beta en Linux).
2. Usamos suposiciones para omitir condicionalmente esta prueba cuando la ruta
anterior no exista (por ejemplo, en el servidor CI).
3. Utilizamos el método `setBinary` de las opciones de Chrome para establecer la
ruta binaria (como un archivo de Java).
Proxies web
En redes informáticas, un proxy es un servidor que actúa como intermediario entre un
cliente y un servidor. Un proxy web es un proxy entre un navegador y un servidor web,
y puede servir para múltiples propósitos, como:
Acceder a información específica de una región
El proxy está ubicado típicamente en una región diferente a la del cliente, y el
servidor responde a esa región.
Evitar restricciones
Un proxy puede ayudar a acceder a sitios web bloqueados, por ejemplo, por un
firewall intermedio.
Capturar tráfico de red
Un proxy puede recopilar solicitudes y respuestas HTTP.
Cacheo
Un proxy puede permitir una recuperación más rápida de sitios web.
La Figura 5-9 representa la ubicación de un proxy web en la arquitectura de Selenium
WebDriver en comparación con el escenario típico en el que no se utiliza un proxy web.
Como se puede ver, el proxy web está colocado en el medio del navegador y la aplicación
web bajo prueba, y trabaja a nivel de HTTP. De esta manera, el proxy web permite
implementar los propósitos mencionados anteriormente (por ejemplo, capturar tráfico
de red HTTP) en las pruebas de Selenium WebDriver.
Figura 5-9. Arquitectura de Selenium WebDriver con y sin un proxy web
La API de Selenium WebDriver proporciona una clase `Proxy` para configurar un proxy
web. Esta clase se configura en un objeto WebDriver utilizando capacidades. El Ejemplo
5-18 ilustra cómo hacerlo.
1. Creamos una instancia de la clase Proxy.
2. La sintaxis necesaria para especificar un proxy es `host:puerto`.
3. Especificamos que el proxy se usa para conexiones HTTP.
4. También especificamos que el proxy se usa para conexiones HTTPS.
5. Aunque no es obligatorio, generalmente se requiere aceptar certificados
inseguros.
6. Configuramos el proxy como una capacidad. Esta línea es equivalente a
`options.setCapability(CapabilityType.PROXY, proxy);`.
"Capturar tráfico de red" en la página 296 muestra cómo usar una biblioteca
de terceros para capturar tráfico de red utilizando un proxy web en una prueba
de Selenium WebDriver.
Recopilación de registros
La API de Selenium WebDriver permite recopilar diferentes fuentes de registros. Esta
función se habilita utilizando capacidades, aunque solo es compatible con navegadores
basados en Chromium al momento de escribir esto. El Ejemplo 5-19 presenta una
configuración de prueba que habilita la recopilación de registros del navegador (es decir,
mensajes de la consola). Este fragmento también contiene la lógica de la prueba, en la
que necesitamos invocar `driver.manage().logs()` para recopilar la lista de registros.
Ejemplo 5-19. Prueba de recopilación de registros del navegador usando Chrome
1. Habilitamos la recopilación de todos los niveles de registros del navegador.
2. Configuramos la capacidad `loggingPrefs`.
3. Abrimos una página de práctica que registra varios rastros en la consola del
navegador.
4. Recopilamos todos los registros y los filtramos por navegador (rastros de
consola).
5. Verificamos que el número de rastros no sea cero.
6. Mostramos cada registro en la salida estándar.
La recopilación de registros no está disponible en la especificación W3C
WebDriver al momento de escribir esto. Sin embargo, se ha implementado en
algunos controladores como `chromedriver` o `msedgedriver` (es decir,
Chrome y Edge), pero está ausente en otros, como `geckodriver` (es decir,
Firefox).
Obtener medios del usuario
WebRTC es un conjunto de tecnologías estándar que permite intercambiar medios en
tiempo real usando navegadores web. Esta tecnología permite la creación de
aplicaciones web de audio y video conferencia usando APIs de JavaScript en el lado del
cliente. La página de práctica contiene una página web que obtiene medios del usuario
(micrófono y cámara web) usando la API de JavaScript `getUserMedia`. Al igual que con
otras APIs, y por razones de seguridad y privacidad, un pop-up del navegador solicita
permiso antes de acceder a los medios del usuario.
Figura 5-10. Página web de muestra cuando se muestra este diálogo.
Figura 5-10. Página de práctica mostrando el pop-up de permiso para los medios del
usuario
Utilizamos capacidades para conceder acceso a los medios del usuario en la API de
Selenium WebDriver. La sintaxis de estas capacidades es la misma en Chrome y Edge
(ver Ejemplo 5-20) pero diferente en Firefox (ver Ejemplo 5-21).
Ejemplo 5-20. Configuración de prueba para conceder medios de usuario sintéticos en
Chrome
1. Argumento para permitir el acceso a los medios del usuario (audio y video).
2. Argumento para simular los medios del usuario utilizando un video sintético
(rueda verde) y audio (un pitido por segundo). Puedes ver este video en la Figura
5-11.
Figura 5-11. Sitio de práctica utilizando medios sintéticos del usuario en Chrome.
Ejemplo 5-21. Configuración de prueba para otorgar medios sintéticos del usuario en
Firefox.
Preferencia para acceder a los medios del usuario.
Preferencia para simular los medios del usuario utilizando un video sintético (con fondo
de color cambiante) y audio (pitido constante). Puedes ver este video en la Figura 5-12.
Figura 5-12. Sitio de práctica usando medios sintéticos del usuario en Firefox
Cargando páginas inseguras
Cuando los navegadores web intentan cargar una página web usando HTTPS (Protocolo
de Transferencia de Hipertexto Seguro) pero el certificado en el lado del servidor es
inválido, el navegador advierte al usuario sobre ello. Ejemplos de certificados inválidos
son los auto-firmados, revocados o criptográficamente inseguros. La Figura 5-13
muestra una captura de pantalla de esta advertencia en Chrome.
Figura 5-13. Página web usando un certificado inseguro
Este problema no necesariamente implica una preocupación de seguridad. Puede
ocurrir durante el desarrollo de un sitio web, por ejemplo, cuando se utiliza un
certificado auto-firmado. Por esta razón, la API de Selenium WebDriver permite
desactivar las verificaciones de certificados utilizando la capacidad
`acceptInsecureCerts`. Esta capacidad es la misma en Chrome, Edge y Firefox. El Ejemplo
5-22 muestra una configuración de prueba en la que esta capacidad está habilitada
usando Chrome. Este fragmento también contiene una prueba que abre un sitio web
inseguro.
1. Habilitamos la capacidad para permitir certificados inseguros.
2. Abrimos un sitio web que utiliza un certificado inseguro (auto-firmado en este
caso).
3. Si el sitio web se carga, el fondo del cuerpo debería ser rojo.
Localización
En ingeniería de software, la localización se refiere al proceso de adaptar una aplicación
para cumplir con la cultura y el idioma (llamado `locale`) de sus usuarios finales. La
localización a veces se escribe como `l10n` (10 es el número de letras entre `l` y `n` en la
palabra en inglés `localization`). La actividad de localización más común es traducir el
texto que se muestra en la interfaz de usuario de la aplicación a diferentes idiomas.
Además, otros aspectos de la interfaz de usuario pueden ajustarse según el `locale`,
como las monedas (euros, dólares, etc.), los sistemas de medición (por ejemplo, el
sistema métrico o imperial), o el formato de números y fechas.
`L10n` es parte de un concepto más amplio llamado internacionalización (`i18n`), que es
el proceso de diseñar y desarrollar una aplicación que permita una fácil `l10n` para
audiencias heterogéneas. Las prácticas comunes para habilitar `i18n` son usar Unicode
para la codificación de texto o agregar soporte para texto vertical o tipografías no latinas
en CSS.
Las pruebas de localización son una forma de prueba no funcional donde se verifica un
Sistema Bajo Prueba (SUT) para configuraciones específicas de `locale`. La API de
Selenium WebDriver permite realizar pruebas de localización basadas en el idioma del
navegador al establecer la capacidad `intl.accept_languages`. Esta capacidad permite
especificar el identificador de `locale`, como `en_US` para inglés americano o `es_ES`
para español europeo, por mencionar algunos. El Ejemplo 5-23 muestra una
configuración de prueba que configura esta capacidad en Chrome. Podemos usar la
misma sintaxis en Edge, aunque especificamos esta capacidad como una preferencia en
Firefox (ver Ejemplo 5-24).
Ejemplo 5-23. Prueba que utiliza un `locale` preferido para Chrome
1. Especificamos el español europeo como el idioma preferido en Chrome.
2. Abrimos una página de práctica que admite varios idiomas (inglés y español).
3. Leemos las traducciones de texto usando un recurso de bundle. Puedes
encontrar estas cadenas en el archivo `strings_es.properties` (y
`strings_en.properties`) en la carpeta del proyecto `src/test/resources`.
4. Verificamos que el cuerpo del documento contenga todas las cadenas esperadas.
Ejemplo 5-24. Configuración de prueba que especifica un `locale` preferido para Firefox
Hay una segunda alternativa para practicar pruebas de localización con Selenium
WebDriver. En lugar de cambiar el idioma preferido (que determina el encabezado HTTP
`accept-language`), podemos cambiar el idioma predeterminado del navegador web. Si
ese encabezado HTTP no está presente, las aplicaciones multilingües usarán el idioma
del navegador como alternativa. La API de Selenium WebDriver permite cambiar el
idioma del navegador con un simple argumento llamado `--lang`, especificado como una
capacidad del navegador. Este argumento es interoperable en Chrome, Edge y Firefox.
El Ejemplo 5-25 muestra cómo configurar el idioma del navegador en inglés americano
usando capacidades de WebDriver.
Ejemplo 5-25. Configuración de prueba que cambia el idioma del navegador en Chrome
Modo Incógnito
El modo incógnito garantiza que los navegadores se ejecuten en un estado limpio. Este
modo permite la navegación privada, es decir, operar de forma aislada de la sesión
principal y los datos del usuario. La API de Selenium WebDriver permite la ejecución de
navegadores en modo incógnito utilizando capacidades. Para Chrome y Edge, este modo
se activa usando el argumento `--incognito` (ver Ejemplo 5-26), mientras que en Firefox,
se usa la preferencia `-private` (ver Ejemplo 5-27).
Ejemplo 5-26. Configuración de prueba para usar Chrome en modo incógnito
Ejemplo 5-27. Configuración de prueba para usar Firefox en modo incógnito
Edge en modo Internet Explorer
Edge ofrece soporte integrado para el navegador heredado de Microsoft, es decir,
Internet Explorer (IE). De esta manera, para crear una prueba de Selenium WebDriver
que use Edge en modo IE, primero debemos habilitar el modo IE en Edge. Como se
muestra en la Figura 5-14, esta opción se habilita en Configuración de Edge →
Navegador predeterminado → Permitir que los sitios se recarguen en modo Internet
Explorer. Luego, podemos usar la API de Selenium WebDriver como se ilustra en el
Ejemplo 5-28.
Figura 5-14. Configuración del navegador para habilitar Edge en modo IE
Ejemplo 5-28. Configuración de la prueba para usar Edge en modo IE
1. Asumimos que la prueba se ejecuta en Windows, ya que el modo IE no es
compatible con otros sistemas operativos.
2. Utilizamos WebDriverManager para gestionar IEDriver (el controlador necesario
para Internet Explorer).
3. Usamos WebDriverManager para descubrir la ruta de Edge.
4. Usamos las opciones de IE para especificar que utilizamos Edge en modo IE.
5. Establecemos la ruta de Edge descubierta anteriormente en las opciones de IE.
6. Creamos la instancia del controlador para usar Internet Explorer (que en realidad
será Edge en modo IE).
El protocolo Chrome DevTools
Chrome DevTools es un conjunto de herramientas para desarrolladores web en
navegadores basados en Chromium, como Chrome y Edge. Estas herramientas permiten
inspeccionar, depurar o perfilar estos navegadores, entre otras funciones. El Protocolo
Chrome DevTools (CDP) es un protocolo de comunicación que permite la manipulación
de Chrome DevTools por clientes externos. Firefox implementa un subconjunto del CDP
para soportar herramientas de automatización como Selenium WebDriver.
Hay dos maneras de utilizar CDP en Selenium WebDriver. A partir de la versión 4,
Selenium WebDriver proporciona la interfaz HasDevTools para enviar comandos CDP al
navegador. Esta interfaz es implementada por ChromiumDriver (utilizado para Chrome
y Edge) y FirefoxDriver (para Firefox). Este mecanismo es bastante poderoso, ya que
proporciona acceso directo al CDP con Selenium WebDriver. Sin embargo, tiene una
limitación relevante ya que está vinculado tanto al tipo de navegador como a su versión.
Por esta razón, la API de Selenium WebDriver proporciona una segunda forma de utilizar
el CDP, basada en un conjunto de clases envolventes construidas sobre el CDP para la
manipulación avanzada de los navegadores. Estas envolturas permiten diferentes
operaciones, como la interceptación del tráfico de red o la autenticación básica y digest.
La siguiente subsección explica estas envolturas. Después de eso, presento varios
ejemplos para usar los comandos CDP directamente.
Envoltorios CDP en Selenium
La API de Selenium WebDriver contiene un grupo de clases auxiliares que envuelven
algunos de los comandos CDP. Estas clases tienen como objetivo proporcionar una API
amigable que habilite funciones avanzadas para las pruebas en Selenium WebDriver.
Interceptador de red
La primera clase envolvente construida sobre el CDP se llama NetworkInterceptor. Esta
clase permite simular las solicitudes del backend, interceptar el tráfico de red y devolver
respuestas predefinidas. Esta función puede ayudar a simplificar pruebas de extremo a
extremo complejas al simular llamadas externas con respuestas rápidas y directas. Para
instanciar NetworkInterceptor, necesitamos especificar parámetros en su constructor
(ver Ejemplo 5-29):
- Un objeto WebDriver que implemente el CDP (es decir, ChromeDriver o EdgeDriver)
- Un objeto Route para mapear las solicitudes de red a respuestas
Ejemplo 5-29. Prueba interceptando tráfico de red utilizando NetworkInterceptor
1. Cargamos una imagen local almacenada como un recurso de prueba en el
proyecto Java.
2. Creamos una instancia de interceptador de red, creando una ruta para todas las
solicitudes que terminen en .png, y simulamos esta solicitud con una nueva
respuesta, en este caso, enviando el contenido de la imagen anterior.
3. Abrimos el sitio de práctica.
4. Si la interceptación funciona como se espera, la imagen en la página debería
tener un ancho mayor que el logotipo original.
Una DevToolsException se lanzará si se utiliza un navegador diferente a
Chrome o Edge (como Firefox) con el código anterior.
Autenticación básica y digest
HTTP proporciona dos mecanismos integrados para reconocer la identidad de un
usuario, llamados autenticación básica y digest. Ambos métodos permiten especificar
las credenciales del usuario utilizando un par de valores: nombre de usuario y
contraseña. La diferencia entre ellos es cómo comunican las credenciales. Por un lado,
el método de autenticación digest envía las credenciales cifradas aplicando una función
hash al nombre de usuario y la contraseña. Por otro lado, la autenticación básica utiliza
Base64 para codificar (no cifrar) las credenciales.
Selenium WebDriver proporciona la interfaz HasAuthentication para implementar de
manera fluida la autenticación básica y digest. El Ejemplo 5-30 muestra una prueba
utilizando Chrome y autenticación básica. Puedes usar el mismo mecanismo con Edge y
autenticación digest (ver la prueba completa en el repositorio de ejemplos).
Ejemplo 5-30. Prueba utilizando autenticación básica con Chrome
1. Convertimos el objeto del controlador a HasAuthentication y registramos las
credenciales (nombre de usuario y contraseña).
2. Abrimos un sitio web protegido con autenticación básica.
3. Verificamos que el contenido de la página esté disponible.
Cuando se usan otros navegadores (como Firefox), no podemos convertir el objeto del
controlador a HasAuthentication. Sin embargo, hay una forma general de enviar las
credenciales en la URL usando la sintaxis `protocol://username:password@domain`. El
Ejemplo 5-31 demuestra este uso.
Ejemplo 5-31. Prueba utilizando autenticación básica y Firefox
Comandos CDP en bruto
A partir de la versión 4, Selenium WebDriver proporciona la interfaz HasDevTools para
usar el CDP directamente. Esta interfaz es implementada por ChromiumDriver (usado
para Chrome y Edge) y FirefoxDriver (para Firefox). Para usar esta función, primero
necesitamos abrir una sesión CDP (es decir, una conexión WebSocket entre el cliente y
el navegador) utilizando el método createSession() de una instancia de DevTool. El
Ejemplo 5-32 muestra la estructura recomendada para usar CDP en pruebas de Selenium
WebDriver. Como puedes ver, la sesión CDP se crea en la configuración de la prueba y
se cierra en la finalización. Cada prueba usará el atributo de clase devTools para
interactuar con las DevTools de Chrome.
Ejemplo 5-32. Estructura de prueba para usar Chrome DevTools
Las siguientes subsecciones presentan varios ejemplos que ilustran el potencial de
DevTools en pruebas con WebDriver. En estos ejemplos, utilizamos una instancia de
DevTools para enviar comandos CDP usando el método send(). La API de Selenium
WebDriver proporciona varios comandos que permiten diferentes operaciones para
probar aplicaciones web, como la emulación de condiciones de red, el manejo de
encabezados HTTP, el bloqueo de URLs, entre otros.
Las pruebas de Selenium WebDriver que utilizan los comandos CDP en bruto
(como se explica en las siguientes subsecciones) están vinculadas a una versión
específica del navegador. Puedes ver esta versión inspeccionando las cláusulas
de importación (por ejemplo, **import org.openqa.selenium.devtools.v96.*;**)
en las pruebas completas disponibles en el repositorio de ejemplos.
Emular condiciones de red
El CDP permite la emulación de diferentes redes (como 2G/3G/4G móviles, WiFi, o
Bluetooth, entre otras) y condiciones (por ejemplo, latencia o ancho de banda). Esta
función puede ser útil para probar el comportamiento de las aplicaciones web bajo
parámetros de conectividad específicos. El **Ejemplo 5-33** muestra una prueba
utilizando esta función. Como puedes ver, esta prueba envía dos comandos CDP:
Network.enable()
Para activar el seguimiento de la red. Este comando tiene tres argumentos
opcionales:
Optional<Integer> maxTotalBufferSize
Tamaño máximo del búfer (en bytes) para las cargas útiles de la red.
Optional<Integer> maxResourceBufferSize
Tamaño máximo del búfer (en bytes) para recursos individuales.
Optional<Integer> maxPostDataSize
Tamaño máximo del cuerpo de la solicitud POST (en bytes).
Network.emulateNetworkConditions()
Para activar la emulación de la red. Las condiciones emuladas se especifican
usando los siguientes parámetros:
Boolean offline
Para emular la falta de conexión a Internet.
Number latency
Latencia mínima (en ms) desde la solicitud hasta la respuesta.
Number downloadThroughput
Ancho de banda máximo de descarga (en bytes/seg). -1 desactiva la limitación
de descarga.
Number uploadThroughput
Ancho de banda máximo de subida (en bytes/seg). -1 desactiva la limitación de
subida.
Optional<ConnectionType> connectionType
Tecnología de conexión emulada. La enumeración ConnectionType acepta las
siguientes opciones: NONE, CELLULAR2G, CELLULAR3G, CELLULAR4G,
BLUETOOTH, ETHERNET, WIFI, WIMAX, y OTHER.
Ejemplo 5-33. Prueba emulando condiciones de red
1. Activamos el seguimiento de la red (sin ajustar ningún parámetro de red).
2. Emulamos una red móvil 3G con un ancho de banda de 50 KBps para descarga y
subida.
3. Obtenemos una marca de tiempo del sistema antes de cargar una página web.
4. Cargamos la página de inicio del sitio de práctica.
5. Calculamos el tiempo necesario para cargar esta página.
Monitoreo de red
También podemos usar el CDP para monitorear el tráfico de red al interactuar con
páginas web. El **Ejemplo 5-34** muestra una prueba utilizando esta función. Esta
prueba utiliza el método **addListener()** de un objeto **DevTools** para rastrear
solicitudes y respuestas HTTP.
Ejemplo 5-34. Prueba de monitoreo de solicitudes y respuestas HTTP
1. Creamos un listener para las solicitudes HTTP y registramos los datos capturados
en la consola.
2. Creamos un listener para las respuestas HTTP y registramos los datos capturados
en la consola.
Captura de pantalla de página completa
Otro uso posible del CDP es hacer capturas de pantalla de una página completa (es decir,
capturar el contenido de la página más allá del área visible). El **Ejemplo 5-35**
demuestra esta función en Chrome.
Ejemplo 5-35. Prueba para hacer una captura de pantalla de página completa usando
CDP en Chrome
1. Cargamos la página de práctica que contiene un texto largo (y, por lo tanto, su
contenido va más allá del área visible estándar).
2. Esperamos hasta que se carguen los párrafos.
3. Obtenemos las métricas de diseño de la página (para calcular las dimensiones de
la página).
4. Enviamos el comando CDP para hacer una captura de pantalla más allá del área
visible de la página. Como resultado, obtenemos la captura de pantalla como una
cadena en Base64.
5. Decodificamos el contenido en Base64 a un archivo PNG.
6. Verificamos que el archivo PNG exista al final de la prueba.
Esta función está disponible en otros navegadores con una implementación completa
del CDP, como Chrome o Edge. Sin embargo, puede que no esté disponible en otros
como Firefox. Afortunadamente, Firefox admite la misma característica a través del
método `getFullPageScreenshotAs()` disponible en los objetos FirefoxDriver. El Ejemplo
5-36 muestra una prueba usando este método y Firefox.
Ejemplo 5-36. Prueba para hacer una captura de pantalla de página completa usando
Firefox
Hacemos una captura de pantalla de toda la página. Al igual que con las capturas de
pantalla normales (ver Tabla 4-2 en el Capítulo 4), el tipo de salida puede ser ARCHIVO,
BASE64 o BYTES. Utilizamos este último para obtener la captura de pantalla como un
arreglo de bytes.
Métricas de rendimiento
El CDP permite recopilar métricas de rendimiento en tiempo de ejecución, como el
número de documentos cargados, el número de nodos DOM, el tiempo para cargar el
DOM y la duración del script, entre otros. El **Ejemplo 5-37** muestra una prueba que
recopila estas métricas y las muestra en la salida estándar.
Ejemplo 5-37. Prueba que recopila métricas de rendimiento
1. Habilitamos la recopilación de métricas.
2. Recopilamos todas las métricas.
Encabezados adicionales
El CDP permite agregar encabezados adicionales a nivel de HTTP. Para ello, necesitamos
enviar el comando `Network.setExtraHTTPHeaders()` en una sesión de CDP. El Ejemplo
5-38 muestra una prueba que utiliza este comando para agregar el encabezado HTTP
`Authorization`, con el fin de enviar credenciales (nombre de usuario y contraseña) en
una página web que requiere autenticación básica para iniciar sesión.
Ejemplo 5-38. Prueba añadiendo encabezados HTTP adicionales
1. Codificamos el nombre de usuario y la contraseña en Base64.
2. Creamos el encabezado de autorización.
3. Abrimos una página web protegida con autenticación básica.
4. Verificamos que la página se muestra correctamente.
Bloquear URLs
El CDP proporciona la capacidad de bloquear URLs específicas en una sesión. El Ejemplo
5-39 muestra una prueba que bloquea la URL del logo de la página web de práctica. Si
ejecutas esta prueba e inspeccionas el navegador durante la ejecución, descubrirás que
este logo no se muestra en la página.
Ejemplo 5-39. Prueba bloqueando una URL
1. Bloqueamos una URL específica.
2. Creamos un listener para rastrear los eventos fallidos.
Emulación de dispositivos
Otra característica proporcionada por el CDP es la capacidad de emular dispositivos
móviles (por ejemplo, smartphones, tabletas). El Ejemplo 5-40 ilustra este uso. Esta
prueba primero anula el agente de usuario enviando el comando
`Network.setUserAgentOverride()`. Luego, emula las métricas del dispositivo enviando
el comando `Emulation.setDeviceMetricsOverride`.
Ejemplo 5-40. Prueba de emulación
1. Sobrescribimos el agente de usuario para emular un Apple iPhone 6.
2. Sobrescribimos los parámetros de pantalla del dispositivo.
Listeners de consola
El CDP permite implementar listeners para monitorear eventos de consola, es decir,
registros y trazas de errores en JavaScript de una página web. El **Ejemplo 5-41**
muestra la prueba. Esta prueba utiliza una página web en el sitio de práctica que
intencionalmente genera varios mensajes de JavaScript (usando los comandos
`console.log()`, `console.error()`, etc.) y también lanza una excepción en JavaScript.
1. Creamos un listener para eventos de consola.
2. Creamos otro listener para errores de JavaScript.
3. Abrimos la página de práctica que escribe mensajes en la consola del navegador.
4. Esperamos un máximo de cinco segundos hasta recibir un evento de consola.
5. Escribimos la información sobre el evento de consola recibido en la salida
estándar.
6. Repetimos el mismo procedimiento para las excepciones de JavaScript.
Anulación de geolocalización
Otra función proporcionada por el CDP es la capacidad de anular las coordenadas de
geolocalización gestionadas por el dispositivo anfitrión. El Ejemplo 5-42 demuestra
cómo hacerlo. Esta prueba envía el comando `Emulation.setGeolocationOverride()`, que
acepta tres argumentos opcionales: latitud, longitud y precisión.
1. Anulamos la ubicación geográfica utilizando las coordenadas de la Torre Eiffel
(París, Francia).
2. Abrimos una página web de práctica que accede a la ubicación del dispositivo y
muestra las coordenadas al usuario.
Administrar cookies
El CDP también permite gestionar las cookies web. El Ejemplo 5-43 muestra una prueba
que lee las cookies de una página de práctica que maneja algunas cookies.
1. Leemos todas las cookies de una página web.
2. Verificamos que las cookies leídas utilizando el comando CDP y las cookies leídas
con la API de Selenium WebDriver (usando `getCookies();`) sean las mismas.
3. Eliminamos todas las cookies.
4. Verificamos que no queden cookies en este punto.
Cargar páginas inseguras
El CDP también permite cargar páginas web inseguras (es decir, páginas web que usan
HTTPS pero cuyo certificado no es válido). El Ejemplo 5-44 ilustra esta característica.
1. Habilitamos el seguimiento de seguridad.
2. Ignoramos los errores de certificado.
3. Verificamos que la página se cargue correctamente.
Contexto de Ubicación
La API de Selenium WebDriver proporciona la interfaz `LocationContext` para simular las
coordenadas de geolocalización del dispositivo del usuario. Esta interfaz está
implementada por `ChromeDriver`, `EdgeDriver` y `OperaDriver`. Por lo tanto, estos
controladores pueden invocar el método `setLocation()` para especificar coordenadas
personalizadas (latitud, longitud y altitud). El **Ejemplo 5-45** muestra una prueba
básica usando esta característica.
1. Convertimos el objeto del controlador a `LocationContext` (solo posible para
Chrome, Edge u Opera).
2. Abrimos una página de práctica donde se muestran las coordenadas de
geolocalización al usuario final.
3. Establecemos una ubicación personalizada, en este caso, las coordenadas del
Monte Everest (en la frontera Nepal-China).
4. Verificamos que las coordenadas sean visibles en la página.
Autenticación Web
La API de Autenticación Web (también conocida como WebAuthn) es una especificación
del W3C que permite a los servidores registrar y autenticar a los usuarios utilizando
criptografía de clave pública en lugar de contraseñas. Los principales navegadores
(Chrome, Firefox, Edge y Safari) han soportado WebAuthn desde enero de 2019. Estos
navegadores permiten la creación y verificación de credenciales usando tokens U2F
(Universal 2nd Factor), que son dispositivos seguros de Universal Serial Bus (USB) o
Near-Field Communication (NFC).
En el enfoque clásico de autenticación web, los usuarios envían su nombre de usuario y
contraseña al servidor usando un formulario web. En WebAuthn, el servidor web usa la
API de Autenticación Web para solicitar al usuario que cree un par de claves privada y
pública (conocido como una credencial). La clave privada se almacena de manera segura
en el dispositivo del usuario, y la clave pública se envía al servidor. Luego, el servidor
puede usar esa clave pública para validar la identidad del usuario.
A partir de la versión 4, Selenium WebDriver soporta WebAuthn de forma nativa. Para
ello, la API de Selenium WebDriver proporciona la interfaz `HasVirtualAuthenticator`. En
lugar de usar dispositivos físicos seguros, esta interfaz permite usar autenticadores
virtuales. Aunque la clase `RemoteWebDriver` implementa esta interfaz, en el momento
de escribir esto, este mecanismo solo está soportado en navegadores basados en
Chromium, es decir, Chrome y Edge. El Ejemplo 5-46 muestra una prueba usando la API
de Autenticación Web.
Ejemplo 5-46. Prueba utilizando WebAuthn
1. Abrimos un sitio web protegido con la API de Web Authentication.
2. Convertimos el objeto del driver a HasVirtualAuthenticator.
3. Creamos y registramos un nuevo autenticador virtual.
4. Enviamos un identificador aleatorio en el formulario web.
5. Enviamos ese identificador y esperamos a que sea recibido.
6. Hacemos clic en el botón para iniciar sesión.
7. Verificamos que la autenticación se haya realizado correctamente.
8. Eliminamos el autenticador virtual.
Imprimir Página
Selenium WebDriver permite imprimir páginas web en documentos PDF. Para ello, la
API de Selenium WebDriver proporciona la interfaz `PrintsPage`. Esta interfaz es
heredada por la clase `RemoteWebDriver`, y por lo tanto, está disponible para todos los
navegadores soportados por Selenium WebDriver. Sin embargo, existen ligeras
diferencias al utilizar un navegador u otro. Por ejemplo, imprimir páginas es posible en
Chrome y Edge solo si el navegador se inicia en modo headless. Para Firefox, esta
restricción no es necesaria, y podemos usar Firefox como de costumbre. El **Ejemplo
5-47** muestra la lógica de prueba para imprimir una página web en PDF. Puedes
encontrar las pruebas completas para Firefox y Chrome/Edge en modo headless en el
repositorio de ejemplos.
1. Convertimos el objeto del driver a `PrintsPage`.
2. Imprimimos la página web actual en PDF utilizando la configuración
predeterminada.
3. Obtenemos el contenido del PDF en Base64.
4. Verificamos que este contenido contenga la firma del archivo (la "palabra
mágica" JVBER).
5. Convertimos el Base64 en un arreglo de bytes crudos.
6. Escribimos el contenido del PDF (arreglo de bytes) en un archivo local.
WebDriver BiDi
El WebDriver BiDi es un borrador del W3C que define el protocolo bidireccional de
WebDriver. En lugar del estricto formato de comando/respuesta del protocolo
WebDriver, BiDi introduce una conexión WebSocket entre el driver y el navegador para
habilitar la comunicación bidireccional. De esta manera, WebDriver BiDi permitirá
diferentes operaciones usando un transporte bidireccional rápido (es decir, sin
necesidad de sondear el navegador para obtener respuestas).
En Selenium WebDriver, el objetivo es que BiDi sea un reemplazo estandarizado a largo
plazo para las operaciones avanzadas actualmente soportadas por CDP. Por ejemplo, la
API de Selenium WebDriver soporta la implementación de escuchadores de eventos a
través de la interfaz `HasLogEvents`. Esta interfaz funciona sobre CDP en el momento de
escribir esto. No obstante, usará BiDi internamente en futuras versiones de Selenium
WebDriver, proporcionando una compatibilidad más robusta entre navegadores.
`HasLogEvents` permite implementar escuchadores para los siguientes eventos:
domMutation
Para capturar eventos sobre cambios en el DOM. El Ejemplo 5-48 muestra una
prueba que implementa un escuchador para estos eventos.
consoleEvent
Para capturar eventos sobre cambios en la consola del navegador, como trazas de
JavaScript. El Ejemplo 5-49 muestra una segunda prueba que implementa este tipo
de escuchador.
Ejemplo 5-48. Prueba implementando un escuchador para eventos de mutación del
DOM
1. Convertimos el objeto del driver a `HasLogEvents`. Esta conversión solo es
posible para Chrome y Edge.
2. Creamos un escuchador para eventos de mutación del DOM. Esta prueba espera
capturar solo un evento, sincronizado usando un `countdown latch`.
3. Forzamos una mutación del DOM ejecutando JavaScript para cambiar la fuente
de una imagen.
4. Verificamos que el evento ocurra en un máximo de 10 segundos.
5. Comprobamos que la fuente de la imagen haya cambiado.
1. Creamos un escuchador para eventos de la consola. Esta prueba espera capturar
cuatro eventos sincronizados usando un `countdown latch`.
2. Abrimos la página web de práctica, que registra varios mensajes en la consola de
JavaScript.
Resumen y Perspectivas
Este capítulo presentó una visión práctica de las características de la API de Selenium
WebDriver que no son interoperables entre navegadores. Primero, descubriste cómo
usar capacidades para ejecutar navegadores en modo sin cabeza, cambiar la estrategia
de carga de páginas, usar extensiones web o gestionar ventanas emergentes del
navegador (por ejemplo, geolocalización, notificaciones o obtener medios del usuario),
entre otras capacidades. Luego, aprendiste que Selenium WebDriver ofrece diferentes
formas de interactuar con navegadores web usando el CDP. Este mecanismo permite
incorporar muchas funciones potentes en nuestras pruebas de Selenium WebDriver,
como emular condiciones de red, autenticación básica y digestiva, monitoreo de red,
manejo de encabezados HTTP o bloqueo de URL, por mencionar algunas. Después,
descubriste otras características específicas de los navegadores, como el contexto de
ubicación, autenticación web (WebAuthn) e impresión de páginas web en documentos
PDF. Finalmente, aprendiste sobre WebDriver BiDi, una estandarización en borrador que
define la comunicación bidireccional con los navegadores con fines de automatización.
BiDi se encuentra en una etapa temprana en el momento de escribir esto. El objetivo es
que Selenium WebDriver soporte diferentes características estándar basadas en BiDi en
futuras versiones.
El próximo capítulo concluye nuestro recorrido con la API de Selenium WebDriver. El
capítulo explica cómo usar esta API para controlar navegadores remotos. Estos
navegadores pueden estar alojados en Selenium Grid, un proveedor de nube (por
ejemplo, Sauce Labs, BrowserStack o CrossBrowserTesting), o ejecutados en
contenedores Docker.
CAPÍTULO 6
WebDriver Remoto
Hasta ahora, los ejemplos explicados en este libro utilizan navegadores web instalados
localmente en la máquina que ejecuta las pruebas. Este capítulo cubre otra
característica relevante de la API de Selenium WebDriver: la capacidad de utilizar
navegadores remotos (es decir, instalados en otros hosts). Primero, revisamos la
arquitectura que permite utilizar navegadores remotos en Selenium WebDriver.
Segundo, estudiamos Selenium Grid, una infraestructura en red que proporciona
navegadores remotos para pruebas de Selenium WebDriver. Tercero, analizamos
algunos de los proveedores de servicios en la nube más relevantes, es decir, empresas
que ofrecen servicios gestionados para pruebas automatizadas. Finalmente, exploramos
cómo utilizar Docker para soportar la infraestructura de navegadores para Selenium.
Arquitectura de Selenium WebDriver
Como se introdujo en el Capítulo 1, Selenium WebDriver es una biblioteca que permite
controlar navegadores web de manera programática. La automatización se basa en las
capacidades nativas de cada navegador. Por lo tanto, necesitamos colocar un archivo
binario llamado driver entre el script (típicamente, una prueba) que utiliza la API de
Selenium WebDriver y el navegador. Los ejemplos que has visto hasta ahora en este
libro utilizan navegadores locales, es decir, navegadores instalados en la misma máquina
que ejecuta la prueba que usa la API de Selenium WebDriver.
La Figura 6-1 ilustra este enfoque. En este caso, y cuando usamos el enlace de lenguaje
Java de la API de Selenium WebDriver, necesitamos crear una instancia de
`ChromeDriver` para controlar Chrome, `FirefoxDriver` para Firefox, etc.
Figura 6-1. Arquitectura de Selenium WebDriver utilizando navegadores locales
El protocolo de comunicación que soporta este proceso se llama W3C WebDriver. Este
protocolo estándar se basa en mensajes JSON sobre HTTP. Gracias a esto, la arquitectura
de Selenium WebDriver puede distribuirse a diferentes computadoras interconectadas
(hosts). La Figura 6-2 muestra una representación esquemática de una arquitectura
remota.
Figura 6-2. Arquitectura de Selenium WebDriver utilizando navegadores remotos
En este caso, la API de Selenium WebDriver envía mensajes W3C WebDriver a un
componente en el servidor, comúnmente denominado Selenium Server. Este servidor
actúa como un intermediario para las solicitudes del cliente hacia otros hosts que
proporcionan los navegadores web donde se realiza la automatización. Esta arquitectura
remota facilita la prueba en múltiples navegadores (es decir, verificar aplicaciones web
en diferentes tipos de navegadores, versiones o sistemas operativos) y la ejecución
paralela de pruebas.
Creación de Objetos RemoteWebDriver
La API de Selenium WebDriver proporciona la clase RemoteWebDriver para controlar
navegadores remotos. Como se muestra en la Figura 2-2, esta clase es la clase base de
otras clases de WebDriver que controlan navegadores locales (es decir, ChromeDriver,
FirefoxDriver, etc.). De esta manera, los objetos RemoteWebDriver se pueden utilizar de
la misma manera que se ha estudiado anteriormente en este libro.
Constructor de RemoteWebDriver
Existen diferentes métodos para instanciar un objeto RemoteWebDriver. El método más
común consiste en invocar su constructor pasando dos argumentos: la URL del Selenium
Server y las capacidades requeridas. Como se ilustra en la Figura 5-1, estas capacidades
son objetos que heredan de la interfaz Capabilities (por ejemplo, ChromeOptions,
FirefoxOptions, etc.). El Ejemplo 6-1 muestra una configuración de prueba. Puede
encontrar la prueba completa en el repositorio de código de este libro.
Hay un segundo constructor de RemoteWebDriver, que acepta solo un
parámetro para las capacidades deseadas. En este caso, la URL del Selenium
Server se lee desde la propiedad del sistema Java `webdriver.remote.server`.
Puede encontrar un ejemplo de esta característica en el repositorio de ejemplos.
Ejemplo 6-1. Instanciación de un objeto RemoteWebDriver utilizando el constructor
1. Creamos un objeto Java URL con la dirección del Selenium Server.
2. Suponemos que esta URL está en línea. Para ello, creamos una suposición de
AssertJ invocando el método estático `isOnline` disponible en
WebDriverManager. Como resultado, la prueba se omite cuando el Selenium
Server está fuera de línea.
3. Instanciamos un objeto `ChromeOptions` para especificar las capacidades
requeridas.
4. Invocamos el constructor de `RemoteWebDriver` usando la URL del Selenium
Server y las opciones de Chrome como argumentos.
Usamos un objeto `ChromeOptions` sin ninguna configuración particular para
especificar las capacidades requeridas en el ejemplo anterior. En otras palabras,
solicitamos usar un navegador Chrome remoto con su comportamiento
predeterminado. No obstante, podríamos usar este objeto para configurar capacidades
específicas (por ejemplo, navegador sin interfaz gráfica, estrategias de carga de página,
emulación de dispositivos, etc.) como se explicó en el Capítulo 5. Además, dado que las
capacidades se manejan internamente utilizando pares clave-valor que encapsulan
aspectos específicos del navegador, podemos gestionar capacidades individuales
invocando el método `options.setCapability(key, value);`. La API de Selenium WebDriver
proporciona la clase `CapabilityType` para especificar la clave de estas capacidades. Esta
clase tiene un conjunto de atributos públicos que se pueden usar como clave en el
método `setCapability()`. La Tabla 6-1 muestra algunos de estos atributos.
Tabla 6-1. Atributos de CapabilityType
Una forma alternativa de especificar las capacidades requeridas en un objeto
`RemoteWebDriver` es utilizando una instancia de `DesiredCapabilities`. La Tabla 6-2
resume los métodos proporcionados por estos objetos.
Tabla 6-2. Métodos de DesiredCapabilities
`DesiredCapabilities` todavía es compatible con Selenium WebDriver 4, ya que
existe mucho código existente que depende de esta función. Sin embargo, la forma
recomendada de especificar las capacidades es utilizando opciones específicas del
navegador (por ejemplo, `ChromeOptions`, `FirefoxOptions`, etc.).
Constructor de RemoteWebDriver
Una segunda forma de crear objetos `RemoteWebDriver` es utilizando el constructor
incorporado disponible en la API de Selenium WebDriver. El Ejemplo 6-2 demuestra
cómo hacerlo, utilizando Edge como el navegador remoto.
Ejemplo 6-2. Instanciación de un objeto RemoteWebDriver utilizando el constructor
Constructor de WebDriverManager
Alternativamente, también podemos usar WebDriverManager para crear una instancia
de RemoteWebDriver. Para ello, necesitamos invocar el método `remoteAddress()` de
un administrador dado para pasar la URL del servidor Selenium. El **Ejemplo 6-3**
muestra una configuración de prueba utilizando esta característica y Firefox como el
navegador remoto.
Ejemplo 6-3. Instanciación de un objeto RemoteWebDriver utilizando
WebDriverManager
Selenium-Jupiter
Como es habitual, Selenium-Jupiter utiliza la característica de resolución de parámetros
proporcionada por Jupiter. De esta manera, y en lo que respecta a los navegadores
remotos, necesitas declarar un parámetro de prueba (o constructor) usando el tipo
`RemoteWebDriver`. Luego, las siguientes anotaciones de Selenium-Jupiter permiten
configurar el navegador remoto:
@DriverUrl
Anotación utilizada para identificar la URL del servidor Selenium. Alternativamente, la
anotación **@EnabledIfDriverUrlOnline** permite especificar esta URL y, al mismo
tiempo, desactiva la prueba si esa URL no está respondiendo.
@DriverCapabilities
Anotación utilizada para configurar las capacidades deseadas.
El Ejemplo 6-4 muestra una prueba de Selenium-Jupiter utilizando un Chrome remoto
proporcionado por un servidor Selenium local. Esta prueba se omitirá cuando la URL
`http://localhost:4444/` esté fuera de línea.
Ejemplo 6-4. Uso de un objeto RemoteWebDriver en una prueba de Selenium-Jupiter
Los modos de instanciación de RemoteWebDriver descritos en esta sección
son equivalentes desde el punto de vista funcional. En otras palabras, estos
objetos funcionan de la misma manera. La diferencia entre ellos radica en el
azúcar sintáctico proporcionado (es decir, el estilo y la expresividad).
Selenium Grid
Como se introdujo en el Capítulo 1, Selenium Grid es un subproyecto del conjunto de
herramientas Selenium que permite crear una infraestructura en red para navegadores
remotos accesibles mediante el protocolo W3C WebDriver. Selenium Grid permite
ejecutar pruebas en paralelo en diferentes máquinas y diferentes navegadores. Para
ello, Selenium Grid proporciona un servidor Selenium que puedes controlar utilizando
una instancia de RemoteWebDriver. Hay tres formas de ejecutar Selenium Grid:
Standalone (Autónomo)
Un solo host actúa como servidor Selenium y suministra los navegadores en este
modo. Proporciona una manera simple de ejecutar pruebas de Selenium
WebDriver en navegadores remotos.
Hub-nodes (Hub-Nodos)
Un problema potencial del modo autónomo es la escalabilidad (ya que el servidor
Selenium y los navegadores se ejecutan en la misma máquina). Así, la arquitectura
hub-nodes define dos tipos de componentes para resolver este problema.
Primero, un host actúa como hub (o servidor Selenium). Luego, uno o más hosts
se registran como nodos en el hub, proporcionando navegadores que se controlan
con Selenium WebDriver. Esta arquitectura se introdujo en el Capítulo 1 (ver
Figura 1-2).
Fully distributed (Totalmente distribuido)
Los enfoques autónomo y hub-nodes son arquitecturas centralizadas que pueden
degradar el rendimiento cuando aumenta el número de solicitudes entrantes. A
partir de Selenium 4, Selenium Grid proporciona un modo completamente
distribuido que implementa mecanismos de balanceo de carga para resolver este
cuello de botella.
Las siguientes subsecciones proporcionan más detalles sobre estos modos y explican
cómo configurar cada enfoque.
Standalone
El modo autónomo es el enfoque más simple para una infraestructura de Selenium Grid.
Podemos ejecutar este modo utilizando la shell y el código Java.
Desde la shell
Primero, podemos usar la shell y la distribución binaria de Selenium Grid para iniciarlo.
Selenium Grid está desarrollado en Java, y cada versión se distribuye como un archivo
JAR independiente con todas sus dependencias (también conocido como uber-JAR o fat-
JAR). Puedes descargar este fat-JAR desde la página de descargas de Selenium.
El servidor Selenium detecta automáticamente los controladores (por ejemplo,
chromedriver, geckodriver, etc.) disponibles en el sistema en el modo autónomo. Para
ello, busca estos controladores en la variable de entorno PATH. Como de costumbre,
podemos gestionar estos controladores manualmente. Sin embargo, se recomienda
usar WebDriverManager para resolver los controladores automáticamente. Así, y como
se explica en el Apéndice B, WebDriverManager puede usarse como una herramienta
de línea de comandos (CLI). WebDriverManager CLI se distribuye como un fat-JAR,
disponible para su descarga en GitHub.
Para ilustrar esto, el **Ejemplo 6-5** muestra los comandos de shell necesarios para
resolver chromedriver y geckodriver en una máquina Linux con WebDriverManager CLI.
Luego, usamos el fat-JAR de Selenium Grid para iniciar un grid autónomo. Ten en cuenta
que estos comandos se ejecutan en la misma carpeta. De esta manera, los controladores
descargados con WebDriverManager están disponibles para Selenium Grid.
Ejemplo 6-5. Comandos para resolver controladores con WebDriverManager CLI y para
iniciar Selenium Grid en modo autónomo usando la Shell
1. Usamos WebDriverManager CLI para resolver chromedriver.
2. Usamos WebDriverManager CLI para resolver geckodriver (el controlador
requerido para Firefox).
3. Iniciamos Selenium Grid en modo autónomo en la misma carpeta (que contiene
chromedriver y geckodriver).
Después de estos comandos, el servidor Selenium autónomo escucha solicitudes HTTP
entrantes en el puerto 4444 del localhost. Por lo tanto, podemos crear una instancia de
RemoteWebDriver usando esa URL (por ejemplo, http://localhost:4444/ si la prueba se
ejecuta en el mismo host) y las capacidades requeridas (para Chrome o Firefox, en este
caso). Por ejemplo:
En Selenium Grid 3, la URL predeterminada del servidor Selenium es
http://localhost:4444/wd/hub. En Selenium Grid 4, aunque esta URL también
debería funcionar, el camino /wd/hub ya no es necesario.
Otra característica útil proporcionada por Selenium Grid es su consola web. Esta consola
es una interfaz gráfica accesible en la URL del servidor Selenium que permite monitorear
los navegadores disponibles registrados en la cuadrícula y las sesiones en ejecución.
La Figura 6-3 muestra una captura de pantalla de la consola de la cuadrícula autónoma
anterior. Nota que, en este caso, el servidor Selenium autónomo puede manejar hasta
ocho sesiones concurrentes (el mismo número de procesadores disponibles en la
máquina que ejecuta la cuadrícula) de Chrome y Firefox.
Figura 6-3. Consola de Selenium Grid
Desde código Java
Una forma alternativa de iniciar Selenium Grid es usando Java. Además del fat-JAR,
Selenium Grid se publica en Maven Central usando `org.seleniumhq.selenium` como
`groupId` y `selenium-grid` como `artifactId`. De esta manera, necesitamos resolver sus
coordenadas en la configuración de nuestro proyecto (Maven o Gradle) para usarlo en
nuestro proyecto Java (consulta el Apéndice B para obtener detalles de configuración).
El Ejemplo 6-6 demuestra cómo iniciar Selenium Grid en modo standalone desde un caso
de prueba en Java.
Ejemplo 6-6. Prueba de inicio de Selenium Grid en modo standalone
1. Encontramos un puerto libre en el localhost. Para eso, utilizamos la clase
`PortProber`, disponible en la API de Selenium WebDriver.
2. Utilizamos `WebDriverManager` para resolver `chromedriver`, ya que el grid
standalone que se iniciará servirá a navegadores Chrome.
3. Iniciamos Selenium Grid en modo standalone, llamando a su método `main`.
4. Creamos una URL en Java usando el puerto previamente seleccionado.
5. Creamos una instancia de `RemoteWebDriver`. Como de costumbre, utilizamos
este objeto en la lógica de la prueba para invocar la API de Selenium WebDriver
y controlar el navegador (consulta el repositorio de ejemplos para ver la clase
completa).
Hub-nodos
La arquitectura clásica de Selenium Grid involucra dos tipos de hosts: el hub (es decir, el
Servidor Selenium) y un grupo de nodos. Al igual que en el modo standalone, podemos
utilizar el fat-JAR de Selenium Grid para iniciar este modo en la terminal. Primero,
iniciamos el hub en un host. Luego, registramos uno o más nodos en el mismo host o en
diferentes hosts. El Ejemplo 6-7 muestra la ejecución de estos comandos en una consola
de Windows.
Ejemplo 6-7. Comando para iniciar Selenium Grid en modo hub-nodos usando la
terminal
1. Iniciamos el hub. Por defecto, este servidor escucha las solicitudes HTTP de W3C
WebDriver en el puerto 4444 y en los puertos TCP 4442 y 4443 para registrar
nodos.
2. En una segunda consola, registramos el/los nodo(s). En este ejemplo, este
comando se ejecuta en el mismo host que el hub. Además, se supone que los
controladores necesarios (por ejemplo, chromedriver y geckodriver) ya están
resueltos (como en el Ejemplo 6-5). Para iniciar nodos desde otro host,
necesitaríamos invocar el siguiente comando:
De la misma manera que el modo standalone, puedes iniciar una grid de hub-
nodos usando código Java. Para eso, necesitas cambiar los parámetros para
invocar la clase principal de Selenium Grid siguiendo la misma sintaxis de los
comandos CLI para el hub y los nodos.
Totalmente Distribuido
A partir de la versión 4, podemos ejecutar una infraestructura de Selenium Grid
siguiendo una arquitectura totalmente distribuida. El aspecto decisivo de este enfoque
es la escalabilidad. En este modo, nodos especializados se encargan de diferentes
aspectos de automatización y gestión de infraestructura. Estos nodos son:
Router
Nodo que actúa como un único punto de entrada a la Grid. Este componente
escucha comandos W3C WebDriver desde scripts de Selenium.
Session Queue
Nodo que almacena las nuevas solicitudes de sesión. Estas sesiones entrantes
esperan ser leídas por el Distribuidor.
Distributor
Nodo que está al tanto de todos los nodos y sus capacidades. Solicita nuevas
solicitudes de sesión a la Session Queue a intervalos regulares.
Event Bus
Componente que proporciona un canal de comunicación orientado a mensajes
entre varios miembros de la arquitectura Grid. Esta comunicación se representa con
líneas punteadas en las Figuras 6-4 y 6-5.
Session Map
Mantiene la relación de las sesiones WebDriver y los nodos donde se están
ejecutando las sesiones.
Nodo(s)
Hosts que proporcionan navegadores web (y sus correspondientes controladores)
para la automatización basada en Selenium WebDriver.
En una arquitectura de hub-nodos, el hub agrupa las responsabilidades del
Router, Session Queue, Distributor, Event Bus y Session Map del modo
totalmente distribuido.
Las siguientes subsecciones proporcionan detalles sobre los procesos más relevantes en
un Selenium Grid totalmente distribuido: registro de nodos, nueva sesión y otros
comandos WebDriver.
Registro de Nodos
El primer proceso requerido para operar un Selenium Grid distribuido es registrar uno o
más nodos. Con ese objetivo, los nodos necesitan registrar sus capacidades en el
Distribuidor. La Figura 6-4 ilustra este proceso, compuesto de tres pasos:
1. Un nodo envía un mensaje a través del Event Bus para anunciar sus capacidades.
2. Este mensaje llega al Distribuidor, que almacena la relación entre nodos y
capacidades.
3. El Distribuidor verifica que el nodo existe intercambiando mensajes HTTP (línea
continua) con el nodo fuente.
Figura 6-4. Registro de nodos en una arquitectura distribuida de Selenium Grid
Nueva sesión
En algún momento, un script (típicamente un caso de prueba) intentará iniciar una
nueva sesión para controlar un navegador automáticamente. La Figura 6-5 describe la
comunicación requerida para llevar a cabo este proceso en un Selenium Grid totalmente
distribuido, a saber:
1. Un script/prueba que usa la API de Selenium WebDriver envía una solicitud al Router
para crear una nueva sesión (es decir, para controlar un navegador de forma
programática).
2. El Router crea una nueva entrada en la Session Queue para almacenar esta nueva
solicitud de sesión.
3. El Distribuidor solicita a la Session Queue las nuevas solicitudes de sesión entrantes
en intervalos.
4. Una vez que el Distribuidor descubre una nueva solicitud de sesión, verifica si un nodo
puede soportar esta sesión. Si la sesión es posible (es decir, un nodo previamente
registrado en el Distribuidor ofrece las capacidades requeridas), el Distribuidor crea una
nueva sesión con el nodo.
5. El Distribuidor envía un mensaje HTTP al Session Map para almacenar la nueva sesión.
El Session Map almacena un identificador único de sesión (session id) que asocia de
manera unívoca el nodo que está ejecutando la sesión del navegador.
Figura 6-5. Nueva sesión en una arquitectura distribuida de Selenium Grid
Comandos WebDriver
Una vez que la sesión está establecida, el script de la API de Selenium WebDriver
continuará enviando comandos W3C WebDriver para controlar el navegador web en el
nodo remoto. La Figura 6-6 muestra cómo ocurre esta comunicación en una
infraestructura de Selenium Grid distribuida, siguiendo estos pasos:
1. El script/prueba intercambia comandos W3C WebDriver para controlar el navegador
(por ejemplo, abrir una página web, interactuar con elementos web, etc.) en la sesión
actual.
2. Las solicitudes posteriores a la misma sesión del navegador utilizan el mismo
identificador de sesión (session id). El Router reconoce que una sesión del navegador
está activa al leer el Session Map.
3. El Router reenvía los comandos subsiguientes de la misma sesión directamente al
nodo asignado.
Figura 6-6. Comandos WebDriver en una arquitectura distribuida de Selenium Grid
Configuración de una grid distribuida
Al igual que en los modos standalone y hub-nodes, podemos usar la distribución de
Selenium Grid (como un fat-JAR o una dependencia regular de Java) para iniciar la
arquitectura totalmente distribuida. El Ejemplo 6-8 muestra el comando de shell
necesario para hacer esto usando la línea de comandos.
Ejemplo 6-8. Comando para iniciar Selenium Grid en modo distribuido usando el Shell
1. Iniciamos el Event Bus. Por defecto, el Event Bus escucha en los puertos TCP 4442
y 4443.
2. Iniciamos el Session Map. Por defecto, este componente escucha mensajes HTTP
entrantes en el puerto 5556.
3. Iniciamos el Session Queue. Por defecto, esta cola escucha HTTP en el puerto
5559.
4. Iniciamos el Distributor. Para ello, necesitamos especificar las direcciones del
Session Map y del Session Queue. Además, dado que ya iniciamos el Event Bus
de forma independiente, establecemos la bandera `--bind-bus` en false. Por
defecto, el Distributor usa el puerto 5553 para la comunicación HTTP.
5. Iniciamos el Router. Necesitamos especificar las URL del Session Map, Session
Queue y Distributor.
6. Iniciamos los Nodes. Necesitamos especificar los puertos donde el Event Bus
escucha mensajes de tipo publicador-suscriptor. Además, en este ejemplo,
varios controladores (como chromedriver y geckodriver) están disponibles en la
misma carpeta en la que se ejecuta este comando.
Observabilidad
En ingeniería de software, la observabilidad es una medida que determina el estado
actual de un sistema de software basado en sus salidas o señales externas. De esta
manera, la observabilidad permite comprender el estado interno del sistema
aprovechando sus indicadores externos. La observabilidad puede ser crítica para
mantener sistemas de software complejos y determinar la causa raíz de cualquier
problema. Para ello, los tres pilares de la observabilidad son:
Métricas
Medidas del rendimiento del sistema a lo largo del tiempo, como el tiempo de
respuesta, transacciones por segundo o uso de memoria, por nombrar algunas.
Registros (Logs)
Líneas de texto (generalmente con marcas de tiempo) que un sistema produce al
ejecutar un fragmento de código.
Trazas (Traces)
Representación de eventos distribuidos causalmente relacionados (como registros
seleccionados) que caracterizan el flujo de solicitudes de una operación
determinada en un sistema de software.
Selenium Grid 4 ofrece diferentes características para medir la observabilidad. Primero,
Selenium Grid permite la trazabilidad usando la API de OpenTelemetry. En segundo
lugar, Selenium Grid proporciona un punto de acceso GraphQL para ejecutar consultas
contra la grid.
Trazabilidad con OpenTelemetry
La trazabilidad es una forma esencial de medir la observabilidad basada en los registros
y métricas de un sistema de software. Selenium Grid expone la trazabilidad de dos
maneras. Primero, podemos verificar las trazas de registro al ejecutar una grid desde el
shell. Por defecto, se muestran los registros en el nivel INFO. Podemos cambiar el nivel
usando el argumento `--log-level` en el comando shell, por ejemplo:
Además, Selenium Grid admite la trazabilidad distribuida a través de las API de
OpenTelemetry. Esta característica permite rastrear los comandos que fluyen a través
de una infraestructura de Selenium Grid. La trazabilidad distribuida requiere dos
actividades en este orden:
1. Instrumentación de código
Selenium Grid permite exportar información de trazabilidad usando la API
de OpenTelemetry.
2. Recolección de datos
Por ejemplo, podemos usar Jaeger, una plataforma de trazabilidad
distribuida de código abierto que proporciona una integración perfecta con
OpenTelemetry. Permite consultar, visualizar y recopilar datos de
trazabilidad.
Los siguientes comandos muestran cómo configurar Selenium Grid para exportar datos
a Jaeger. Primero, necesitamos un backend de Jaeger en funcionamiento. Para ello,
podemos descargar los binarios ejecutables desde la página de descarga de Jaeger.
Alternativamente, podemos iniciar el servidor usando Docker, de la siguiente manera:
Usaremos la URL http://localhost:16686 para acceder a la interfaz de usuario de Jaeger.
Usaremos la URL http://localhost:14250 para recopilar los datos (exportados por
Selenium Grid).
Luego, iniciamos Selenium Grid de la siguiente manera:
1. Usamos un endpoint de Jaeger para exportar los datos de trazabilidad.
2. Especificamos el nombre del servicio como `selenium-standalone`.
Buscaremos este nombre en la interfaz de usuario de Jaeger para visualizar
los datos recopilados (ver Figura 6-7).
3. Usamos Coursier para descargar y generar el classpath de dos dependencias
requeridas (`opentelemetry-exporter-jaeger` y `grpc-netty`).
Figura 6-7. Interfaz de usuario de Jaeger mostrando los datos recopilados de Selenium
Grid
Consultas GraphQL
GraphQL es un lenguaje de consulta y manipulación de datos de código abierto para
APIs.
GraphQL define una sintaxis para solicitar datos, generalmente desde un servicio en
línea. Selenium Grid 4 proporciona un endpoint de GraphQL
(http://localhost:4444/graphql). Una forma sencilla de realizar consultas GraphQL a este
endpoint es utilizando `curl` desde el shell. Por ejemplo, y suponiendo que tenemos un
Selenium Grid en ejecución en el localhost, podemos enviar el siguiente comando al
endpoint de GraphQL para obtener el número máximo y el número actual de sesiones
en la grid:
Configuración
Puedes encontrar más detalles sobre Selenium Grid en su documentación oficial. Para
una configuración avanzada, hay dos formas de especificar una configuración
personalizada para Selenium Grid:
Usando opciones de CLI para los diferentes aspectos de Selenium Grid
Algunos ejemplos de estas opciones son `--port` para cambiar el puerto
predeterminado a través del cual escucha el Selenium Server (4444 por defecto), o
`--session-timeout`, que es el tiempo de espera en el que los nodos se terminan
cuando no hay actividad (trescientos segundos por defecto).
Usando archivos TOML
TOML (Tom’s Obvious, Minimal Language) es un formato de configuración diseñado
para ser legible por humanos. Al igual que las opciones de CLI, estos archivos
permiten configurar los parámetros de Selenium Grid pero utilizando la notación
TOML.
Proveedores de Nube
Como se introdujo en el Capítulo 1, un proveedor de nube en el ecosistema de Selenium
es una empresa que ofrece servicios gestionados (generalmente comerciales) para
pruebas automatizadas en la web y móviles. Los servicios comunes ofrecidos por los
proveedores de nube incluyen:
Navegadores como servicio
Para solicitar navegadores web bajo demanda hospedados por los proveedores.
Estos navegadores suelen ser de diferentes tipos, versiones y sistemas
operativos. Esta función se usa típicamente para pruebas automatizadas o en
vivo en múltiples navegadores.
Capacidades de análisis
Para monitorear y depurar pruebas automatizadas. Con este fin, los proveedores
de nube suelen ofrecer grabaciones de sesión o características avanzadas de
informes de errores.
Pruebas móviles
Para solicitar dispositivos móviles emulados (y reales) en diferentes plataformas,
como Android e iOS.
Pruebas visuales
Para inspeccionar automáticamente la interfaz de usuario y asegurar que los
usuarios finales tengan una experiencia visual correcta.
Ejemplos de proveedores de nube actuales para Selenium son Sauce Labs,
BrowserStack, LambdaTest, CrossBrowserTesting, Moon Cloud, TestingBot, Perfecto o
Testinium. Todas estas empresas ofrecen servicios específicos con diferentes planes de
precios. Su aspecto común es que cada proveedor de nube mantiene un endpoint de
Selenium Server que podemos usar en pruebas de RemoteWebDriver. El Ejemplo 6-9
ilustra cómo usar uno de ellos (concretamente, Sauce Labs) para crear un objeto
WebDriver. Puedes encontrar pruebas equivalentes para otros proveedores de nube
(BrowserStack, LambdaTest, CrossBrowserTesting, Perfecto y Testinium) en el
repositorio de ejemplos. Estas pruebas permiten usar navegadores remotos gestionados
por los proveedores de nube.
Ejemplo 6-9. Configuración de prueba para usar Sauce Labs
1. Para usar Sauce Labs, necesitamos una cuenta válida. En otras palabras,
necesitamos credenciales en forma de un nombre de usuario y una clave de
acceso. Para evitar codificar estas credenciales en nuestra lógica de prueba,
utilizo propiedades del sistema de Java en esta prueba. Estas propiedades se
pueden proporcionar en el comando de ejecución (por ejemplo, `mvn test -
DsauceLabsUsername=myname -DsauceLabsAccessKey=mykey`). Una forma
alternativa de especificar estos datos es utilizando variables de entorno (por
ejemplo, `String username = System.getenv("SAUCELABS_USERNAME");`).
2. Omitimos esta prueba (usando suposiciones) cuando el nombre de usuario o
la clave no están disponibles.
3. Debemos incluir el nombre de usuario y la clave como capacidades de
Selenium.
4. Podemos especificar una etiqueta personalizada para identificar esta prueba
en el panel de Sauce Labs (ver Figura 6-8).
5. Usamos la última versión de un navegador dado (Chrome, como se especifica
en la línea siguiente).
6. Usamos una etiqueta personalizada llamada `sauce:options` para seleccionar
las capacidades requeridas en la nube de Sauce Labs.
7. Usamos el endpoint público de Sauce Labs como una URL remota. Sauce Labs
proporciona endpoints en diferentes regiones. En este ejemplo, utilizo el
centro de datos de la UE Central. Otras posibilidades son US West, US East o
Asia-Pacífico Sudeste.
8. Usamos tanto la URL como las capacidades para crear una instancia de
`RemoteWebDriver`.
Figura 6-8. Panel de Sauce Labs mostrando el resultado de una prueba automatizada
Navegadores en Contenedores Docker
Docker es una plataforma de código abierto que permite crear, desplegar y ejecutar
aplicaciones como contenedores ligeros y portátiles. La plataforma Docker está
compuesta por dos componentes principales:
- Docker Engine, una aplicación que permite crear y ejecutar contenedores en un host.
Docker Engine es una aplicación cliente-servidor compuesta por tres elementos:
• Un servidor implementado como un proceso daemon (`dockerd`)
• Una API REST utilizada por los clientes de la aplicación para instruir al daemon
• Una herramienta CLI (el comando `docker`)
- Docker Hub, un servicio en la nube para distribuir contenedores.
En Selenium, Docker puede ser una tecnología relevante para soportar la infraestructura
del navegador requerida para pruebas automatizadas basadas en Selenium WebDriver.
Las siguientes subsecciones explican las alternativas para ejecutar navegadores en
contenedores Docker.
Imágenes Docker para Selenium Grid
Un subproyecto oficial de la suite Selenium es `docker-selenium`. Este proyecto
mantiene imágenes Docker para los diferentes componentes de Selenium Grid (es decir,
standalone, hub, nodes, router, distributor, session queue, etc.) y navegadores web
(Chrome, Firefox y Edge). Estas imágenes Docker son de código abierto y se publican en
Docker Hub. Una forma sencilla de usar estas imágenes es iniciarlas utilizando la shell
(con el comando `docker`) y usar una instancia de `RemoteWebDriver` para controlar
navegadores dockerizados. Las siguientes subsecciones explican cómo hacerlo.
Los comandos y pruebas presentados en esta sección suponen que Docker
está disponible en tu sistema. En otras palabras, necesitas tener instalado
Docker Engine en tu máquina para ejecutar estos ejemplos correctamente.
Standalone
Podemos encontrar imágenes de Selenium para navegadores independientes (Chrome,
Firefox y Edge) en Docker Hub. El siguiente comando muestra cómo iniciar Chrome en
Docker usando la shell:
Este comando inicia la imagen Docker `selenium/standalone-chrome:latest`, es decir, la
versión más reciente de Chrome disponible en Docker Hub. Alternativamente, podemos
usar una versión fija de Chrome (por ejemplo, `selenium/standalone-chrome:94.0`). El
contenedor Docker se inicia en modo detached (usando el flag `-d`) utilizando una
memoria compartida de 2 GB (`--shm-size="2g"`). Este valor es conocido por funcionar
bien, aunque puedes cambiarlo dependiendo de tus recursos o necesidades específicas.
Finalmente, el puerto interno del contenedor 4444 se mapea al mismo puerto del host
donde se ejecuta el comando (`-p 4444:4444`). Luego podemos usar el siguiente
comando Java para instanciar un objeto WebDriver que utilice este Chrome
dockerizado:
Además, al usar Selenium Grid, podemos utilizar contenedores Docker para registrar
nodos. El siguiente comando muestra cómo iniciar un Selenium Grid en modo
independiente utilizando un nodo con Firefox en Docker:
Hub-nodos
Podemos iniciar fácilmente Selenium Grid en el modo hub-nodos utilizando las imágenes
oficiales de Docker de Selenium. Los siguientes comandos muestran cómo hacerlo en la
terminal.
1. Primero, creamos una red Docker llamada grid. Esta red permite la
comunicación entre el hub y los nodos utilizando sus nombres de host (por
ejemplo, selenium-hub).
2. Iniciamos el Selenium Hub. Necesitamos mapear los puertos 4444 (para la
URL del servidor Selenium) y 4442-4443 (para el registro de nodos).
3. Registramos los nodos. En este comando, usamos Chrome (selenium/node-
chrome). Otros navegadores pueden registrarse en el hub utilizando otras
imágenes de Docker (por ejemplo, selenium/node-firefox o selenium/node-
edge).
4. Si ya no es necesario, podemos eliminar la red grid al final.
Características adicionales
El proyecto docker-selenium ofrece una amplia variedad de características. Te
recomiendo que consultes su README para más detalles. Aquí tienes un resumen de
estas características:
Scripts de Docker Compose
Estos scripts permiten iniciar fácilmente Selenium Grid en modo hub-nodos y en
modo completamente distribuido.
Grabación de video
Podemos grabar la sesión de escritorio de los navegadores en los nodos
utilizando otro contenedor Docker.
Grid dinámico
Esto nos permite iniciar contenedores Docker bajo demanda.
Despliegue en Kubernetes
Kubernetes es un sistema de orquestación de contenedores de código abierto
que automatiza el despliegue y la gestión de aplicaciones en contenedores.
Podemos usar Kubernetes para desplegar los contenedores Docker de
Selenium.
Configuración avanzada de contenedores
Esto se puede usar, por ejemplo, para especificar configuraciones
personalizadas de Selenium o Java.
Acceso a la sesión remota
Esto se puede lograr utilizando Virtual Network Computing (VNC) (un sistema
de compartición de escritorio gráfico) y noVNC (un cliente VNC basado en web
de código abierto).
Selenoid
Selenoid es una implementación de código abierto en Golang de un Selenium Hub.
Selenoid puede considerarse un servidor Selenium liviano que proporciona una
infraestructura de navegador basada en Docker. El equipo de Selenoid también
mantiene las imágenes de Docker utilizadas por Selenoid. Estas imágenes incluyen varios
navegadores web y dispositivos Android, como Chrome, Firefox, Edge, Opera, Safari
(motor WebKit) o Chrome Mobile.
Existen diferentes formas de usar Selenoid y sus imágenes Docker. Una forma sencilla
es usar el administrador de configuración (un binario llamado cm) proporcionado por el
proyecto. El siguiente fragmento muestra cómo iniciar Selenoid y su UI (un panel basado
en web para monitorear Selenoid):
1. Iniciamos Selenoid. El administrador de configuración descarga la imagen
Docker para Selenoid y las dos últimas versiones de varios navegadores
(Chrome, Firefox y Opera). Una vez iniciado, Selenoid escucha las solicitudes
de Selenium WebDriver en la URL http://localhost:4444/wd/hub.
2. Opcionalmente, podemos iniciar la UI de Selenoid. Esta UI es una aplicación
web accesible en la URL http://localhost:8080/. La Figura 6-9 muestra una
captura de pantalla de esta UI durante la ejecución de una prueba de
Selenium WebDriver. El Ejemplo 6-10 muestra la configuración de una
prueba que utiliza un navegador Chrome proporcionado por Selenoid.
Figura 6-9. UI de Selenoid durante la ejecución de una prueba usando VNC
Ejemplo 6-10. Instanciación de un objeto RemoteWebDriver utilizando el constructor
1. La capacidad `enableVNC` es específica de Selenoid y nos permite iniciar el
navegador en Docker con soporte para VNC (de esta manera, podemos
visualizar la sesión del navegador en la UI de Selenoid, como se ilustra en la
Figura 6-9).
2. Dado que esta capacidad es específica del proveedor, la forma compatible
con W3C WebDriver para establecer esta capacidad es utilizando un espacio
de nombres personalizado (en este caso, `selenoid:options`).
Características adicionales
Selenoid ofrece diferentes características y capacidades de configuración. Puedes
consultar su documentación para más detalles. Estas características incluyen grabación
de video, configuración personalizada, gestión de logs o acceso a herramientas de
desarrollo del navegador, por nombrar algunas.
WebDriverManager
A partir de la versión 5, WebDriverManager permite el uso sin esfuerzo de navegadores
web en contenedores Docker. Con ese fin, cada gestor (por ejemplo, `chromedriver()`,
`firefoxdriver()`, etc.) proporciona el método `browserInDocker()`. WebDriverManager
descarga internamente las imágenes Docker y ejecuta el contenedor, creando una
instancia de `RemoteWebDriver` al invocar el método `create()`. WebDriverManager
utiliza las imágenes Docker mantenidas por el equipo de Selenoid. De esta manera,
puedes usar Chrome (de escritorio y móvil), Firefox, Edge, Opera y Safari como
contenedores Docker listos para usar a través de WebDriverManager. El Ejemplo 6-11
ilustra una prueba básica usando esta característica.
Ejemplo 6-11. Prueba completa utilizando WebDriverManager y Chrome en Docker
1. Obtenemos una instancia del gestor para Chrome (`chromedriver()`). Luego,
utilizando la API fluida de WebDriverManager, especificamos que los futuros
objetos WebDriver creados con esta instancia (llamada `wmd`) utilizarán
Docker para ejecutar el navegador correspondiente (Chrome, en este caso).
2. Suponemos que un motor Docker está disponible en la máquina que ejecuta
esta prueba. Para ello, creamos una suposición de AssertJ invocando el
método estático `isDockerAvailable` en WebDriverManager. De esta manera,
cuando Docker no está disponible, la prueba se omite.
3. En la configuración de la prueba, creamos la instancia de WebDriver.
Internamente, WebDriverManager se conectará a Docker Hub para descubrir
la última versión de Chrome disponible como una imagen Docker. Esta
imagen se descarga en la máquina local, se ejecuta el contenedor Docker y
se devuelve la instancia correspondiente de `RemoteWebDriver` a la lógica
de prueba.
4. WebDriverManager permite cerrar las instancias de WebDriver previamente
creadas mediante el método `quit()`. Este método tiene el mismo efecto que
cerrar directamente la instancia (`driver.quit()` en este caso), y los
contenedores Docker utilizados se terminan de manera ordenada.
WebDriverManager proporciona una API fluida para configurar diferentes aspectos de
los navegadores web en Docker. El siguiente fragmento muestra varias posibilidades.
Como de costumbre, puedes encontrar las pruebas completas usando estas
características en el repositorio de ejemplos para este libro.
1. Seleccionamos un gestor determinado para usar el navegador dockerizado
correspondiente (Firefox en este caso). Además de Chrome y Firefox, las
otras alternativas son Edge, Opera, Safari y Chrome Mobile.
2. Por defecto, WebDriverManager utiliza la versión más reciente disponible en
Docker Hub para el navegador dockerizado. No obstante, podemos forzar el
uso de una versión específica (por ejemplo, 94.0). Además, diferentes
comodines son válidos para especificar las siguientes versiones, a saber:
latest
Para usar la versión más reciente (opción predeterminada).
latest-N
Para usar una versión anterior a la versión estable. Por ejemplo, si
especificamos `latest-1` (es decir, la versión más reciente menos uno), se
utiliza la versión anterior a la versión estable.
beta
Para usar la versión beta. Esta versión solo está disponible para Chrome
y Firefox, utilizando un fork de las imágenes Docker de Aerokube para
estos navegadores mantenido por Twilio.
dev
Para usar la versión de desarrollo (de nuevo, para Chrome y Firefox).
3. Conéctate a la sesión de escritorio remoto usando VNC o noVNC. Por defecto,
WebDriverManager imprime la URL de noVNC en los registros. Además, esta
URL es accesible invocando el método `wdm.getDockerNoVncUrl()`. La Figura
6-10 muestra un navegador web que permite ver e interactuar con una
sesión remota a través de noVNC.
4. Para habilitar la grabación de la sesión. Al final de la prueba, puedes
encontrar la grabación (en formato MP4) en la carpeta raíz del proyecto.
Figura 6-10. Escritorio remoto usando noVNC de un navegador dockerizado iniciado con
WebDriverManager
Características adicionales
Como se explica en su documentación, puedes configurar WebDriverManager de
múltiples maneras. Por ejemplo, puedes especificar aspectos detallados de los
navegadores dockerizados, como la zona horaria, red, memoria compartida, volúmenes,
variables de entorno, resolución de pantalla o salida de grabación, entre otros. Además,
WebDriverManager puede usarse como un servidor Selenium. Este servidor utiliza las
imágenes de contenedor descargadas de Docker Hub para soportar la infraestructura
del navegador.
Selenium-Jupiter
Selenium-Jupiter utiliza internamente WebDriverManager para gestionar y manejar
navegadores web en contenedores Docker. Para navegadores dockerizados, Selenium-
Jupiter proporciona la anotación `@DockerBrowser`. Puedes usar esta anotación con
parámetros `WebDriver` o `RemoteWebDriver` en métodos de prueba. El Ejemplo 6-12
demuestra esta característica. En este ejemplo, usamos Chrome en Docker.
Ejemplo 6-12. Prueba completa utilizando Selenium-Jupiter y Chrome en Docker
1. Decoramos la clase de prueba con la anotación de Selenium-Jupiter
`@EnabledIfDockerAvailable`. Esta anotación desactiva la prueba cuando Docker
no está instalado en la máquina que ejecuta la prueba.
La anotación `@DockerBrowser` permite configurar diferentes aspectos y
características. El siguiente fragmento ilustra algunos de ellos:
1. Podemos cambiar el navegador usando el atributo `type`. Los valores aceptados
son `CHROME`, `FIREFOX`, `OPERA`, `EDGE`, `SAFARI` y `CHROME_MOBILE`.
2. Podemos cambiar la versión del navegador usando el atributo `version`. Al igual
que WebDriverManager, Selenium-Jupiter permite especificar un valor de
versión fijo (por ejemplo, 94.0) y usar los comodines `latest` y `latest-N`, así como
`beta` y `dev` para Chrome y Firefox.
3. Habilitamos el acceso a la sesión de escritorio remoto a través de VNC y noVNC
usando el atributo `vnc`.
4. Habilitamos la grabación de la sesión con el atributo `recording`.
Puedes encontrar más detalles, ejemplos y capacidades de configuración de Selenium-
Jupiter en su documentación.
Resumen y Perspectivas
Selenium WebDriver permite controlar navegadores web remotos. Esta característica es
posible porque el protocolo de comunicación subyacente (W3C WebDriver) se basa en
mensajes JSON sobre HTTP. De esta manera, los componentes de la arquitectura de
Selenium WebDriver (Selenium Server, nodos o scripts cliente) pueden ser distribuidos
(es decir, ejecutados en diferentes hosts). Para usar esta característica en Java,
necesitamos crear una instancia de `RemoteWebDriver`, generalmente pasando dos
argumentos: la URL del Selenium Server y las capacidades requeridas. Podemos iniciar
una infraestructura de Selenium Server usando Selenium Grid (en modo independiente,
hub-nodes o completamente distribuido). Alternativamente, podemos usar los servicios
gestionados proporcionados por un proveedor de nube (como Sauce Labs,
BrowserStack, LambdaTest o CrossBrowserTesting, entre otros). Finalmente, podemos
usar Docker para soportar una infraestructura de navegadores web en contenedores.
Este capítulo concluye la segunda parte del libro, en la que has descubierto las
principales características de la API de Selenium WebDriver. La siguiente parte del libro
cubre diferentes aspectos del desarrollo de pruebas end-to-end utilizando la API de
Selenium WebDriver, comenzando con el Page Object Model (POM), un patrón de
diseño ampliamente utilizado para mejorar el mantenimiento de pruebas y reducir la
duplicación de código en Selenium WebDriver.
PARTE III
Conceptos Avanzados
Esta última parte cubre diferentes aspectos y casos de uso basados en la API de Selenium
WebDriver. Primero, aprenderás sobre el Page Object Model (POM), un patrón de
diseño ampliamente utilizado que permite el desarrollo de pruebas de WebDriver
reutilizables y mantenibles. El siguiente capítulo explica diferentes técnicas para
pruebas robustas entre navegadores, como pruebas parametrizadas, orden de pruebas
o ejecución paralela de pruebas. El capítulo siguiente describe cómo usar bibliotecas y
marcos de terceros en conjunto con Selenium WebDriver, como Cucumber o el
Framework Spring, entre otros. El capítulo final resume varias bibliotecas
complementarias a Selenium WebDriver, como Appium o REST Assured. Para concluir,
descubrirás las principales características de las alternativas actuales a Selenium, como
Cypress, WebDriverIO, TestCafe, Puppeteer y Playwright.
CAPÍTULO 7
El Modelo de Objetos de Página (POM)
Un patrón de diseño es una solución reutilizable para un problema recurrente en la
ingeniería de software. Este capítulo presenta el Modelo de Objetos de Página (POM),
un patrón de diseño popular utilizado para desarrollar pruebas con Selenium
WebDriver. El uso de POM ofrece diferentes beneficios, como mejorar la reutilización y
evitar la duplicación de código. POM se basa en crear clases de página para modelar la
interfaz de usuario del SUT en un único repositorio, que luego se utiliza desde la lógica
de prueba.
Motivación
Algunos de los mayores desafíos al desarrollar pruebas end-to-end con Selenium
WebDriver son la mantenibilidad y la inestabilidad. Respecto a la mantenibilidad, el
problema puede surgir durante el desarrollo o evolución del SUT. Los cambios realizados
en la interfaz de usuario pueden hacer que las pruebas end-to-end existentes se
rompan. Los costos de mantenimiento para corregir estas pruebas pueden ser
relevantes cuando se tiene una gran suite de pruebas en la que existe duplicación de
código en varios casos de prueba (por ejemplo, cuando los mismos localizadores se
utilizan repetidamente en diferentes pruebas).
En cuanto a la inestabilidad (es decir, la falta de fiabilidad), una prueba es inestable
cuando tiene un comportamiento inconsistente, es decir, pasa y falla periódicamente
bajo las mismas condiciones (lógica de prueba, datos de entrada, configuración, etc.).
Hay dos causas principales de la inestabilidad en las pruebas de Selenium WebDriver.
Primero, la raíz del problema puede ser el SUT. Por ejemplo, un error en la lógica del
lado del servidor (por ejemplo, una condición de carrera) puede exponer un
comportamiento errático en las pruebas end-to-end. En este caso, los desarrolladores y
probadores deben trabajar juntos para detectar y resolver el problema, generalmente
corrigiendo errores del lado del servidor. En segundo lugar, la causa podría estar en la
propia prueba. Esta es una situación no deseada que los probadores deben evitar.
Existen diferentes estrategias para prevenir la inestabilidad en las pruebas de Selenium
WebDriver, como implementar una estrategia robusta de localización (para evitar
pruebas frágiles debido a cambios en la capacidad de respuesta o en la vista) o utilizar
una estrategia de espera (para manejar la naturaleza distribuida y asíncrona de las
aplicaciones web, como se explica en “Estrategias de Espera” en la página 94).
Utilizar un patrón de diseño como POM puede ayudar a reducir la duplicación de código
y mejorar los problemas de mantenibilidad. Además, podemos usar POM para incluir
estrategias de localización y espera robustas y reutilizables. La siguiente sección describe
cómo llevar a cabo el patrón de diseño POM.
El patrón de diseño POM en sí mismo no es estrictamente una solución para la
inestabilidad de las pruebas. Sin embargo, como se explica en las secciones
siguientes, permite la encapsulación de código reutilizable que previene la falta
de fiabilidad de las pruebas.
El Patrón de Diseño POM
El principio del patrón de diseño POM es separar la lógica para manejar los elementos
de la interfaz de usuario en clases separadas (llamadas clases de página) de la lógica de
prueba. En otras palabras, modelamos la apariencia y el comportamiento de nuestro
SUT siguiendo un paradigma orientado a objetos, es decir, como objetos de página.
Luego, estos objetos de página son utilizados por las pruebas de Selenium WebDriver.
Veamos un ejemplo simple para ilustrar POM. Considera la Figura 7-1, que contiene un
formulario de inicio de sesión. Como es habitual, esta página se encuentra en el sitio de
práctica. El Ejemplo 7-1 muestra un caso de prueba usando Selenium WebDriver
estándar. En programación, usamos el término “vanilla” para referirnos a la tecnología
utilizada sin personalización desde la forma original. En este caso, utilizamos la API
estándar de Selenium WebDriver, explicada en la Parte II de este libro.
Figura 7-1. Página web de práctica con un formulario de inicio de sesión
Ejemplo 7-1. Prueba usando Selenium WebDriver estándar para implementar un inicio
de sesión exitoso
1. Escribimos la palabra "user" como nombre de usuario en el formulario web.
2. Escribimos la misma palabra como contraseña en el formulario web.
3. Hacemos clic en el botón de Enviar.
4. Verificamos que la caja de éxito se muestre.
Esta prueba es completamente correcta, pero podría surgir un problema si
implementamos pruebas adicionales utilizando la misma página web. Por ejemplo, el
Ejemplo 7-2 muestra otro caso de prueba utilizando Selenium WebDriver estándar para
implementar una prueba negativa (un inicio de sesión fallido) usando el mismo
formulario web. Esta prueba, nuevamente, es válida, pero junto con el Ejemplo 7-1,
duplicamos la mayor parte de la lógica para localizar elementos web, solo cambiando
los datos de entrada y el resultado esperado. Este enfoque viola uno de los principios
más importantes en el diseño de software: "No te repitas" (DRY, por sus siglas en inglés).
Esto es problemático, ya que utilizar el mismo código en diferentes lugares dificulta el
mantenimiento.
Ejemplo 7-2. Prueba usando Selenium WebDriver estándar para implementar un inicio
de sesión fallido
Objetos de Página (Page Objects)
Las clases de objetos de página permiten la separación del código dedicado a la interfaz
de usuario, como los localizadores y el diseño de la página, de la lógica de prueba.
Podemos ver las clases de página como un único repositorio que encapsula las
operaciones o servicios proporcionados por la aplicación bajo prueba. Estas clases se
instancian como objetos de página en diferentes casos de prueba. Podemos
implementar pruebas de extremo a extremo utilizando los métodos expuestos en estos
objetos, evitando así la repetición de código.
Aquí hay un ejemplo básico usando objetos de página. En el siguiente ejemplo,
refactorizamos la prueba explicada en la sección anterior (es decir, usando el formulario
de inicio de sesión) utilizando un objeto de página en lugar de Selenium WebDriver
estándar. El primer paso es crear una clase Java que modele la página de inicio de sesión.
El **Ejemplo 7-3** muestra una implementación muy básica de esta clase de página.
Ejemplo 7-3. Clase de página básica para modelar el formulario de inicio de sesión de
práctica
1. Declaramos un atributo de clase **WebDriver**. Esta variable se utiliza en el
objeto de página para implementar la interacción con la página web.
2. Declaramos todos los localizadores necesarios como atributos adicionales. En
este caso, localizamos el campo de texto para el nombre de usuario y la
contraseña, el botón de enviar y la caja de éxito.
3. El constructor definido por esta clase de página acepta el objeto **WebDriver**.
Usamos el constructor para cargar la página que se va a probar.
4. Declaramos un método para modelar la interacción necesaria para iniciar sesión,
es decir, escribir el nombre de usuario y la contraseña, y hacer clic en el botón
de enviar.
5. Declaramos otro método para verificar si la caja de éxito es visible.
Ahora, podemos utilizar esta clase de página en un caso de prueba. El **Ejemplo 7-4**
ilustra cómo hacerlo. Observa que creamos una instancia de **WebDriver**, como de
costumbre, antes de cada prueba y la cerramos después de cada prueba. Usamos este
controlador como argumento en el constructor de la clase de página.
Ejemplo 7-4. Prueba utilizando la clase de página básica para implementar un inicio de
sesión exitoso
1. Declaramos la clase de página como un atributo en la clase de prueba.
2. Creamos el objeto de la página, pasando la instancia de **WebDriver**.
3. Invocamos el método definido por la clase de página para realizar la operación
de inicio de sesión.
4. Verificamos que la caja de éxito esté disponible en la página web resultante
utilizando un método proporcionado por el objeto de la página.
Este enfoque es un buen comienzo para mejorar la mantenibilidad de nuestras pruebas
porque ahora toda la lógica relacionada con la página de inicio de sesión está
centralizada como una clase reutilizable. No obstante, el código en las clases de página
aún es frágil. Por ejemplo, imagina que necesitamos implementar una prueba negativa
para la página de inicio de sesión, es decir, un intento de inicio de sesión utilizando
credenciales incorrectas. El **Ejemplo 7-5** parece una forma razonable de hacerlo,
dada la implementación actual de la clase de página. Sin embargo, si ejecutas esta
prueba, descubrirás que falla debido a una excepción **NoSuchElementException**. La
siguiente sección explica cómo resolver este problema potencial creando objetos de
página más robustos.
Ejemplo 7-5. Prueba utilizando la clase de página básica para implementar un inicio de
sesión fallido
Objetos de Página Robustos
El ejemplo presentado en las secciones anteriores mejora la mantenibilidad del código,
ya que las operaciones de la página están encapsuladas en una sola clase en lugar de
estar dispersas por toda la suite de pruebas. Dicho esto, existen diferentes formas de
mejorar la implementación de las clases de página anteriores. En primer lugar, es
probable que nuestro SUT (Sistema Bajo Prueba) tenga varias páginas web, no solo una.
Por esta razón, una estrategia común es seguir un enfoque orientado a objetos y crear
una clase base de página que encapsule la lógica común para todas las clases de página.
Ejemplo 7-6 muestra una clase Java que implementa una base típica para clases de
página.
Ejemplo 7-6. Ejemplo de una clase base para clases de página
1. Definimos un atributo de espera explícita (`WebDriverWait`) en la clase base.
Instanciamos este atributo en el constructor utilizando un valor predeterminado
de tiempo de espera (cinco segundos en este ejemplo).
2. Creamos un método setter para cambiar el valor predeterminado del tiempo de
espera. Por ejemplo, podríamos necesitar ajustar este tiempo de espera
dependiendo del tiempo de respuesta del sistema.
3. Creamos varios métodos comunes que las clases de página pueden reutilizar,
como `visit()` (para abrir una página web), `find()` (para localizar un elemento
web) o `type()` (para enviar datos a un elemento editable, como un campo de
entrada).
4. Implementamos un método para verificar si un elemento web está visible o no.
Nota que este método oculta la complejidad de esperar por este elemento,
devolviendo un valor booleano simple que las pruebas pueden utilizar.
Usamos la clase base anterior como la clase padre de clases de página específicas. Por
ejemplo, el Ejemplo 7-7 muestra una clase Java que extiende esta base para
implementar la clase de página, utilizando la página de muestra de inicio de sesión en el
sitio de práctica.
Ejemplo 7-7. Clase de página de inicio de sesión usando la base anterior
1. Definimos los localizadores de la página como atributos de la clase.
2. Definimos un constructor con dos parámetros: el objeto `WebDriver` y el valor
de tiempo de espera (en segundos).
3. Definimos otro constructor que abre la página web en prueba.
4. Incluimos un método para iniciar sesión utilizando el nombre de usuario y la
contraseña como credenciales. Esto utiliza los métodos definidos en la clase
padre (`type()` y `click()`).
5. Incluimos otro método para verificar si la caja de éxito es visible o no (usando el
método `isDisplayed()` definido en la clase base).
Finalmente, podemos usar la clase de página para implementar una prueba de Selenium
WebDriver. El Ejemplo 7-8 muestra una prueba usando JUnit 5 (como de costumbre,
puedes encontrar las versiones de JUnit 4, TestNG y Selenium-Jupiter en el repositorio
de ejemplos).
Ejemplo 7-8. Prueba usando la clase de página para implementar un inicio de sesión
exitoso y fallido
1. Instanciamos el objeto de la página antes de cada prueba.
2. Dado que la lógica de la clase de la página es robusta, podemos invocar
`successBoxPresent()` para implementar una prueba negativa. Este método
implementa internamente una espera explícita para el elemento web, que
eventualmente devuelve `false` cuando la caja de éxito no se muestra.
Creación de un Lenguaje Específico de Dominio (DSL)
Podemos llevar las cosas un poco más allá en nuestro proceso de modelar nuestro SUT
y crear un Lenguaje Específico de Dominio (DSL) completo utilizando las clases de
página. En informática, un DSL es un lenguaje especializado para un dominio particular.
Al usar el POM y Selenium WebDriver, podemos ver un DSL como la encapsulación de
todas las operaciones y servicios del SUT en los métodos proporcionados por las clases
de página. De esta manera, los casos de prueba utilizan una API simple y legible
proporcionada por las clases de página. Estas clases encapsulan todas las llamadas a la
API de Selenium WebDriver para interactuar con el SUT.
Continuando con el ejemplo mostrado en las secciones anteriores, el Ejemplo 7-9
muestra una clase de página base para la página de inicio de sesión siguiendo un
enfoque DSL. Esta clase base es bastante similar al Ejemplo 7-6, pero en este caso, esta
clase también encapsula la lógica necesaria para crear una instancia de WebDriver.
Ejemplo 7-9. Ejemplo de clase base siguiendo un enfoque DSL
1. Declaramos un parámetro de tipo `String` en el constructor base. Esta cadena
será el nombre del navegador (especificado en las pruebas).
2. Usamos `WebDriverManager` para resolver el controlador requerido y crear la
instancia de `WebDriver`. Como se explica en "Generic Manager" en la página
353, `WebDriverManager` permite el uso de un administrador parametrizado
invocando el método `getInstance()`. En este caso, usamos el nombre del
navegador (por ejemplo, chrome, firefox, etc.) para seleccionar el administrador.
3. También encapsulamos el método para terminar la sesión y cerrar el navegador.
El Ejemplo 7-10 muestra la clase de página que extiende esta base. Como se puede ver,
la única diferencia con el Ejemplo 7-7 es que esta clase de página utiliza un parámetro
de cadena (el nombre del navegador) en el constructor.
Ejemplo 7-10. Clase de página de inicio de sesión siguiendo un enfoque DSL
Finalmente, el Ejemplo 7-11 muestra la prueba resultante. Observa que esta prueba no
contiene ninguna llamada directa a Selenium WebDriver o WebDriverManager. La clase
de página encapsula todos los detalles de bajo nivel de la interacción con el navegador,
exponiendo una API de alto nivel y legible que se utiliza en la prueba.
Ejemplo 7-11. Caso de prueba usando POM y siguiendo un enfoque DSL
1. Instanciamos el objeto de la página, simplemente especificando el tipo de
navegador que se utilizará (Chrome en este caso).
2. Como de costumbre, terminamos la sesión del navegador después de cada
prueba, pero esta vez utilizando un método proporcionado por el objeto de la
página.
Factory Page
Page Factory es el nombre dado a varias clases de soporte proporcionadas por la API de
Selenium WebDriver para facilitar la implementación de clases de objetos de página. Las
más relevantes de estas clases de soporte son:
FindBy
Anotación utilizada a nivel de atributo para identificar elementos web en una
página.
FindAll
Anotación que permite componer diferentes localizadores @FindBy.
PageFactory
Clase utilizada para inicializar todos los elementos web previamente declarados
con @FindBy (y @FindAll).
CacheLookup
Un inconveniente de usar la anotación @FindBy para localizar elementos web es
que, a medida que se utiliza cada localizador, el driver intentará encontrarlo en
la página actual. Esta característica es útil en aplicaciones web dinámicas. Sin
embargo, sería deseable almacenar en caché los elementos web en aplicaciones
web estáticas. Por esta razón, la anotación @CacheLookup permite almacenar
en caché los elementos web una vez que se localizan, mejorando el rendimiento
de las pruebas resultantes.
El Ejemplo 7-12 muestra una clase de página que utiliza estas clases de soporte de
Selenium WebDriver. Puedes encontrar la prueba resultante usando esta clase de
página en el repositorio de objetos. Esta prueba es equivalente al Ejemplo 7-11, pero
usa `FactoryLoginPage` en lugar de `ExtendedLoginPage` para la interacción con la
página de inicio de sesión.
Ejemplo 7-12. Clase que usa la Page Factory proporcionada por Selenium WebDriver
1. Declaramos los elementos web en la página utilizando el tipo `WebElement`
decorado con dos anotaciones:
@FindBy
Para especificar el localizador (por id y css en este ejemplo).
@CacheLookup
Para almacenar en caché los resultados de la localización del elemento
web (dado que la página web es estática y su contenido no cambiará en
diferentes llamadas).
2. Invocamos el método `initElements` para localizar los elementos web utilizando
la instancia de WebDriver.
El enfoque de Page Factory solo se recomienda cuando la página web probada
con Selenium WebDriver es estática. Este enfoque puede llevar a efectos
indeseables, como elementos web obsoletos (es decir, elementos antiguos o
que ya no están disponibles) cuando se utilizan páginas web dinámicas.
Resumen y Perspectivas
Este capítulo proporcionó una visión general completa del Page Object Model (POM) en
las pruebas de Selenium WebDriver. POM es un patrón de diseño en el que se separa la
lógica para interactuar con las páginas web y el código de prueba. De esta manera, las
clases de página contienen la lógica relacionada con los localizadores web y el diseño de
la página, y las clases de prueba determinan cómo ejercitar y verificar el SUT (sistema
bajo prueba). El patrón POM mejora la mantenibilidad de los conjuntos de pruebas
basados en Selenium WebDriver, ya que las clases de página se almacenan en un único
repositorio que modela el SUT. Este repositorio se usa posteriormente en diferentes
casos de prueba. Podemos crear páginas web robustas utilizando estrategias adecuadas
de localización y espera.
El siguiente capítulo presenta aspectos específicos de los marcos de pruebas unitarias
utilizados (JUnit, TestNG y Selenium-Jupiter) para mejorar el proceso de prueba general
con Selenium WebDriver. Estas características permiten crear pruebas parametrizadas
(para pruebas cruzadas de navegadores), categorizar pruebas (para filtrado de pruebas),
ordenar y reintentar pruebas, o ejecutar pruebas en paralelo.
CAPÍTULO 8
Especificidades de los Marcos de Pruebas
En los ejemplos presentados a lo largo de este libro, he recomendado incrustar las
llamadas a la API de Selenium WebDriver en métodos de Java decorados con la
anotación `@Test` usando diferentes marcos de pruebas unitarias: JUnit 4, JUnit 5 (solo
o extendido con Selenium-Jupiter), o TestNG. Al ejecutar pruebas regulares, la diferencia
en el uso de uno u otro marco de pruebas es mínima. No obstante, cada marco de
pruebas tiene características específicas para diferentes casos de uso. Este capítulo
resume algunas de estas características para implementar pruebas con Selenium
WebDriver. Como de costumbre, puedes encontrar el código fuente para este capítulo
en el repositorio de ejemplos de este libro. Puedes usar estos ejemplos para comparar
y elegir el marco de pruebas unitarias más conveniente para tus necesidades específicas.
Pruebas Parametrizadas
Una característica ampliamente soportada por los marcos de pruebas unitarias es la
creación de pruebas parametrizadas. Esta característica permite la ejecución de pruebas
múltiples veces utilizando diferentes parámetros. Aunque podemos implementar
pruebas parametrizadas tanto con JUnit (4 y 5) como con TestNG, existen diferencias
significativas entre cada implementación.
JUnit 4
Necesitamos usar un ejecutor de pruebas llamado `Parameterized` para implementar
pruebas parametrizadas en JUnit 4. Un ejecutor de pruebas en JUnit 4 es una clase de
Java responsable de ejecutar pruebas. Decoramos una clase de Java utilizando la
anotación `@RunWith` de JUnit 4 para especificar un ejecutor de pruebas. Luego,
necesitamos usar la anotación `@Parameters` de JUnit 4 para decorar el método que
proporciona los parámetros de prueba. Hay dos formas de inyectar estos parámetros en
la clase de prueba: en el constructor de la clase de prueba o como atributos de la clase
decorados con la anotación `@Parameter`. El Ejemplo 8-1 muestra un caso de prueba
donde los parámetros de prueba se inyectan usando la segunda técnica. Este ejemplo
ejecuta la misma prueba para iniciar sesión en el sitio de práctica utilizando diferentes
credenciales (nombre de usuario y contraseña). Como resultado, el mensaje
proporcionado por la página web es diferente (inicio de sesión exitoso o credenciales
inválidas).
Ejemplo 8-1. Prueba parametrizada usando JUnit 4
1. Especificamos el ejecutor de pruebas `Parameterized` para esta clase de Java.
2. Inyectamos tres parámetros de prueba como atributos de la clase: nombre de
usuario (índice 0), contraseña (índice 1) y texto esperado (índice 2).
3. Especificamos el parámetro de prueba en un método que devuelve una colección
de parámetros genéricos (`Collection<Object[]>`).
4. Devolvemos una colección de los tres conjuntos de `String` que se utilizarán
como parámetros de prueba. Los valores de cada entrada serán inyectados
utilizando los tres parámetros previamente declarados (nombre de usuario,
contraseña y texto esperado).
5. En la lógica de la prueba (que se ejecutará dos veces, una por entrada de datos),
intentamos iniciar sesión en el sitio de práctica utilizando el nombre de usuario
y la contraseña proporcionados como parámetros.
6. Afirmamos que los datos esperados (que son diferentes dependiendo de las
credenciales proporcionadas como parámetro) están disponibles en el cuerpo de
la página.
Una de las limitaciones más significativas de JUnit 4 es que solo se puede
usar un ejecutor de pruebas por clase de Java. En otras palabras, los
ejecutores de pruebas no son composables en JUnit 4. Para superar esta
restricción (entre otras), el equipo de JUnit lanzó JUnit 5 en 2017.
TestNG
Podemos usar la anotación `@DataProvider` para decorar el método que proporciona
los parámetros de prueba en una prueba parametrizada de TestNG. Como se puede ver
en el Ejemplo 8-2, este método devuelve un array bidimensional de objetos generales
de Java. La anotación `@DataProvider` debe proporcionar un nombre como atributo.
Este nombre se usa más tarde en el método `@Test` para especificar el proveedor de
datos. Finalmente, los parámetros se inyectan en el método de prueba.
Ejemplo 8-2. Prueba parametrizada usando TestNG
1. Creamos un método que se utiliza como proveedor de datos.
2. Especificamos que esta prueba usará el proveedor de datos anterior al que
llamamos `loginData`.
3. Una diferencia notable entre JUnit 4 y TestNG en cuanto a las pruebas
parametrizadas es que los parámetros (nombre de usuario, contraseña y texto
esperado en este ejemplo) se inyectan en TestNG como parámetros del método
de prueba.
JUnit 5
Jupiter (el modelo de programación y extensión de JUnit 5) proporciona un mecanismo
poderoso para crear pruebas parametrizadas. En resumen, necesitamos dos elementos
para implementar estas pruebas en JUnit 5:
- Un proveedor de argumentos, que es la fuente de datos para las pruebas
parametrizadas. La Tabla 8-1 ofrece un resumen completo de estos proveedores de
argumentos.
- La anotación `@ParameterizedTest` (en lugar de la usual anotación `@Test`), que
decora el método de prueba donde se inyectan los parámetros.
Tabla 8-1. Proveedores de argumentos en JUnit 5
Ejemplo 8-3 ilustra la versión de JUnit Jupiter del mismo test parametrizado mostrado
en los ejemplos anteriores. Podemos usar diferentes proveedores de argumentos para
implementar este test parametrizado. En este caso, usamos `@MethodSource` para
devolver un flujo de argumentos. Una alternativa que podría encajar bien para este test
es usar `@CsvSource` para incrustar los datos de entrada y el resultado esperado en
formato CSV.
Ejemplo 8-3. Test parametrizado usando JUnit 5
1. Definimos un método estático para ser usado como proveedor de argumentos
en `@MethodSource`.
2. En lugar de un `@Test` regular, implementamos un test parametrizado.
3. El proveedor de argumentos está vinculado a los datos proporcionados por el
método `loginData`.
4. Los parámetros se inyectan en el método de prueba.
Selenium-Jupiter
Puedes usar el mismo enfoque para implementar tests parametrizados en JUnit 5
cuando uses Selenium-Jupiter. La única diferencia es que delegas la creación y
eliminación de objetos WebDriver a Selenium-Jupiter. El **Ejemplo 8-4** demuestra
cómo implementar el mismo test explicado en las secciones anteriores (es decir, un
inicio de sesión parametrizado), pero usando Selenium-Jupiter.
Ejemplo 8-4. Prueba parametrizada utilizando JUnit 5 con Selenium-Jupiter
1. Cuando se utilizan diferentes resolutores de parámetros en una prueba de
Jupiter, por convención, primero debemos declarar los parámetros inyectados
debido a @ParameterizedTest, y luego el parámetro inyectado por las
extensiones (en este caso, Selenium-Jupiter para los objetos WebDriver).
Pruebas en múltiples navegadores
Las pruebas en múltiples navegadores son un tipo de prueba funcional en la que
verificamos que una aplicación web funcione como se espera en diferentes tipos de
navegadores web. Una forma de implementar pruebas en múltiples navegadores es a
través de pruebas parametrizadas, utilizando el tipo de navegador (es decir, Chrome,
Firefox, Edge, etc.) como el parámetro de prueba. Las siguientes secciones describen
cómo utilizar las capacidades del marco de pruebas unitarias para realizar pruebas
parametrizadas aplicadas a pruebas en múltiples navegadores. En estos ejemplos,
utilizaremos navegadores locales (Chrome, Firefox y Edge). Una forma alternativa de
realizar pruebas en múltiples navegadores es utilizar navegadores remotos (desde un
servidor Selenium, proveedor en la nube o Docker), como se explica en el Capítulo 6.
JUnit 4
El Ejemplo 8-5 muestra una prueba en múltiples navegadores implementada con JUnit
4. Utilizamos WebDriverManager para facilitar la parametrización. Como se explica en
“Generic Manager” en la página 353, WebDriverManager puede usar un administrador
u otro dependiendo del valor de un parámetro. Este parámetro puede ser una clase
WebDriver, una enumeración o el nombre del navegador. En los siguientes ejemplos
utilizamos este último (aunque puedes encontrar los métodos alternativos en el
repositorio de ejemplos).
Ejemplo 8-5. Pruebas en múltiples navegadores usando JUnit 4
1. Especificamos tres navegadores usando sus nombres.
2. Utilizamos el administrador genérico de WebDriverManager, usando estos
nombres de navegadores como parámetros. Una forma alternativa de
seleccionar un navegador u otro es utilizando el administrador genérico sin
parámetros (es decir, con el método `.getInstance()`, como se explica en
“Generic Manager” en la página 353) y luego parametrizar la prueba (o la suite)
usando la propiedad del sistema Java `wdm.defaultBrowser` (por ejemplo, al
ejecutarlo con Maven o Gradle).
3. Esta prueba se ejecuta tres veces, usando un navegador diferente (Chrome, Edge
y Firefox) cada vez.
TestNG
El Ejemplo 8-6 muestra la misma prueba en múltiples navegadores, esta vez usando
TestNG. En este caso, el parámetro de prueba (el nombre del navegador) se inyecta en
el método de prueba.
Ejemplo 8-6. Pruebas en múltiples navegadores usando TestNG
Necesitamos crear la instancia de WebDriver en la lógica de la prueba, ya que los
parámetros de prueba se inyectan en el método de prueba cuando se usa TestNG.
JUnit 5
El Ejemplo 8-7 muestra la misma prueba en múltiples navegadores siguiendo el modelo
de Jupiter. Nuevamente, usamos WebDriverManager para crear la instancia de
WebDriver, utilizando el nombre del navegador como parámetro. Dado que estos
parámetros son cadenas, usamos @ValueSource como un proveedor de argumentos.
Ejemplo 8-7. Pruebas en múltiples navegadores usando JUnit 5
En Jupiter, los parámetros en las pruebas parametrizadas se inyectan en los métodos de
prueba. Por esta razón, necesitamos crear la instancia del driver en la lógica de la
prueba.
Selenium-Jupiter
Selenium-Jupiter ofrece una función complementaria para crear pruebas en múltiples
navegadores, llamada plantillas de prueba. Las plantillas de prueba son un tipo especial
de prueba parametrizada compatible con Jupiter, en la cual una extensión recopila los
parámetros. Selenium-Jupiter utiliza esta función para proporcionar una forma
completa de especificar diferentes aspectos del navegador (como tipo, versión,
argumentos y capacidades) usando una notación JSON personalizada llamada escenario
del navegador en la jerga de Selenium-Jupiter. Puedes encontrar más detalles sobre esta
función en la documentación de Selenium-Jupiter.
El Ejemplo 8-8 muestra un ejemplo de escenario de navegador. Este JSON se almacena
en un archivo llamado `browsers.json`, el nombre predeterminado utilizado por una
prueba de plantilla. El Ejemplo 8-9 muestra una prueba de plantilla utilizando este
escenario de navegador.
Ejemplo 8-8. Escenario de navegador para una plantilla de prueba en Selenium-Jupiter
Este escenario de navegador contiene tres navegadores. El primero es un Chrome local.
El segundo navegador es un Edge local en modo sin cabeza (headless). El tercer
navegador es Firefox 93, ejecutado en un contenedor Docker.
Ejemplo 8-9. Pruebas en múltiples navegadores usando plantillas de prueba en JUnit 5
con Selenium-Jupiter
1. Utilizamos esta anotación de Selenium-Jupiter para omitir la prueba cuando
Docker no está disponible (ya que uno de los navegadores definidos en el
escenario usa Docker).
2. Necesitamos decorar el método de prueba usando `@TestTemplate` en lugar de
la anotación habitual `@Test`.
3. Utilizamos el `WebDriver` genérico para inyectar las instancias del controlador.
Alternativamente, `RemoteWebDriver` también es válido para plantillas de
prueba.
Categorización y Filtrado de Pruebas
Una necesidad común al basar una suite de pruebas en Selenium WebDriver
(especialmente cuando el número de pruebas es alto) es ejecutar solo un grupo de
pruebas. Hay diferentes formas de lograr la ejecución de pruebas individuales o en
grupo. Al usar un IDE para ejecutar pruebas, podemos seleccionar las pruebas
específicas que se ejecutarán. Al usar la línea de comandos, hay otros mecanismos que
podemos usar para seleccionar estas pruebas.
A primera vista, podemos utilizar los mecanismos de filtrado proporcionados por las
herramientas de construcción. Por ejemplo, Maven y Gradle permiten incluir o excluir
pruebas en función de las clases de prueba y los nombres de los métodos. La sintaxis
básica para estos comandos se introduce en el Apéndice C. La Tabla 8-2 muestra varios
ejemplos comunes utilizando estos comandos. Ten en cuenta que en estos ejemplos se
usa el comodín `*` para coincidir con cualquier carácter en el nombre de la clase de
prueba.
Tabla 8-2. Ejemplos de comandos de Maven y Gradle para incluir y excluir pruebas
Además de la herramienta de compilación, podemos usar características integradas
proporcionadas por los marcos de pruebas unitarias para categorizar (también conocido
como agrupar o etiquetar) y filtrar pruebas basadas en esas categorías. Las siguientes
subsecciones explican cómo hacerlo.
JUnit 4
JUnit 4 proporciona la anotación `@Category` para agrupar pruebas. Necesitamos
especificar una o más clases de Java como atributos en esta anotación. Luego, podemos
usar estas clases para seleccionar y ejecutar las pruebas que pertenecen a una o más
categorías. El Ejemplo 8-10 muestra una clase básica que utiliza esta característica.
Ejemplo 8-10. Prueba utilizando categorías y JUnit 4
1. WebForm es una interfaz vacía disponible en el repositorio de ejemplos.
2. HomePage es otra interfaz vacía disponible en el repositorio de ejemplos.
Luego, podemos usar la línea de comandos para ejecutar pruebas basadas en sus grupos.
Por ejemplo, los siguientes comandos muestran el comando de Maven y Gradle para
ejecutar las pruebas que pertenecen a la categoría **HomePage**.
Podemos combinar este filtrado con el soporte de Maven y Gradle para seleccionar
pruebas basadas en el nombre de la clase. Por ejemplo, los siguientes comandos
ejecutan aquellas pruebas que pertenecen a la categoría **HomePage**, pero solo en
la clase de prueba **CategoriesJUnit4Test**.
TestNG
TestNG también permite agrupar pruebas. El Ejemplo 8-11 demuestra un uso básico de
esta característica. En resumen, la anotación `@Test` permite especificar etiquetas de
cadena para estos grupos.
Ejemplo 8-11. Prueba utilizando grupos y TestNG
1. Configuramos el atributo `alwaysRun` en `true` para indicar que los métodos de
configuración y desmontaje no se filtran durante la ejecución de las pruebas.
2. Asignamos el nombre de grupo WebForm a la primera prueba de esta clase.
3. Establecemos el nombre de grupo HomePage para la segunda prueba.
Luego, podemos usar la línea de comandos para filtrar la ejecución de pruebas en
función de estas categorías. El siguiente fragmento muestra primero cómo ejecutar la
prueba que pertenece al grupo **HomePage**. El segundo ilustra cómo combinar esta
agrupación con el mecanismo de filtrado de Maven y Gradle basado en el nombre de la
clase.
JUnit 5
El modelo de programación Jupiter proporciona una forma de agrupar pruebas basadas
en etiquetas personalizadas llamadas tags. Usamos la anotación `@Tag` para ese
propósito. El Ejemplo 8-12 ilustra esta característica.
Ejemplo 8-12. Prueba utilizando etiquetas y JUnit 5
1. Marcamos la primera prueba usando la etiqueta WebForm.
2. Clasificamos la segunda prueba utilizando la etiqueta HomePage.
Podemos usar estas etiquetas para incluir o excluir pruebas cuando las ejecutamos
desde la línea de comandos. Los siguientes comandos muestran varios ejemplos para
Maven y Gradle:
Ordenando Pruebas
El orden de ejecución de las pruebas es desconocido de antemano en los marcos de
pruebas unitarias utilizados en este libro. No obstante, existen mecanismos para
seleccionar un orden de ejecución dado. Un posible uso de esta característica en el
ámbito de Selenium WebDriver es reutilizar la misma sesión del navegador (es decir,
usar la misma instancia de WebDriver) por diferentes pruebas, interactuando con el SUT
(Sistema Bajo Prueba) en un orden determinado. Los siguientes ejemplos demuestran
este caso de uso para JUnit 4, TestNG, JUnit 5 y JUnit 5 más Selenium-Jupiter.
JUnit 4
JUnit 4 proporciona la anotación `@FixMethodOrder` para establecer el orden de
ejecución de las pruebas. Esta anotación acepta una enumeración llamada
MethodSorters, que se compone de los siguientes valores:
NAME_ASCENDING
Ordena los métodos de prueba por el nombre del método en orden lexicográfico.
JVM
Deja los métodos de prueba en el orden devuelto por la JVM.
DEFAULT
Ordena los métodos de prueba en un orden determinista, pero no predecible.
El Ejemplo 8-13 muestra un caso de prueba completo en el que las pruebas se ejecutan
utilizando el nombre del método.
Ejemplo 8-13. Ordenando pruebas usando JUnit 4
1. Usamos la anotación `@FixMethodOrder` a nivel de clase para fijar el orden de
las pruebas disponibles en esta clase.
2. Creamos la instancia del controlador (driver) antes de todas las pruebas (ya que
queremos usar la sesión de WebDriver en todas las pruebas).
3. Cerramos la instancia del controlador después de todas las pruebas. Por lo tanto,
terminamos la sesión después de la última prueba de esta clase.
4. Dado que los nombres de las pruebas están ordenados lexicográficamente
(testA, testB y testC), la ejecución de las pruebas seguirá esta secuencia.
TestNG
Una forma simple de ordenar las pruebas en TestNG es usando una prioridad
incremental para cada prueba. El Ejemplo 8-14 demuestra esta característica, utilizando
el atributo `priority` en la anotación `@Test`.
Ejemplo 8-14. Ordenando pruebas usando TestNG
JUnit 5
Jupiter proporciona la anotación `@TestMethodOrder` para ordenar pruebas. Esta
anotación se puede configurar usando las siguientes implementaciones de ordenación:
DisplayName
Ordena los métodos de prueba alfanuméricamente según sus nombres de
visualización.
MethodName
Ordena los métodos de prueba alfanuméricamente según sus nombres.
OrderAnnotation
Ordena los métodos de prueba basándose en los valores numéricos especificados
usando la anotación `@Order`. El Ejemplo 8-15 muestra una prueba usando este
método.
Random
Ordena los métodos de prueba de forma seudoaleatoria.
Ejemplo 8-15. Ordenando pruebas usando JUnit 5
Selenium-Jupiter
Como es habitual, las pruebas que utilizan Selenium-Jupiter también emplean el modelo
de programación de Jupiter; por lo tanto, estas características (como la ordenación de
pruebas) también son válidas para las pruebas de Selenium-Jupiter. El Ejemplo 8-16
muestra la misma prueba que antes, usando Selenium-Jupiter para la instanciación del
controlador. Por defecto, los objetos de controlador se crean antes de cada prueba y se
terminan después de cada prueba. Selenium-Jupiter proporciona la anotación
`@SingleSession` para cambiar este comportamiento, creando la instancia del
controlador antes de todas las pruebas y cerrando la sesión después de todas las
pruebas.
Ejemplo 8-16. Ordenando pruebas usando JUnit 5 con Selenium-Jupiter
Análisis de Fallos
El análisis de fallos (también conocido como resolución de problemas) es el proceso de
recopilar y analizar datos para descubrir la causa de un fallo. Este proceso puede ser
desafiante para las pruebas de Selenium WebDriver porque se prueba todo el sistema,
y las causas subyacentes de una prueba fallida pueden ser múltiples. Por ejemplo, la
causa de un fallo en una prueba de extremo a extremo podría ser la lógica del lado del
cliente (frontend), la lógica del lado del servidor (backend) o incluso la integración con
otros componentes (por ejemplo, base de datos o servicios externos).
Podemos utilizar diferentes técnicas para ayudar a los desarrolladores y testers en el
proceso de análisis de fallos. Una manera típica de hacer esto es detectar cuando una
prueba ha fallado y, antes de terminar la sesión del controlador, recopilar algunos datos
para descubrir la causa. Los siguientes recursos pueden ayudar en este proceso:
Capturas de pantalla
Una imagen de la interfaz de usuario de la aplicación web después de un fallo en
la prueba puede ayudar a determinar la causa del fallo. En “Capturas de Pantalla”
en la página 112 se explica cómo usar la API de Selenium WebDriver para hacer
capturas de pantalla.
Registro del navegador
La consola de JavaScript puede ser otra fuente potencial de información cuando
ocurre un error. En “Recopilación de Registros” en la página 168 se explica cómo
llevar a cabo esta recopilación de registros.
Grabaciones de sesión
Podemos grabar fácilmente la sesión del navegador cuando usamos navegadores
en contenedores Docker. En “Navegadores en Contenedores Docker” en la página
219 se explica cómo hacer esto con WebDriverManager y Selenium-Jupiter.
Las siguientes subsecciones proporcionan ejemplos básicos para realizar capturas de
pantalla del navegador de pruebas fallidas. Para ello, necesitamos recurrir a las
características específicas de los frameworks de pruebas para detectar pruebas fallidas.
JUnit 4
JUnit permite ajustar el comportamiento predeterminado de las pruebas mediante el
uso de reglas. Una clase de prueba define una regla decorando un atributo de clase con
la anotación `@Rule`. La Tabla 8-3 resume las reglas proporcionadas por defecto por
JUnit 4.
Tabla 8-3. Reglas en JUnit 4
Podemos usar la regla `TestWatcher` para recopilar datos para el análisis de fallos con
JUnit 4. Ejemplo 8-17 muestra una prueba que captura una captura de pantalla cuando
la prueba falla. Ejemplo 8-18 contiene la implementación para esta regla. Como se
mencionó anteriormente, hacemos una captura de pantalla del navegador. La lógica
para realizar esta captura de pantalla está disponible en Ejemplo 8-19.
Ejemplo 8-17. Análisis de pruebas fallidas usando JUnit 4
Definimos la regla a nivel de clase, pasando la instancia del driver como parámetro.
Forzamos a que esta prueba falle para hacer la captura de pantalla del navegador
utilizando la regla.
Ejemplo 8-18. Análisis de pruebas fallidas usando JUnit 4
Encapsulamos la lógica para el análisis de fallos en una clase separada. Sobrescribimos
el método que se activa cuando la prueba falla. En este caso, simplemente usamos la
instancia del gestor de fallos para hacer una captura de pantalla.
Ejemplo 8-19. Análisis de pruebas fallidas usando JUnit 4
1. Tomamos la captura de pantalla en formato PNG, almacenada con un nombre
de archivo pasado como parámetro.
TestNG
TestNG proporciona varios oyentes por defecto. Estos oyentes son clases que capturan
diferentes eventos del ciclo de vida de una prueba. Por ejemplo, el oyente `ITestResult`
permite monitorear el estado y el resultado de una prueba. Como muestra el Ejemplo
8-20, podemos usar fácilmente este oyente para implementar el análisis de fallos en una
prueba de Selenium WebDriver.
Ejemplo 8-20. Análisis de pruebas fallidas usando TestNG
1. Declaramos un parámetro `ITestResult` en el método de limpieza de la
prueba.
2. Leemos el estado de la prueba.
3. En caso de fallo, creamos una instancia del gestor de fallos (usamos la misma
lógica descrita en el Ejemplo 8-19) para crear una captura de pantalla.
JUnit 5
En JUnit 5, el modelo de extensiones Jupiter reemplazó y mejoró la gestión del ciclo de
vida de las pruebas de JUnit 4 basada en reglas. Como se introdujo en el Capítulo 2, el
modelo de extensiones proporcionado por Jupiter permite agregar nuevas
características sobre el modelo de programación de Jupiter. De esta manera, una
extensión de Jupiter es una clase Java que implementa uno o varios puntos de extensión,
que son interfaces que permiten diferentes tipos de operaciones en el modelo de
programación de Jupiter. La Tabla 8-4 resume los puntos de extensión proporcionados
por Jupiter.
Tabla 8-4. Puntos de extensión de Jupiter
Un punto de extensión conveniente para implementar el análisis de fallos es
`AfterTestExecutionCallback`, ya que permite incluir lógica personalizada
inmediatamente después de que se haya ejecutado una prueba individual. El Ejemplo 8-
21 proporciona una prueba de Jupiter que utiliza una anotación personalizada (ver
Ejemplo 8-22) que implementa este punto de extensión.
Ejemplo 8-21. Análisis de pruebas fallidas usando JUnit 5
1. Utilizamos la extensión `FailureWatcher` para las pruebas disponibles en esta
clase. Pasamos la instancia del driver como argumento.
2. Forzamos una falla para que la extensión tome la captura de pantalla del
navegador.
Ejemplo 8-22. Análisis de pruebas fallidas usando JUnit 5
1. Esta extensión implementa un único punto de extensión:
`AfterTestExecutionCallback`.
2. Este punto de extensión debe sobrescribir este método, que se ejecuta
inmediatamente después de cada prueba.
3. Verificamos si hay una excepción de ejecución presente.
4. Si es así, tomamos una captura de pantalla usando la instancia de WebDriver.
Selenium-Jupiter
Selenium-Jupiter es una extensión de Jupiter que, entre otras características, permite
tomar capturas de pantalla del navegador sin esfuerzo. El Ejemplo 8-23 demuestra esta
característica.
Ejemplo 8-23. Análisis de pruebas fallidas en JUnit 5 con Selenium-Jupiter
1. Selenium-Jupiter toma una captura de pantalla del navegador en el caso de
pruebas fallidas simplemente utilizando esta capacidad de configuración.
Reintentar Pruebas
Como se explicó en el Capítulo 7, la inestabilidad de las pruebas (es decir, la falta de
fiabilidad) es un problema bien conocido en las pruebas de extremo a extremo. Como
testers, a veces necesitamos identificar una prueba inestable (es decir, una prueba que
pasa o falla bajo las mismas condiciones), y para eso, reintentamos una prueba dada
para verificar si su resultado es consistente. Por lo tanto, podríamos querer un
mecanismo para reintentar pruebas en caso de falla. Esta sección explica cómo llevar a
cabo este proceso utilizando los diferentes marcos de pruebas unitarias.
JUnit 4
Necesitamos usar una regla personalizada de JUnit 4 para reintentar pruebas fallidas. El
Ejemplo 8-24 muestra una prueba usando un ejemplo de este tipo de regla, y el Ejemplo
8-25 contiene el código fuente de esa regla.
Ejemplo 8-24. Reintentar pruebas usando JUnit 4
1. Declaramos la regla de reintento como un atributo de prueba.
2. Utilizamos el mismo navegador para todas las repeticiones.
3. Abrimos una página web de práctica llamada calculadora aleatoria. Esta página
ha sido diseñada para producir resultados incorrectos un determinado
porcentaje del tiempo (50% por defecto). Luego, la calculadora funciona
perfectamente después de un número configurable de veces (cinco por defecto).
4. Usamos la interfaz gráfica de la calculadora para realizar una operación
aritmética esencial.
5. Verificamos el resultado. Hay un 50% de probabilidad de obtener un resultado
incorrecto en los primeros cinco intentos.
Ejemplo 8-25. Regla de JUnit 4 para reintentar pruebas fallidas
1. Implementamos la interfaz genérica para las reglas de JUnit 4, es decir,
`TestRule`.
2. Esta regla acepta un valor entero en su constructor, que se usa para determinar
el número máximo de reintentos.
3. Necesitamos sobrescribir el método `apply`, que permite manipular el ciclo de
vida de la prueba.
4. Repetimos la ejecución de la prueba en un bucle, repitiendo un número máximo
de veces igual al número de reintentos.
5. En caso de error durante la ejecución de la prueba, obtenemos el objeto de
excepción y repetimos la ejecución de la prueba.
6. Si se llega a esta línea, significa que la prueba se ha repetido el número máximo
de veces.
TestNG
TestNG proporciona una capacidad personalizada para implementar reintentos de
pruebas. Como se muestra en el Ejemplo 8-26, usamos el atributo `retryAnalyzer` de una
anotación `@Test` para habilitar esta función. El Ejemplo 8-27 muestra la
implementación para ese analizador de reintentos.
Ejemplo 8-26. Reintentando pruebas usando TestNG
Ejemplo 8-27. Analizador de pruebas para TestNG
1. Necesitamos implementar un listener de TestNG llamado IRetryAnalyzer para
implementar un analizador de reintentos.
2. No podemos parametrizar esta clase; por lo tanto, declaramos el número
máximo de reintentos dentro de la clase (como una constante, en este caso).
3. Necesitamos sobrescribir el método `retry`. Este método devuelve un valor
booleano que determina si la prueba se reintenta o no en caso de fallo.
4. La lógica para determinar este valor es un acumulador que verifica si se ha
alcanzado el umbral de reintentos.
JUnit 5
Necesitamos usar el modelo de extensión explicado previamente (ver Tabla 8-4) para
reintentar pruebas fallidas. En lugar de reinventar la rueda, podemos usar una extensión
de Jupiter de código abierto existente para este propósito. Para reintentar pruebas, y
como se introdujo en el Capítulo 2, hay varias alternativas: JUnit Pioneer o rerunner-
jupiter. El Ejemplo 8-28 muestra una prueba usando esta última.
Ejemplo 8-28. Reintentando pruebas usando JUnit 5
Selenium-Jupiter
Las pruebas que utilizan Selenium-Jupiter también pueden usar otras extensiones. El
Ejemplo 8-29 muestra cómo usar rerunner-jupiter en una prueba de Selenium-Jupiter.
Ejemplo 8-29. Reintentando pruebas en JUnit 5 con Selenium-Jupiter
1. Reutilizamos el mismo navegador para todas las repeticiones posibles.
Ejecución de Pruebas en Paralelo
El tiempo requerido para ejecutar una suite de pruebas de Selenium WebDriver
(especialmente si el número de pruebas es alto) puede ser considerable. La razón de
esta lentitud es que una prueba de Selenium WebDriver regular inicia un nuevo
navegador cada vez, lo que hace que el tiempo total de ejecución aumente. Una posible
solución a este problema es ejecutar las pruebas en paralelo.
Hay diferentes maneras de lograr esta paralelización. Primero, podemos usar las
capacidades integradas para la ejecución paralela proporcionadas por las herramientas
de construcción (Maven o Gradle). En segundo lugar, podemos usar las características
proporcionadas por los marcos de pruebas unitarias (JUnit 4 o 5, y TestNG) con ese
objetivo. Las siguientes subsecciones explican todas estas opciones.
Maven
Maven ofrece diferentes mecanismos para la ejecución paralela. Primero, Maven
permite construir módulos de proyectos multimódulo en paralelo. Para ello,
necesitamos invocar el comando de Maven desde la línea de comandos utilizando la
opción `-T`. Esta opción acepta dos tipos de argumentos para la paralelización: usando
un número fijo de hilos o usando un factor multiplicado por el número de núcleos de
CPU disponibles en tu sistema. El siguiente fragmento muestra un ejemplo de cada tipo:
1. Ejecuta las pruebas de un proyecto multimódulo (por ejemplo, el repositorio de
ejemplos) en paralelo utilizando cuatro hilos.
2. Ejecuta las pruebas de un proyecto multimódulo usando el mismo número de
hilos que los núcleos de la CPU (por ejemplo, cuatro hilos en un sistema de cuatro
núcleos).
Además, el plugin utilizado para ejecutar pruebas unitarias en Maven (llamado Surefire)
proporciona dos formas de ejecutar pruebas en paralelo. La primera es la multihilo
dentro de un solo proceso JVM. Para habilitar este modo, necesitamos especificar
diferentes parámetros de configuración, como:
parallel
Configura el nivel de granularidad para la paralelización. Los valores posibles para
este parámetro son métodos (para ejecutar métodos de prueba en hilos
separados), clases (para clases de prueba), suites (para suites de prueba),
suitesAndClasses (para suites de prueba y clases), suitesAndMethods (para suites
de prueba y métodos) y all (para ejecutar cada prueba en hilos separados).
threadCount
Define el número máximo de hilos para la paralelización.
useUnlimitedThreads
Permite un número ilimitado de hilos.
Hay dos maneras de especificar estos parámetros de configuración. Primero, podemos
configurarlos directamente en el archivo de configuración de Maven (es decir, el archivo
`pom.xml`). El Ejemplo 8-30 demuestra cómo hacerlo. Además, podemos especificar
estos parámetros como propiedades del sistema al usar la línea de comandos, por
ejemplo:
Ejemplo 8-30. Ejemplo de configuración de Maven Surefire para ejecución paralela
La segunda forma de implementar paralelismo con Maven Surefire es mediante la
creación de procesos JVM múltiples, es decir, la bifurcación. Esta opción puede ser útil
si necesitamos evitar problemas de concurrencia a nivel de hilos, ya que los diferentes
procesos no comparten espacio de memoria, a diferencia de lo que ocurre en la
multihilo. Como desventaja, la bifurcación consume más memoria y tiene un
rendimiento más bajo. Para habilitar la bifurcación, necesitamos usar la propiedad de
configuración `forkCount` (de nuevo, en el `pom.xml` o como una propiedad del sistema)
a un valor mayor que uno (es decir, el número de procesos JVM que se deben crear).
Por ejemplo, el siguiente comando ejecuta las pruebas de un proyecto Maven utilizando
cuatro procesos JVM:
Gradle
Gradle también ofrece varias formas de ejecutar pruebas en paralelo. Primero, permite
ejecutar tareas en paralelo en un proyecto multimódulo. Hay dos maneras de habilitar
este modo. Primero, configurando la propiedad `org.gradle.parallel=true` en el archivo
de configuración `gradle.properties`. Segundo, usando la opción `--parallel` en el
comando, por ejemplo:
Además, podemos usar la propiedad de configuración `maxParallelForks` en el archivo
de configuración de Gradle para especificar el número máximo de procesos de prueba
que se deben iniciar en paralelo. Por defecto, Gradle ejecuta una sola clase de prueba a
la vez. Podemos cambiar este comportamiento predeterminado configurando un valor
mayor que uno para este parámetro. Además de un valor fijo, podemos especificar el
número de núcleos de CPU disponibles en el sistema:
En el repositorio de ejemplo, esta propiedad se habilita condicionalmente usando un
perfil llamado `parallel` (ver Apéndice C). Por lo tanto, podemos usar este perfil desde
la línea de comandos:
JUnit 4
JUnit proporciona una manera básica de ejecutar pruebas en paralelo a través de la clase
`ParallelComputer`. Esta clase acepta dos parámetros booleanos en su constructor para
habilitar la ejecución paralela de clases y métodos, respectivamente. El Ejemplo 8-31
muestra una prueba usando esta clase.
Ejemplo 8-31. Ejecución paralela de pruebas usando JUnit 4
1. Especificamos qué clases de prueba se ejecutan en paralelo.
2. Habilitamos la ejecución paralela de pruebas para clases y métodos.
TestNG
Una forma común de especificar la ejecución paralela para las pruebas en TestNG es a
través del archivo de configuración `testng.xml`. Los atributos más relevantes para
habilitar este modo en TestNG son:
parallel
Especifica el modo para ejecutar pruebas en paralelo. Las alternativas son
`methods` (métodos), `tests` (pruebas) y `classes` (clases).
threadcount
Establece el número máximo de hilos por defecto para ejecutar pruebas en paralelo.
El Ejemplo 8-32 muestra una configuración básica de `testng.xml` para el paralelismo de
pruebas.
Ejemplo 8-32. Configuración paralela de pruebas para TestNG
Podemos usar Maven o Gradle en la línea de comandos para ejecutar el conjunto de
pruebas paralelas anterior:
JUnit 5
JUnit 5 permite diferentes formas de ejecutar pruebas en paralelo. La siguiente lista
resume los parámetros de configuración más relevantes para este propósito:
junit.jupiter.execution.parallel.enabled
Bandera booleana para habilitar el paralelismo de pruebas (false por defecto).
junit.jupiter.execution.parallel.mode.classes.default
Para ejecutar clases de prueba en paralelo. Los valores posibles son `same_thread`
para ejecución en un solo hilo (por defecto) y `concurrent` para ejecución paralela.
junit.jupiter.execution.parallel.mode.default
Para ejecutar métodos de prueba en paralelo. Los valores posibles son los mismos
que antes (para las clases de prueba).
Hay dos formas de especificar estos parámetros. Primero, en el archivo de configuración
`junitplatform.properties` (que debería estar disponible en el classpath del proyecto). El
Ejemplo 8-33 muestra el contenido de muestra de este archivo. Segundo, utilizando
propiedades del sistema y la línea de comandos. Los siguientes comandos
(Maven/Gradle) muestran cómo hacerlo:
Ejemplo 8-33. Ejecución paralela de pruebas usando JUnit 5
Además, el modelo de programación Jupiter proporciona la anotación `@Execution`
para cambiar el modo de paralelización para clases o métodos de prueba. Esta anotación
se puede usar a nivel de clase o de método y acepta dos valores:
`ExecutionMode.CONCURRENT` (para ejecución paralela) y
`ExecutionMode.SAME_THREAD` (para ejecución en un solo hilo). El Ejemplo 8-34
muestra la estructura de una clase de prueba contenida en el repositorio de ejemplo.
Suponiendo que la prueba paralela esté habilitada (como en el Ejemplo 8-33), esta clase
se ejecutará en paralelo junto con otras pruebas que permiten la paralelización.
Ejemplo 8-34. Ejecución paralela de pruebas usando JUnit 5
Escuchadores de Pruebas
Una necesidad común en el proceso de pruebas es llevar un seguimiento de las
diferentes etapas de la ejecución de las pruebas. Los marcos de pruebas unitarias
proporcionan así una característica conocida como escuchador de pruebas. Los
escuchadores de pruebas pueden verse como utilidades que modifican el
comportamiento predeterminado de las pruebas al realizar acciones personalizadas en
varias etapas del ciclo de ejecución de pruebas. Como es habitual, cada marco de
pruebas unitarias proporciona su propia implementación para estos escuchadores de
pruebas.
JUnit 4
En JUnit 4, los escuchadores de pruebas incluyen operaciones personalizadas cuando las
pruebas son iniciadas, aprobadas, finalizadas, fallidas, omitidas o ignoradas. El primer
paso para implementar un escuchador de JUnit 4 es crear una clase Java que extienda la
clase `RunListener`. En esta clase, puedes sobrescribir varios métodos (por ejemplo,
`testRunStarted`, `testIgnored`, `testFailure`, etc.) para incluir lógica adicional en los
diferentes pasos del ciclo de vida de la prueba. El Ejemplo 8-35 muestra una
implementación básica de un escuchador de pruebas de JUnit 4. Este escuchador
simplemente muestra un mensaje en la salida estándar sobre la etapa de la prueba.
Ejemplo 8-35. Escuchador de pruebas usando JUnit 4
Una forma común de registrar un escuchador de pruebas en JUnit 4 es crear un ejecutor
personalizado y usar ese ejecutor en las clases de prueba. El Ejemplo 8-36 muestra un
ejecutor de pruebas personalizado que registra el escuchador anterior. El Ejemplo 8-37
muestra un esqueleto de prueba utilizando este ejecutor.
Ejemplo 8-36. Escuchadores de pruebas usando JUnit 4
1. Extendemos `BlockJUnit4ClassRunner`, el ejecutor de pruebas predeterminado
en JUnit 4.
2. Registramos nuestro escuchador de pruebas personalizado.
3. Llamamos al padre para continuar usando el ejecutor de pruebas
predeterminado.
Ejemplo 8-37. Escuchadores de pruebas usando JUnit 4
Decoramos las clases de prueba usando la anotación `@RunWith` de JUnit 4 y nuestro
ejecutor personalizado.
TestNG
TestNG proporciona la interfaz `ITestListener` para implementar escuchadores de
pruebas. Las clases que implementan esta interfaz pueden sobrescribir métodos para
las diferentes etapas del ciclo de vida de TestNG, como `onTestSuccess`, `onTestFailure`
u `onTestSkipped`, entre otros. El Ejemplo 8-38 muestra una clase de muestra que
implementa esta interfaz. En este ejemplo, los métodos del escuchador registran un
mensaje en la salida estándar. El Ejemplo 8-39 muestra una prueba que usa este
escuchador.
Ejemplo 8-38. Escuchador de pruebas usando TestNG
Ejemplo 8-39. Escuchadores de pruebas usando TestNG
Usamos la anotación `@Listeners` de TestNG para especificar que todas las pruebas en
esta clase usan nuestro escuchador de pruebas personalizado.
JUnit 5
Como se discutió anteriormente (ver Tabla 8-4), Jupiter proporciona una amplia
variedad de puntos de extensión para incluir lógica personalizada en el ciclo de vida de
pruebas de JUnit 5. Además de este modelo de extensión, JUnit 5 permite la
implementación de escuchadores de pruebas para seguir varias etapas de ejecución de
pruebas, como pruebas iniciadas, omitidas o finalizadas. Esta función está disponible a
través de la API JUnit Launcher, que es la API para descubrir, filtrar y ejecutar pruebas
en la Plataforma JUnit (ver Figura 2-4).
Para crear un escuchador de pruebas en JUnit 5, necesitamos implementar la interfaz
`TestExecutionListener`. Una clase que implemente esta interfaz puede sobrescribir
diferentes métodos para ser notificada de eventos que ocurren durante la ejecución de
pruebas. El **Ejemplo 8-40** contiene una clase básica que implementa esta interfaz.
Estos tipos de escuchadores se registran en JUnit 5 usando un mecanismo estándar de
cargador de servicios de Java. Para ello, necesitamos crear un archivo llamado `/META-
INF/services/org.junit.platform.launcher.TestExecutionListener` en el classpath del
proyecto, y escribir el nombre completamente calificado del escuchador de pruebas que
queremos registrar (por ejemplo,
`io.github.bonigarcia.webdriver.jupiter.ch08.listeners.MyTestListener` para el Ejemplo
8-40). Tenga en cuenta que este archivo no está incluido en el repositorio de ejemplos
para evitar interferir en todo el conjunto de pruebas.
Ejemplo 8-40. Escuchadores de pruebas usando JUnit 5
La interfaz `TestExecutionListener` pertenece a la API JUnit Platform
Launcher; por lo tanto, para usarla, necesitamos incluir esta API como una
dependencia adicional en nuestro proyecto. El Apéndice C explica la
configuración requerida para Maven y Gradle al respecto.
Pruebas Deshabilitadas
Los marcos de pruebas unitarias permiten deshabilitar (es decir, omitir en la ejecución
de pruebas) clases de prueba completas o métodos de prueba individuales de manera
programática. Las siguientes subsecciones explican las diferencias entre JUnit 4, TestNG,
JUnit 5 y Selenium-Jupiter.
JUnit 4
JUnit 4 proporciona la anotación `@Ignore` para deshabilitar pruebas. Esta anotación se
puede usar a nivel de clase o de método. Opcionalmente, podemos incluir un mensaje
en la anotación para especificar la razón de la desactivación. El Ejemplo 8-41 contiene
una prueba deshabilitada.
Ejemplo 8-41. Pruebas deshabilitadas usando JUnit 4
TestNG
TestNG permite deshabilitar pruebas de dos maneras. Primero, podemos usar la
anotación `@Ignore` para clases o métodos de prueba. Segundo, podemos usar el
atributo `enabled` de la anotación `@Test`. El Ejemplo 8-42 ilustra ambos métodos.
Ejemplo 8-42. Pruebas deshabilitadas usando TestNG
JUnit 5
El modelo de programación Jupiter proporciona varias anotaciones para deshabilitar
pruebas en función de diferentes condiciones. La Tabla 8-5 resume estas anotaciones, y
el Ejemplo 8-43 ofrece un ejemplo básico usando algunas de estas anotaciones.
Tabla 8-5. Anotaciones de Jupiter para deshabilitar pruebas
Ejemplo 8-43. Pruebas deshabilitadas usando JUnit 5
1. Saltamos esta prueba siempre.
2. Saltamos esta prueba en el caso de usar Java 8.
3. Saltamos esta prueba en cualquier sistema operativo que no sea macOS.
Selenium-Jupiter
Selenium-Jupiter proporciona anotaciones adicionales para deshabilitar pruebas
condicionalmente dependiendo de condiciones específicas de las pruebas con Selenium
WebDriver. Estas condiciones son la disponibilidad del navegador, la disponibilidad de
Docker y la URL en línea (es decir, debe devolver un código de respuesta 200 al solicitar
la URL con el método HTTP GET). El Ejemplo 8-44 muestra varias pruebas usando estas
anotaciones.
Ejemplo 8-44. Pruebas deshabilitadas usando JUnit 5 con Selenium-Jupiter
1. Esta prueba se omite si Safari no está disponible en el sistema.
2. Esta prueba se omite si Docker no está disponible en el sistema.
3. Esta prueba se omite si una URL del servidor de Selenium no está en línea. Si es
así, la prueba se ejecuta y se usa la URL anterior para crear una instancia de
`RemoteWebDriver`. Para especificar las capacidades requeridas, usamos la
anotación `@DriverCapabilities` en esta prueba (como se explicó en el Capítulo
6).
Resumen y Perspectivas
Este capítulo presentó algunas de las características específicas más relevantes del
marco de pruebas utilizado en este libro (es decir, JUnit 4, TestNG, JUnit 5 y Selenium-
Jupiter) para el desarrollo de pruebas con Selenium WebDriver. Primero, aprendiste
cómo implementar pruebas parametrizadas. Esta característica puede ser conveniente
para pruebas cruzadas en diferentes navegadores (es decir, usando distintos
navegadores para pruebas web). Luego, aprendiste cómo categorizar pruebas y usar
estas categorías para incluirlas o exclu‐irlas de la ejecución de pruebas. Continuaste
comprendiendo mecanismos para el análisis de fallos (por ejemplo, tomar una captura
de pantalla del navegador cuando una prueba falla), reintentar pruebas o ejecutar
pruebas en paralelo. Finalmente, descubriste cómo implementar escuchadores de
pruebas y los diferentes mecanismos para deshabilitar pruebas.
En el próximo capítulo, aprenderás cómo integrar Selenium WebDriver con diferentes
utilidades de terceros para implementar pruebas avanzadas de extremo a extremo.
Descubrirás cómo descargar archivos de aplicaciones web, capturar tráfico sin usar CDP
(por ejemplo, en Firefox), probar requisitos no funcionales (como rendimiento,
seguridad o accesibilidad), manejar diferentes datos de entrada, mejorar los informes
de pruebas e integrar con marcos existentes como Spring o Cucumber.
CAPÍTULO 9
Integraciones con Terceros
Este capítulo introduce diferentes tecnologías de terceros (como bibliotecas o marcos)
que podemos usar con Selenium WebDriver. Necesitamos usar estas tecnologías cuando
la API de Selenium WebDriver es insuficiente para realizar tareas específicas. Este es el
caso de la descarga de archivos, para lo cual necesitamos usar una utilidad de terceros
para esperar hasta que los archivos se descarguen correctamente o, alternativamente,
usar un cliente HTTP para controlar la descarga. También capturamos el tráfico HTTP
usando un proxy de terceros.
Otro escenario en el que necesitamos usar utilidades externas con Selenium WebDriver
es al implementar pruebas no funcionales, como pruebas de rendimiento, seguridad,
accesibilidad o pruebas A/B. También podemos usar bibliotecas de terceros para
desarrollar pruebas de Selenium WebDriver usando una API fluida, generar datos de
prueba falsos o mejorar los informes de pruebas. Finalmente, podemos integrar marcos
relevantes como Cucumber para Desarrollo Guiado por Comportamiento (BDD) o el
Framework Spring (para el desarrollo de aplicaciones web). Revisaremos todos estos
usos en este capítulo.
Para usar las utilidades de terceros presentadas en este capítulo, primero debes
incluir las dependencias requeridas en tu proyecto. Puedes encontrar los detalles
para resolver cada dependencia usando Maven y Gradle en el Apéndice C.
Descarga de Archivos
Selenium WebDriver tiene un soporte limitado para la descarga de archivos porque su
API no expone el progreso de la descarga. En otras palabras, podemos usar Selenium
WebDriver para descargar archivos desde aplicaciones web, pero no podemos controlar
el tiempo necesario para copiar estos archivos en el sistema de archivos local. Por esta
razón, podemos usar bibliotecas de terceros para mejorar la experiencia de las
descargas web con Selenium WebDriver.
Existen diferentes alternativas para este propósito. Las siguientes subsecciones explican
cómo.
Uso de Capacidades Específicas del Navegador
Podemos usar capacidades específicas del navegador (como hicimos en el Capítulo 5)
para configurar varios parámetros para la descarga de archivos, como la carpeta de
destino. Este enfoque es conveniente ya que estas características están disponibles en
la API de Selenium WebDriver por defecto, pero también tiene varias desventajas.
Primero, es incompatible con diferentes tipos de navegadores (Chrome, Firefox, etc.).
En otras palabras, las capacidades requeridas son distintas para cada navegador
individual. Segundo, y más importante, no tenemos el control para rastrear el progreso
de la descarga. Para solucionar este problema, necesitamos usar una biblioteca de
terceros.
En este libro, propongo usar la biblioteca de código abierto Awaitility. Awaitility es una
biblioteca popular que proporciona características para manejar operaciones
asíncronas. De esta manera, proporciona una API fluida para gestionar hilos, tiempos de
espera y problemas de concurrencia. En el caso de descargar archivos con Selenium
WebDriver, usamos la API de Awaitility para esperar hasta que los archivos descargados
se almacenen en el sistema de archivos. El Ejemplo 9-1 muestra un ejemplo usando
Chrome y Awaitility. El Ejemplo 9-2 muestra la configuración de prueba equivalente al
usar Firefox.
Ejemplo 9-1. Descargar archivos de prueba usando Chrome y Awaitility
1. Especificamos una carpeta para guardar los archivos descargados. No obstante,
debes tener en cuenta que Chrome permite solo ciertos directorios para la
descarga. Por ejemplo, permite la carpeta de descargas (y subcarpetas), pero
prohíbe usar otras rutas, como la carpeta de escritorio o el directorio personal.
2. Usamos una preferencia de Chrome para especificar la carpeta de destino.
3. Usamos una página web disponible en el sitio de práctica para descargar
diferentes archivos haciendo clic en botones (ver Figura 9-1).
4. Hacemos clic en dos de los botones disponibles en la página. Como resultado, el
navegador comienza a descargar dos archivos: una imagen PNG y un documento
PDF, respectivamente.
5. Usamos Awaitility para configurar un tiempo de espera de cinco segundos.
6. Esperamos hasta que el primer archivo esté en el sistema de archivos.
7. También esperamos hasta que el segundo archivo esté descargado.
Ejemplo 9-2. Configuración de prueba para descargar archivos usando Firefox
1. Firefox permite especificar cualquier carpeta para la descarga de archivos. En
este caso, usamos la carpeta del proyecto local.
2. Usamos una preferencia de Firefox para especificar un directorio de descarga
personalizado.
3. Necesitamos configurar la preferencia `browser.download.folderList` en 2 para
seleccionar una carpeta de descarga personalizada. Los otros valores posibles
son 0 para descargar archivos en el escritorio del usuario y 1 para usar la carpeta
de descargas (valor predeterminado).
4. Especificamos los tipos de contenido que Firefox no pedirá guardar en el sistema
de archivos local.
5. Desactivamos la previsualización de archivos PDF.
Figura 9-1. Página web de práctica para descargar archivos
Usando un Cliente HTTP
Un mecanismo alternativo para descargar archivos con Selenium WebDriver es usar una
biblioteca de cliente HTTP. Propongo usar Apache HttpClient, ya que
WebDriverManager utiliza internamente esta biblioteca, y por lo tanto, puedes usarla
como una dependencia transitiva en tu proyecto. El Ejemplo 9-3 muestra un caso de
prueba completo que descarga varios archivos con Apache HttpClient desde el sitio de
práctica. Observa que en este caso, no es necesario esperar explícitamente hasta que la
descarga del archivo termine, ya que Apache HttpClient maneja las respuestas HTTP de
manera sincrónica.
Ejemplo 9-3. Prueba de descarga de archivos utilizando un cliente HTTP
1. Usamos la página web de práctica nuevamente para descargar archivos.
2. Hacemos clic en un botón para descargar un archivo.
3. Refactorizamos la lógica común para descargar archivos en el método
`download` de la clase.
4. Repetimos la operación para descargar un segundo archivo.
5. Creamos una instancia de Apache HTTPClient dentro de un bloque try-with-
resources. Este cliente se cierra automáticamente al final del alcance del bloque.
6. Usamos otro bloque try-with-resources para enviar una solicitud HTTP a la URL
proporcionada y, como resultado, obtener una respuesta HTTP.
7. Copiamos el archivo resultante en el sistema de archivos local.
Captura de tráfico de red
La "monitorización de red" en la página 182 y el "interceptor de red" en la página 178
explican cómo usar capacidades específicas del navegador para capturar el tráfico HTTP
entre Selenium WebDriver y la aplicación web bajo prueba. La desventaja de este
mecanismo es que solo está disponible en los navegadores que admiten CDP. Sin
embargo, podemos usar un proxy de terceros para otros navegadores. En este libro,
propongo utilizar BrowserMob proxy para este propósito.
BrowserMob es un proxy de código abierto que permite manipular el tráfico HTTP
utilizando una biblioteca Java. El Ejemplo 9-4 muestra una prueba completa utilizando
este proxy en una prueba de Selenium WebDriver. En este ejemplo, utilizamos el proxy
BrowserMob para interceptar el tráfico HTTP entre la prueba y el sitio web objetivo,
registrando este tráfico (solicitud-respuesta) como trazas de registro.
1. Creamos una instancia de BrowserMob.
2. Iniciamos este proxy.
3. Capturamos el tráfico HTTP usando HAR (HTTP Archive), un formato de archivo
basado en JSON utilizado para capturar y exportar este tráfico.
4. Activamos la captura de las solicitudes y respuestas HTTP intercambiadas.
5. Convertimos el servidor BrowserMob en un proxy de Selenium WebDriver.
6. Configuramos este proxy como una opción del navegador (en este caso, para
Firefox).
7. Necesitamos permitir certificados inseguros ya que la comunicación con el proxy
se realiza usando HTTP (y no HTTPS).
8. Detenemos el proxy después de la prueba.
9. Usamos la instancia del proxy para recopilar el tráfico HTTP (solicitudes y
respuestas). Utilizamos un registrador para escribir esta información en la salida
estándar en este ejemplo básico.
Pruebas no funcionales
Como se explicó en el Capítulo 1, Selenium WebDriver se usa principalmente para
evaluar los requisitos funcionales de las aplicaciones web. En otras palabras, los testers
utilizan la API de Selenium WebDriver para verificar que una aplicación web bajo prueba
se comporte como se espera. Sin embargo, podemos aprovechar esta API para probar
requisitos no funcionales, es decir, atributos de calidad como rendimiento, seguridad,
accesibilidad, etc. Una estrategia común para lograr este objetivo es integrarse con
utilidades específicas de terceros. Las siguientes subsecciones explican diferentes
integraciones con Selenium WebDriver para pruebas no funcionales.
Rendimiento
Las pruebas de rendimiento evalúan la capacidad de respuesta y estabilidad de un SUT
bajo una carga particular. En lugar de Selenium WebDriver, los testers suelen adoptar
herramientas específicas como Apache JMeter para pruebas de rendimiento. Apache
JMeter es una herramienta de código abierto que permite enviar múltiples solicitudes
HTTP a un punto final de URL dado mientras mide el tiempo de respuesta y otras
métricas. Aunque la integración directa entre Selenium WebDriver y Apache JMeter no
es trivial, podemos aprovechar una prueba existente de Selenium WebDriver como un
plan de prueba de JMeter (es decir, la serie de pasos que JMeter ejecuta). El beneficio
de este enfoque es que el plan de prueba resultante de JMeter mimetizará el mismo
flujo de trabajo del usuario utilizado en la prueba de Selenium WebDriver, reutilizando
el mismo tráfico HTTP que realiza el navegador (por ejemplo, para bibliotecas JavaScript,
CSS, etc.). Para ese propósito, propongo el siguiente procedimiento:
1. Usar el proxy BrowserMob (introducido en la sección anterior) para capturar el tráfico
de red intercambiado en Selenium WebDriver como un archivo HAR.
2. Convertir el archivo HAR resultante en un plan de prueba de JMeter. Los planes de
prueba en JMeter se almacenan como archivos basados en XML con la extensión JMX.
3. Cargar el plan de prueba JMX en JMeter y ajustarlo para simular usuarios concurrentes
e incluir escuchadores de resultados.
4. Ejecutar el plan de prueba y evaluar los resultados.
El Ejemplo 9-5 muestra un caso de prueba completo que implementa el primer paso.
Como se puede ver, el inicio de sesión necesario para comenzar y crear el archivo HAR
se realiza antes y después de cada prueba. Puedes utilizar este enfoque para aprovechar
las pruebas funcionales existentes (es decir, la lógica en los métodos `@Test`) como
pruebas de rendimiento (para ser ejecutadas en JMeter).
Ejemplo 9-5. Prueba de creación de un archivo HAR
1. Iniciamos BrowserMob antes de la prueba y lo configuramos en la sesión de
WebDriver.
2. Obtenemos el archivo HAR después de la prueba y lo guardamos como un
archivo local.
Después de ejecutar la prueba anterior, obtenemos un archivo HAR llamado login.har.
Ahora, necesitamos convertirlo en un plan de prueba de JMeter. Hay diferentes
alternativas para hacerlo. Puedes encontrar varios programas (por ejemplo, en Ruby o
Java) que realizan esta tarea de manera gratuita en la web. También puedes usar
servicios de conversión en línea, como el Convertidor JMX de BlazeMeter. En este
ejemplo, utilizo este servicio en línea y abro el plan de prueba JMX resultante en JMeter.
En este punto, puedes ajustar la configuración de JMeter a tu conveniencia (puedes
encontrar más información sobre JMeter en su manual oficial). Por ejemplo, la Figura 9-
2 muestra la interfaz gráfica de JMeter después de cargar el plan de prueba JMX
resultante junto con los siguientes cambios:
- Aumentar el número de usuarios concurrentes a cien (en la pestaña “Thread Group”)
- Incluir algunos escuchadores de resultados, como “Aggregate Graph” y “Graph Results”
Figura 9-2. Interfaz gráfica de JMeter cargando el plan de prueba resultante
Ahora, podemos ejecutar el plan de prueba con JMeter (por ejemplo, haciendo clic en
el botón con un triángulo verde en la interfaz gráfica de JMeter). Como resultado, se
genera una carga de cien usuarios concurrentes siguiendo las interacciones inicialmente
desarrolladas como una prueba de Selenium WebDriver (Ejemplo 9-5). La Figura 9-3
muestra los resultados para los escuchadores añadidos previamente.
Figura 9-3. Resultados de JMeter
Uso de navegadores para generar la carga
El uso de herramientas como JMeter es conveniente para muchos escenarios de pruebas
de rendimiento para aplicaciones web. Sin embargo, este enfoque no es adecuado
cuando necesitas navegadores reales para recrear el flujo de trabajo completo del
usuario (por ejemplo, en aplicaciones web de videoconferencia). En ese caso, una
solución posible es usar WebDriverManager junto con Docker.
El Ejemplo 9-6 demuestra este uso. Como puedes ver en esta prueba,
WebDriverManager permite crear una lista de instancias de WebDriver simplemente
especificando el tamaño como un parámetro en el método `create()`. Luego, por
ejemplo, podemos usar Java estándar para ejercitar la aplicación web bajo prueba en
paralelo utilizando un grupo de hilos.
Ejemplo 9-6. Prueba de carga usando WebDriverManager y Docker
1. Creamos una instancia del administrador de Chrome, usando Docker para
ejecutar los navegadores como contenedores.
2. Asumimos que Docker está instalado en la máquina que ejecuta esta prueba. De
lo contrario, la prueba se omite.
3. Creamos una lista de WebDriver (conteniendo cinco instancias en este ejemplo).
4. Creamos un grupo de hilos usando el mismo tamaño que la lista de WebDriver.
5. Usamos el grupo de hilos para ejecutar en paralelo la evaluación del SUT.
6. Esperamos hasta que cada evaluación paralela termine. Usamos un método de
sincronización basado en un contador de latencia para hacer esto.
Seguridad
Una organización relevante en el ámbito de la seguridad del software es OWASP (Open
Web Application Security Project), una fundación sin fines de lucro que promueve
soluciones abiertas para mejorar la seguridad del software. Uno de los proyectos más
populares de OWASP es el Zed Attack Proxy (ZAP). OWASP ZAP es un escáner de
seguridad de aplicaciones web de código abierto utilizado para implementar
evaluaciones de vulnerabilidades (es decir, buscar problemas de seguridad) o pruebas
de penetración (es decir, un ciberataque simulado) para encontrar vulnerabilidades
explotables en aplicaciones web.
Podemos usar OWASP ZAP como una aplicación de escritorio independiente. La Figura
9-4 muestra una captura de pantalla de su interfaz gráfica de usuario.
Figura 9-4. Interfaz gráfica de usuario de OWASP ZAP
Esta interfaz gráfica proporciona diferentes características para escaneos
automatizados con el fin de detectar amenazas de seguridad que una aplicación web
podría enfrentar, tales como inyecciones SQL, scripting entre sitios (XSS) o falsificación
de solicitudes entre sitios (CSRF), por mencionar algunas.
Además de la aplicación independiente, podemos integrar una prueba de Selenium
WebDriver con ZAP. El Ejemplo 9-7 proporciona un caso de prueba que ilustra esta
integración. Los pasos requeridos para ejecutar esta prueba correctamente son:
1. Inicia OWASP ZAP en el localhost. Por defecto, OWASP inicia un proxy que escucha en
el puerto 8080. Puedes cambiar este puerto utilizando la interfaz gráfica de OWASP
mediante la opción del menú Herramientas → Opciones → Proxies Locales.
2. Desactiva la clave API (o copia su valor en la prueba de Selenium WebDriver). Puedes
cambiar este valor en la opción del menú Herramientas → Opciones → API.
3. Implementa una prueba de Selenium WebDriver que use OWASP ZAP como proxy
(como en el Ejemplo 9-7).
4. Ejecuta la prueba de Selenium WebDriver. En este punto, deberías ver el informe de
vulnerabilidades generado en la interfaz gráfica de ZAP.
Ejemplo 9-7. Prueba usando OWASP ZAP como escáner de seguridad
1. Configuramos la dirección y el puerto en el que el proxy local de ZAP está
escuchando.
2. Si la clave API de ZAP no está desactivada, debemos establecer su valor aquí.
3. Configuramos ZAP como un proxy de Selenium WebDriver.
4. Interactuamos con ZAP utilizando su API.
5. Después de la prueba, creamos un informe en HTML con las vulnerabilidades
encontradas durante la ejecución de la prueba de Selenium WebDriver. La Figura
9-5 muestra una captura de pantalla de este informe.
También podemos usar OWASP ZAP como una GUI independiente, como se
introdujo anteriormente. El beneficio potencial de la integración con Selenium
WebDriver podría ser reutilizar pruebas funcionales existentes para evaluar la
seguridad o realizar evaluaciones de seguridad automatizadas (por ejemplo, una
suite de pruebas de regresión ejecutada por un servidor CI).
Figura 9-5. Informe generado por ZAP después de ejecutar una prueba de Selenium
WebDriver
Accesibilidad
La accesibilidad digital se refiere a la capacidad de los usuarios con discapacidades para
utilizar de manera efectiva sistemas de software como sitios web, aplicaciones móviles,
etc. Una referencia esencial en este ámbito son las Pautas de Accesibilidad para el
Contenido Web (WCAG, por sus siglas en inglés), que son un conjunto de
recomendaciones estándar creadas por la Iniciativa de Accesibilidad Web del W3C (WAI)
que explican cómo hacer que el contenido web sea más accesible para las personas con
discapacidades.
Existen varios métodos para probar la accesibilidad de las aplicaciones web. El enfoque
más común consiste en verificar las recomendaciones de WCAG. Para ello, podemos
utilizar escáneres de accesibilidad automatizados como Axe, un motor de código abierto
para pruebas automatizadas de accesibilidad de aplicaciones web siguiendo las reglas
de WCAG. Axe proporciona una integración fluida con los enlaces Java de Selenium
WebDriver mediante una biblioteca auxiliar. El Ejemplo 9-8 muestra una prueba
utilizando esta biblioteca.
Ejemplo 9-8. Prueba utilizando Axe para generar un informe de accesibilidad
1. Analizamos la sesión actual de WebDriver con Axe. De esta manera, todas las
páginas cargadas en el navegador serán escaneadas por Axe.
2. Obtenemos un informe de las violaciones de accesibilidad encontradas.
3. Registramos cada violación en la salida estándar. En este ejemplo, los problemas
encontrados son:
color-contrast
Los elementos deben tener un contraste de color suficiente.
heading-order
Los niveles de encabezado solo deben aumentar de uno en uno.
image-alt
Las imágenes deben tener texto alternativo.
link-name
Los enlaces deben tener texto discernible.
4. Escribimos los resultados como un archivo JSON local.
Pruebas A/B
Las pruebas A/B son una forma de evaluación de usabilidad que compara variaciones de
la misma aplicación para descubrir cuál es más efectiva para los usuarios finales.
Diferentes productos comerciales facilitan características avanzadas para las pruebas
A/B en pruebas de Selenium WebDriver. Por ejemplo, Applitools Eyes proporciona una
comparación visual automatizada de múltiples variaciones de páginas web. Otra opción
es Optimizely, una empresa que ofrece herramientas para personalizar y experimentar
con pruebas A/B.
Otra forma de realizar pruebas A/B es utilizar la API de Selenium WebDriver "vanilla" y
condiciones personalizadas para las diferentes variaciones de una página web. El
Ejemplo 9-9 muestra una prueba básica siguiendo un enfoque manual para una página
web multivariada. Observe que esta prueba muestra una forma simple de implementar
una prueba A/B basada en la evaluación de las diferentes variaciones de la página.
Ejemplo 9-9. Prueba A/B básica utilizando Selenium WebDriver
1. Abrimos una página web de práctica multivariada. El contenido de esta página
se carga aleatoriamente el 50% de las veces.
2. Verificamos que las variaciones de la página sean las esperadas.
API Fluida
Como se introdujo en el Capítulo 1, Selenium es la tecnología base para otros marcos y
bibliotecas. Por ejemplo, podemos encontrar varias bibliotecas que envuelven Selenium
WebDriver para exponer una API fluida para crear pruebas end-to-end para aplicaciones
web. Un ejemplo de este tipo de biblioteca es Selenide, una biblioteca de código abierto
(licencia MIT) que define una API fluida concisa sobre Selenium WebDriver. Selenide
proporciona varios beneficios, como la espera automática de elementos web o el
soporte para aplicaciones AJAX.
Una diferencia relevante de una prueba con Selenide en comparación con Selenium
WebDriver es que Selenide maneja los objetos WebDriver internamente. Para eso, usa
WebDriverManager para resolver el controlador requerido (por ejemplo, chromedriver,
geckodriver, etc.), manteniendo la instancia de WebDriver en un hilo separado que se
cierra al final de la prueba. Como resultado, no se requiere el código base de prueba
necesario para la creación y terminación de los objetos WebDriver.
Ejemplo 9-10. Prueba usando Selenid
1. Usamos el método estático `open` proporcionado por Selenide para navegar a
una URL dada. Por defecto, Selenide usa un navegador Chrome local, aunque el
navegador puede ser cambiado usando una clase de configuración (por ejemplo,
`Configuration.browser = "firefox";`) o usando una propiedad del sistema Java
(por ejemplo, `-Dselenide.browser=firefox`).
2. El método de Selenide `$` permite localizar elementos web por selector CSS o
usando localizadores By de Selenium WebDriver. Esta línea de código usa el
segundo método para escribir texto en un campo de entrada.
3. Localizamos otro elemento web, esta vez por selector CSS, y hacemos clic en él.
4. Verificamos que el elemento web para el inicio de sesión exitoso esté presente
en la página y contenga el texto esperado.
Datos de Prueba
Una parte relevante de cualquier caso de prueba son los datos de prueba, es decir, los
datos de entrada utilizados para ejercitar el SUT (sistema bajo prueba). La selección de
datos de prueba adecuados es crucial para implementar pruebas efectivas.
Las técnicas clásicas en la teoría de pruebas para la selección de datos incluyen:
Particionamiento de Equivalencia
El proceso de probar dividiendo todos los posibles datos de prueba de entrada
en conjuntos de valores que asumimos se procesan de la misma manera.
Pruebas de Límite ( Boundary testing )
El proceso de probar entre los extremos extremos o entre particiones de los
valores de entrada. La idea básica de este enfoque es seleccionar los valores
límite representativos en un dominio de entrada (por ejemplo, por debajo del
mínimo, mínimo, justo por encima del mínimo, nominal, justo por debajo del
máximo, máximo y por encima del máximo).
Estos enfoques pueden ser imprácticos en pruebas end-to-end ya que el número
requerido de pruebas (y el tiempo de ejecución resultante) para llevar a cabo estas
estrategias puede ser enorme. Alternativamente, normalmente seleccionamos algunos
datos de prueba representativos manualmente para verificar el camino feliz (es decir,
pruebas positivas) y, opcionalmente, algunos datos de prueba para condiciones
inesperadas (pruebas negativas).
Otra alternativa para seleccionar datos de prueba es usar datos falsos, es decir, datos
aleatorios de diferentes dominios, como nombres personales, apellidos, direcciones,
países, correos electrónicos, números de teléfono, etc. Una alternativa simple para esto
es usar Java Faker, una portación de la popular gema Ruby Faker. El Ejemplo 9-11
muestra una prueba usando esta biblioteca. Esta prueba utiliza datos falsos para enviar
un formulario web disponible en el sitio de práctica. La Figura 9-6 muestra esta página
web después de enviar el formulario con esos datos falsos.
Ejemplo 9-11. Prueba usando Java Faker para generar diferentes tipos de datos falsos
1. Creamos una instancia de Java Faker.
2. Enviamos datos aleatorios de diferentes tipos (nombre, dirección, país, etc.).
3. Verificamos que los datos se envíen correctamente.
4. Comprobamos que no haya errores en la página.
Figura 9-6. Página de práctica usando datos falsos
Informe
Un informe de prueba es un documento que resume los resultados después de ejecutar
una suite de pruebas. Este documento típicamente contiene el número de pruebas
ejecutadas junto con sus veredictos (éxito, fallo, omisión) y el tiempo de ejecución. Hay
diferentes maneras de obtener un informe de prueba en nuestro proyecto Java. Por
ejemplo, al usar Maven, podemos crear un informe de prueba básico utilizando los
siguientes comandos:
1. Ejecutamos la prueba con Maven. Como resultado, el plugin Maven Surefire generó
un conjunto de archivos XML en la carpeta `target`. Estos archivos contienen los
resultados de la ejecución de las pruebas.
2. Convertimos los informes XML en un informe HTML. Puedes encontrar este informe
HTML en la carpeta `target/site` de tu proyecto.
3. Forzamos una copia del CSS y las imágenes requeridas en el informe HTML. La Figura
9-7 muestra una captura de pantalla de este informe.
Figura 9-7. Informe de prueba generado con Maven
También podemos generar un informe equivalente usando Gradle. Después de ejecutar
una suite de pruebas con esta herramienta de construcción, Gradle genera
automáticamente un informe HTML en la carpeta `build/reports`. La Figura 9-8 muestra
un ejemplo del informe de prueba generado al ejecutar un grupo de pruebas (usando el
comando en la terminal `gradle test --tests Hello*`).
Figura 9-8. Informe de prueba generado con Gradle
Además de Maven y Gradle, podemos usar bibliotecas de informes existentes para crear
informes más completos. Una posible alternativa es Extent Reports, una biblioteca para
crear informes de pruebas interactivos. Extent Reports ofrece ediciones profesionales
(comerciales) y comunitarias (de código abierto). El Ejemplo 9-12 muestra una prueba
utilizando esta última.
Ejemplo 9-12. Prueba usando Extent Reports para generar un informe HTML
1. Creamos una instancia del generador de informes de prueba.
2. Lo configuramos para que genere un informe en HTML.
3. Después de cada prueba, creamos una entrada en el informe de prueba usando
el nombre de la prueba como identificador. En JUnit 5, usamos `TestInfo`, un
resolvedor de parámetros incorporado que permite obtener información sobre
la prueba actual.
4. Como de costumbre, puedes encontrar el código fuente completo en el
repositorio de ejemplos. En particular, esta clase tiene dos métodos de prueba.
La Figura 9-9 muestra el informe de prueba resultante generado cuando se
ejecuta esta clase de prueba.
Figura 9-9. Informe de prueba generado con Extent Reports
Un inconveniente de Extent Reports es que necesitamos agregar explícitamente cada
prueba al generador de informes. Una posible solución a este problema es usar listeners
de prueba personalizados (como se explica en “Listeners de prueba” en la página 282)
para agrupar la lógica común de informes.
Otra posible biblioteca para generar informes de prueba completos es Allure, un marco
de informes de código abierto para generar informes de prueba para diferentes
lenguajes de programación, incluidos Java, Python y JavaScript, entre otros. Una
diferencia notable entre Allure y Extent Reports es que Allure utiliza un listener de
pruebas configurado en la herramienta de construcción Maven o Gradle (consulta el
Apéndice C para más detalles sobre esta configuración). De esta manera, no
necesitamos cambiar nuestra suite de pruebas para generar informes con Allure.
La Tabla 9-1 resume los comandos necesarios para crear informes con Allure usando
Maven y Gradle.
Tabla 9-1. Comandos de Maven y Gradle para generar informes de prueba con Allure
Figura 9-10. Informe de prueba generado con Allure y servido localmente
Desarrollo Guiado por Comportamiento
Como se introdujo en el Capítulo 1, el Desarrollo Guiado por Comportamiento (BDD, por
sus siglas en inglés) es una metodología de software que promueve el desarrollo y
prueba de sistemas de software utilizando escenarios de usuario de alto nivel.
Diferentes herramientas implementan la metodología BDD. Una de las más populares
es Cucumber. Cucumber ejecuta pruebas basadas en historias de usuario escritas en
Gherkin, una notación legible por humanos basada en lenguajes naturales (por ejemplo,
inglés y otros). Gherkin fue diseñado para ser utilizado por personas que no son
programadores (por ejemplo, clientes o usuarios finales), y sus principales palabras clave
se enumeran a continuación (consulta el manual del usuario de Gherkin para obtener
más información):
Feature
Descripción de alto nivel de la característica del software que se prueba.
Scenario
Prueba concreta que ilustra una regla de negocio. Los escenarios describen
diferentes piezas de información (llamadas pasos en la jerga de Gherkin), tales
como:
Given
Condiciones previas y estado inicial
When
Acciones del usuario
And
Acciones adicionales del usuario
Then
Resultado esperado
El Ejemplo 9-13 muestra una característica de Gherkin que contiene dos escenarios para
una prueba de inicio de sesión (exitoso y fallido).
Ejemplo 9-13. Escenarios de Gherkin para iniciar sesión en el sitio de práctica
1. Descripción de la característica
2. Primer escenario (inicio de sesión exitoso)
3. Navegador a utilizar
4. URL de la página web
5. Conjunto de acciones (ingresar credenciales y hacer clic en el botón de envío)
6. Mensaje esperado
7. Segundo escenario (inicio de sesión fallido)
Para ejecutar los escenarios de Gherkin como casos de prueba, primero debemos crear
las definiciones de pasos correspondientes. Una definición de paso es un código que
conecta y ejercita el SUT utilizando la información especificada en el escenario. En Java,
utilizamos anotaciones (como @Given, @Then, @When o @And) para decorar los
métodos que implementan cada paso. Estas anotaciones contienen un valor de cadena
para mapear cada definición de paso y los parámetros. El Ejemplo 9-14 muestra una
definición de pasos para el escenario de Gherkin definido en el Ejemplo 9-13. Usamos la
API de Selenium WebDriver para implementar las acciones necesarias para la
navegación, la interacción con elementos web, etc.
Ejemplo 9-14. Pasos para iniciar sesión en el sitio de práctica con Cucumber
1. Usamos el primer paso para crear una instancia de WebDriver.
2. Abrimos la URL.
3. Escribimos las credenciales.
4. Hacemos clic en el botón de Enviar.
5. Verificamos que el mensaje esperado esté en la página.
Finalmente, necesitamos ejecutar nuestra definición de pasos como un caso de prueba.
Como es habitual, creamos esa prueba usando un marco de pruebas unitarias. Esta
prueba depende del marco de pruebas unitarias en la integración con Cucumber. En
otras palabras, el código requerido es diferente en JUnit 4 (ver Ejemplo 9-15), JUnit 5
(ver Ejemplo 9-16) y TestNG (ver Ejemplo 9-17). Las pruebas resultantes se ejecutan de
la manera habitual (es decir, usando la terminal o un IDE).
Selenium-Jupiter no proporciona características adicionales para la integración
con Cucumber, por lo que el procedimiento predeterminado de JUnit 5 es el
mismo en el proyecto Selenium-Jupiter en el repositorio de ejemplos.
Ejemplo 9-15. Prueba de Cucumber usando JUnit 4
Usamos el runner de Cucumber para ejecutar las definiciones de pasos como casos de
prueba.
Especificamos la ubicación de los escenarios de Gherkin. En este caso, la característica
es la carpeta `io/github/bonigarcia` en el classpath del proyecto (concretamente, en la
carpeta `src/test/resources`). Esta anotación también especifica el paquete inicial para
buscar el código de enlace (es decir, las definiciones de pasos).
Ejemplo 9-16. Prueba de Cucumber usando JUnit 5
1. Necesitamos usar el módulo de suite de JUnit 5 para ejecutar pruebas de
Cucumber.
2. Incluimos el motor de Cucumber en la Plataforma JUnit.
3. Especificamos la ruta para las características dentro del classpath del proyecto.
4. Establecemos el paquete inicial para buscar el código de enlace.
Ejemplo 9-17. Prueba de Cucumber usando TestNG
1. Especificamos la ubicación de los escenarios de Gherkin y el paquete para buscar
el código de enlace.
2. Extendemos una clase de prueba principal de TestNG para las pruebas de
Cucumber.
Marcos de Trabajo para la Web
Los marcos de trabajo para la web son marcos de software diseñados para apoyar el
desarrollo de aplicaciones y servicios web. Uno de los marcos de trabajo más populares
para el lenguaje Java es el Spring Framework. Spring es un marco de código abierto para
construir aplicaciones Java, incluidas aplicaciones web y servicios empresariales. La
tecnología central de Spring se conoce como Inversión de Control (IoC), que es un
procedimiento para crear instancias fuera de la clase en la que se utilizan estos objetos.
Estos objetos, llamados beans o componentes en la jerga de Spring, se inyectan más
tarde bajo demanda como dependencias por el contenedor IoC de Spring.
Los siguientes ejemplos muestran pruebas básicas que verifican una aplicación web local
creada con Spring Boot, un subproyecto del portafolio de Spring que simplifica el
desarrollo de aplicaciones basadas en Spring gracias a las características de convención
sobre configuración y autodetección. Además, Spring Boot proporciona un servidor web
integrado para facilitar el desarrollo de aplicaciones web. La integración con Selenium
WebDriver en este tipo de proyecto facilita el proceso de prueba de aplicaciones web
basadas en Spring al desplegar automáticamente en el servidor web integrado por cada
caso de prueba.
El código para integrar Spring Boot con Selenium WebDriver y los marcos de pruebas
unitarias utilizados en este libro es diferente. El Ejemplo 9-18 muestra una prueba que
integra Spring Boot y JUnit 4. El Ejemplo 9-19 muestra las diferencias al usar TestNG, el
Ejemplo 9-20 ilustra cómo usar JUnit 5 y, finalmente, el Ejemplo 9-21 muestra una
prueba de Spring basada en JUnit 5 con Selenium-Jupiter.
Ejemplo 9-18. Prueba usando Spring Boot y JUnit 4
1. Usamos el runner de Spring en JUnit 4.
2. Usamos una anotación de Spring Boot para pruebas para definir el nombre de la
clase de Spring Boot. Además, especificamos que la aplicación web se despliega
utilizando un puerto disponible aleatorio.
3. Inyectamos el puerto de la aplicación web como un atributo de la clase.
Ejemplo 9-19. Prueba usando Spring Boot y TestNG
1. Usamos la anotación `@SpringBootTest` de la misma manera en JUnit 4.
2. Extendemos una clase padre de TestNG para ejecutar esta prueba usando el
contexto de Spring.
Ejemplo 9-20. Prueba usando Spring Boot y JUnit
1. Usamos la extensión de Spring para JUnit 5 para integrar el contexto de Spring
en una prueba de Jupiter.
2. Usamos Spring Boot para iniciar nuestro contexto de aplicación Spring como en
los ejemplos anteriores.
Ejemplo 9-21. Prueba usando Spring Boot y JUnit 5 más Selenium-Jupiter
1. En el caso de Selenium-Jupiter, usamos dos extensiones de JUnit 5 (para Spring
y Selenium WebDriver).
2. Como es habitual en Selenium-Jupiter, usamos el mecanismo de resolución de
parámetros de JUnit 5 para declarar el tipo de instancia de WebDriver que
usamos en esta prueba.
Resumen y Perspectivas
Este capítulo ofreció una visión práctica para integrar diferentes tecnologías (como
herramientas, bibliotecas y marcos) en el desarrollo de pruebas end-to-end para
aplicaciones web con Selenium WebDriver. Primero, usamos Awaitility (una biblioteca
para manejar operaciones asíncronas) para esperar hasta que los archivos se descarguen
con Selenium WebDriver. Una biblioteca alternativa para ejecutar el mismo caso de uso
(es decir, la descarga de archivos) es Apache HttpClient. Luego, utilizamos el proxy
BrowserMob para interceptar el tráfico HTTP intercambiado por una prueba de
Selenium WebDriver. El siguiente grupo de tecnologías se centró en habilitar pruebas
no funcionales con Selenium WebDriver: BrowserMob (para crear un plan de prueba
JMeter para pruebas de rendimiento), OWASP ZAP (para pruebas de seguridad) y Axe
(para pruebas de accesibilidad). A continuación, usamos la API fluida proporcionada por
Selenide, Java Faker, para crear datos de prueba falsos para pruebas de Selenium
WebDriver, y Extent Reports y Allure para generar informes de prueba detallados.
Finalmente, descubrimos cómo integrar Cucumber (un marco BDD) y Spring (un marco
para Java y aplicaciones web) con Selenium WebDriver.
El siguiente capítulo concluye este libro presentando marcos complementarios a
Selenium WebDriver, a saber, REST Assured (para probar servicios REST) y Appium (para
probar aplicaciones móviles). Finalmente, el capítulo presenta varias alternativas
populares a Selenium WebDriver en el espacio de automatización de navegadores:
Cypress, WebDriverIO, TestCafe, Puppeteer y Playwright.
CAPÍTULO 10
Más allá de Selenium
Este capítulo cierra el libro presentando varias tecnologías complementarias a Selenium.
Primero, analizaremos los conceptos básicos de las aplicaciones móviles e
introduciremos Appium, un popular marco de pruebas para pruebas móviles. Luego,
aprenderás cómo probar servicios REST (Transferencia de Estado Representacional) con
una biblioteca de Java de código abierto llamada REST Assured. Finalmente, se te
presentarán herramientas alternativas a Selenium WebDriver para implementar
pruebas end-to-end para aplicaciones web, a saber: Cypress, WebDriverIO, TestCafe,
Puppeteer y Playwright.
Aplicaciones Móviles
Las aplicaciones móviles (generalmente llamadas aplicaciones móviles, o simplemente
apps) son aplicaciones de software diseñadas para ejecutarse en dispositivos móviles,
como teléfonos inteligentes, tabletas o dispositivos portátiles. Existen dos sistemas
operativos principales para dispositivos móviles:
Android
Un sistema operativo móvil de código abierto (licencia Apache 2.0) basado en una
versión modificada de Linux. Fue desarrollado inicialmente por una startup llamada
Android, adquirida por Google en 2005.
iOS
Un sistema operativo móvil propietario creado por Apple exclusivamente para su
hardware (por ejemplo, iPhone, iPad o Watch).
Una forma común de clasificar las aplicaciones móviles es la siguiente:
Aplicaciones nativas
Aplicaciones móviles desarrolladas para un sistema operativo móvil particular (por
ejemplo, Android o iOS).
Aplicaciones basadas en la web
Aplicaciones web renderizadas en un navegador móvil (por ejemplo, Chrome, Safari
o Firefox Mobile). Estas aplicaciones están diseñadas para ser responsivas (es decir,
adaptables a diferentes tamaños de pantalla y áreas de visualización).
Aplicaciones híbridas
Aplicaciones móviles desarrolladas usando estándares web del lado del cliente (es
decir, HTML, CSS y JavaScript) y desplegadas en dispositivos móviles usando un
contenedor nativo llamado webview. Ejemplos de marcos que permiten el
desarrollo de aplicaciones híbridas son Ionic, React Native o Flutter.
Aplicaciones web progresivas (PWAs)
Aplicaciones web construidas con APIs modernas de estándares web (para
instalabilidad, capacidad de respuesta, etc.) destinadas a funcionar en múltiples
plataformas, incluyendo dispositivos de escritorio y móviles.
Pruebas Móviles
Las pruebas son un proceso esencial en el desarrollo de aplicaciones móviles. Las
pruebas móviles implican diferentes desafíos como la compatibilidad del hardware, la
conectividad de red o las especificidades del sistema operativo. Los enfoques diferentes
para llevar a cabo pruebas móviles incluyen:
Uso de navegadores de escritorio con emulación móvil
Podemos usar Selenium WebDriver para este tipo de pruebas móviles. Para ello, puedes
usar características específicas del navegador (como se explica en “Emulación de
Dispositivos” en la página 153) o usar el CDP con navegadores basados en Chromium
(como se explica en “Emulación de Dispositivos” en la página 187).
Uso de dispositivos virtuales
Existen dos tipos de dispositivos móviles virtuales:
Emuladores
Aplicaciones de escritorio que virtualizan todos los aspectos de los dispositivos
móviles, incluyendo el hardware y el sistema operativo.
Simuladores
Aplicaciones de escritorio que imitan ciertas características de un sistema
operativo móvil. Están principalmente destinadas a iOS ya que los dispositivos
Android se emulan fácilmente.
Uso de dispositivos reales
Uso de dispositivos reales y sus APIs nativas de Android o iOS en condiciones reales.
Appium
Appium es un marco de automatización de pruebas de código abierto para aplicaciones
móviles. Appium proporciona una API multiplataforma que permite probar aplicaciones
nativas, híbridas y web móviles para iOS y Android en dispositivos virtuales o reales.
Además, Appium permite pruebas automatizadas para aplicaciones de escritorio en
Windows y macOS.
La historia de Appium comenzó en 2011 cuando Dan Cuellar creó una herramienta de
automatización para aplicaciones iOS desarrolladas en C# llamada iOSAuto. Conoció a
Jason Huggins (el co-creador de Selenium) durante el SeleniumConf 2012 en Londres.
Jason contribuyó al proyecto añadiendo un servidor web y utilizando el protocolo de
comunicación WebDriver sobre HTTP, haciendo iOSAuto compatible con cualquier
cliente Selenium WebDriver. Cambiaron el nombre del proyecto a Appium (el Selenium
para Apps). En enero de 2013, Sauce Labs decidió apoyar a Appium y proporcionar más
recursos para desarrolladores. El nuevo equipo reescribió Appium usando Node.js ya
que es un marco conocido y eficiente para el lado del servidor.
Como se ilustra en la Figura 10-1, Appium sigue una arquitectura cliente-servidor.
Appium es un servidor web que expone una API REST que lleva a cabo una sesión
automatizada en aplicaciones móviles o de escritorio. Para ello, el servidor Appium
recibe solicitudes entrantes de los clientes, ejecuta esos comandos en los
dispositivos/apps de destino y responde con una respuesta HTTP que representa el
resultado de la ejecución del comando. Las bibliotecas cliente de Appium se comunican
con el servidor Appium usando el Protocolo de Comunicación JSON Móvil (una extensión
oficial del protocolo WebDriver original). El servidor Appium y su cliente también usan
la especificación WebDriver W3C. Existen diferentes bibliotecas cliente de Appium. La
Tabla 10-1 resume estas bibliotecas, tanto mantenidas oficialmente por el proyecto
Appium como por la comunidad.
Figura 10-1. Arquitectura de Appium
Tabla 10-1. Bibliotecas cliente de Appium
En Appium, el soporte para la automatización de una plataforma particular se
proporciona a través de un componente llamado "driver" en la jerga de Appium. Estos
drivers estaban estrechamente acoplados con el Servidor de Appium en la versión 1. Sin
embargo, en Appium 2 (la versión más reciente de Appium al momento de escribir este
texto), estos drivers están segregados del Servidor de Appium (ver Figura 10-1) y se
instalan por separado.
Tabla 10-2. Drivers de Appium
Una prueba básica de Appium
Esta sección presenta un caso de prueba básico utilizando el servidor de Appium 2 y el
cliente de Appium para Java. Para simplificar, utilizo el Driver UiAutomator2 y un
dispositivo Android emulado. El Sistema Bajo Prueba (SUT) será una aplicación web,
concretamente, el sitio de práctica utilizado a lo largo de este libro. Las llamadas a los
clientes de Appium para Java están incrustadas en los diferentes marcos de pruebas
unitarias utilizados en el resto de los ejemplos (es decir, JUnit 4 y 5, TestNG y Selenium-
Jupiter). Como de costumbre, puedes encontrar el código fuente completo en el
repositorio de ejemplos. Los requisitos para ejecutar esta prueba son:
1. Instalar el servidor de Appium 2.
2. Instalar el Driver UiAutomator2.
3. Instalar el SDK de Android (es decir, el kit de desarrollo de software oficial para
Android). Puedes instalar fácilmente este SDK al instalar Android Studio en tu
computadora.
4. Crear un Dispositivo Virtual Android (AVD) utilizando el Administrador de AVD en
Android Studio. La Figura 10-2 muestra la opción del menú para abrir esta herramienta,
y la Figura 10-3 muestra el dispositivo virtual utilizado en la prueba (un teléfono móvil
Nexus 5 usando el nivel de API 30 de Android).
5. Iniciar el dispositivo virtual y el servidor de Appium.
Figura 10-2. Android Studio
Figura 10-3. AVD Manager
Como se explicó anteriormente, el servidor de Appium es una aplicación de Node.js. Por
lo tanto, necesitas tener Node.js instalado en tu sistema para ejecutar Appium. Los
siguientes comandos resumen cómo instalar el servidor de Appium 2 y el controlador
UiAutomator2, y cómo iniciar el servidor de Appium:
1. Usamos npm (el administrador de paquetes predeterminado para Node.js) para
instalar Appium 2.
2. Usamos Appium para instalar el controlador UiAutomator2.
3. Iniciamos el servidor de Appium (por defecto, escucha en el puerto 4723).
Incluimos una bandera para permitir que Appium gestione los controladores de
navegador necesarios (por ejemplo, chromedriver) para automatizar
aplicaciones web (al igual que en Selenium WebDriver).
El Ejemplo 10-1 muestra una prueba completa utilizando el cliente de Java de Appium.
Como puedes ver, esta prueba es bastante similar a las pruebas regulares de Selenium
WebDriver explicadas en este libro. La principal diferencia en este caso es que usamos
una instancia de `AppiumDriver`, una clase proporcionada por el cliente de Java de
Appium. Esta clase extiende la clase `RemoteWebDriver` de la API de Selenium
WebDriver. Por lo tanto, podemos aprovechar la API de Selenium WebDriver para
probar aplicaciones web en dispositivos móviles. La Figura 10-4 muestra el dispositivo
móvil emulado (un Nexus 5) durante esta prueba.
Figura 10-4. Dispositivo Android
Ejemplo 10-1. Prueba utilizando el cliente Java de Appium
1. Especificamos la URL del servidor de Appium.
2. Realizamos una suposición utilizando el endpoint /status de la URL del servidor
de Appium. Si esta URL no está en línea, se omite la prueba.
3. Utilizamos opciones de Chrome para especificar capacidades.
4. La primera capacidad obligatoria al usar Appium es el nombre de la plataforma
(Android en este caso).
5. La siguiente capacidad requerida es el nombre del dispositivo. Este nombre debe
coincidir con el nombre definido en el administrador de AVD (ver Figura 10-3).
6. La última capacidad obligatoria es el nombre del driver (UiAutomator2 en este
caso)
7. Creamos una instancia de AppiumDriver utilizando la URL del servidor de Appium
y las opciones del navegador.
8. Utilizamos el objeto driver para ejercer el SUT como de costumbre.
Servicios REST
REST (Transferencia de Estado Representacional) es un estilo arquitectónico para
diseñar servicios distribuidos. Roy Fielding acuñó este término en su disertación doctoral
de 2000. REST es una forma popular de crear servicios web sobre el protocolo HTTP.
REST sigue una arquitectura cliente-servidor. El servidor maneja un conjunto de
recursos, escuchando las solicitudes entrantes realizadas por los clientes. Estos recursos
son los bloques de construcción de los servicios REST y definen el tipo de información
transferida. Cada recurso está identificado de manera única. En HTTP, utilizamos URLs
(también conocidas como endpoints) para acceder a recursos individuales. Cada recurso
tiene una representación, una explicación legible por máquina del estado actual de un
recurso. Utilizamos un formato de intercambio de datos para definir representaciones,
como JSON, YAML o XML. Los servicios REST exponen un conjunto de acciones sobre los
recursos, como CRUD (crear, recuperar, actualizar y eliminar). Podemos utilizar los
métodos HTTP (los llamados verbos) para mapear acciones REST. La Tabla 10-3 resume
los métodos HTTP utilizados para crear servicios REST. Finalmente, podemos usar los
códigos de estado HTTP para identificar la respuesta asociada con las acciones REST. La
Tabla 10-4 resume los códigos de estado HTTP típicos utilizados en REST. La Figura 10-5
muestra una secuencia de solicitudes y respuestas de un servicio REST de ejemplo que
utiliza diferentes métodos HTTP y códigos de respuesta.
Tabla 10-3. Métodos HTTP para crear servicios REST
Tabla 10-4. Códigos de estado HTTP para crear servicios REST
Figura 10-5. Ejemplo de un servicio REST
REST Assured
Las APIs REST son omnipresentes. Como es habitual, se recomienda encarecidamente
implementar pruebas automatizadas para verificar estos servicios, por ejemplo,
utilizando REST Assured. REST Assured es una biblioteca Java de código abierto (licencia
Apache 2.0) para probar servicios REST. Proporciona una API fluida para probar y validar
servicios REST. Una forma conveniente de crear aserciones legibles con REST Assured es
generar POJOs (Plain Old Java Objects) para mapear las respuestas REST (por ejemplo,
en formato JSON) como clases Java. Luego, podemos usar una biblioteca como AssertJ
para verificar las condiciones esperadas utilizando los accesores (es decir, los métodos
getter) de estos POJOs. El Ejemplo 10-2 muestra un caso de prueba utilizando este
enfoque. El Ejemplo 10-3 contiene el POJO utilizado en esta prueba.
Ejemplo 10-2. Prueba utilizando REST Assured
1. Utilizamos REST Assured para solicitar un servicio REST público en línea
utilizando el método HTTP GET. Esta línea también verifica el código de estado
esperado (200) y convierte la carga de respuesta (en JSON) a una clase Java
(mostrada en el Ejemplo 10-3).
2. Aseguramos que la lista de encabezados (usando el método de acceso
correspondiente) contenga un valor clave dado.
3. Aseguramos que el origen no esté en blanco.
Ejemplo 10-3. Clase POJO para probar un servicio REST
1. Este POJO define un conjunto de atributos para mapear la carga de respuesta en
JSON usando Java.
2. Definimos accesores (getters) y mutadores (setters) para cada atributo de la
clase. Los IDE modernos permiten generar estos métodos automáticamente a
partir de los atributos de la clase.
Alternativas a Selenium
Selenium es actualmente la tecnología líder para implementar pruebas de extremo a
extremo. Sin embargo, no es la única alternativa disponible. Esta sección ofrece una
visión general de otros marcos y bibliotecas que también permiten implementar
pruebas de extremo a extremo para aplicaciones web. Además, las siguientes
subsecciones revisan los principales pros y contras de cada una de estas alternativas. En
mi opinión, Selenium sigue siendo la solución de referencia para las pruebas de extremo
a extremo, ya que está construido para promover los estándares web (es decir, el W3C
WebDriver y WebDriver BiDi) para apoyar el proceso de automatización y, por lo tanto,
garantiza la compatibilidad entre navegadores.
Cypress
Cypress es un marco de pruebas automatizadas de extremo a extremo basado en
JavaScript. Como se ilustra en la Figura 10-6, la arquitectura de Cypress consta de un
proceso Node.js más una herramienta llamada Test Runner que se ejecuta en un
navegador.
Figura 10-6. Arquitectura de Cypress
El Test Runner es una aplicación web interactiva que integra una prueba basada en
Mocha (un marco de pruebas unitarias en JavaScript) más la aplicación web bajo prueba
en dos iframes. El código de prueba y el código de la aplicación se ejecutan en la misma
pestaña del navegador (es decir, en el mismo bucle de JavaScript). El proceso de Node.js
se comunica con el Test Runner mediante un WebSocket. Finalmente, el proceso de
Node.js actúa como un proxy para el tráfico HTTP entre el Test Runner y la aplicación
web bajo prueba.
El Test Runner de Cypress es de código abierto y está licenciado bajo los términos de la
licencia MIT. El equipo de Cypress también proporciona soporte comercial para
características avanzadas. Una de ellas es el Cypress Dashboard, una aplicación web
gestionada en la nube que permite rastrear las pruebas ejecutadas en el Test Runner. La
Tabla 10-5 resume algunos de los pros y contras más relevantes de Cypress.
Tabla 10-5. Pros y contras de Cypress
Los siguientes comandos muestran cómo instalar Cypress localmente y ejecutarlo.
Después de ejecutar estos comandos, verás la interfaz gráfica de Cypress (como en la
Figura 10-7). Puedes usar esta interfaz gráfica para ejecutar pruebas con Cypress.
1. Podemos usar npm (el gestor de paquetes predeterminado en Node.js) para
instalar Cypress.
2. Podemos usar npx (un ejecutador de paquetes de npm) para ejecutar el proceso
de Cypress.
Figura 10-7. Interfaz gráfica de Cypress
Por defecto, en la interfaz gráfica de Cypress, puedes encontrar ejemplos introductorios
de pruebas en las carpetas `1-getting-started` y `2-advanced-examples`. Además,
podemos crear nuevas pruebas usando el botón **New Spec File**. Por ejemplo, el
Ejemplo 10-4 muestra una prueba básica nueva usando Cypress (es decir, un "hola
mundo" en Cypress). Esta prueba se llama `hello-worldcypress.spec.js` (la extensión
`.spec.js` se usa por defecto en las pruebas de Mocha) y se guarda en la ruta
`cypress/integration` de la instalación de Cypress. La Figura 10-8 muestra una captura
de pantalla del Test Runner de Cypress durante la ejecución de esta prueba.
Ejemplo 10-4. Prueba de "Hola Mundo" usando Cypress
1. Abrimos la página de inicio de sesión en el sitio de práctica.
2. Escribimos las credenciales correctas (nombre de usuario y contraseña).
3. Hacemos clic en el botón de Enviar.
4. Verificamos que la página resultante contiene el mensaje de inicio de sesión exitoso.
5. Tomamos una captura de pantalla del navegador.
Figura 10-8. Cypress Test Runner
WebDriverIO
WebDriverIO es un marco de pruebas automatizadas para aplicaciones web y móviles.
Es completamente de código abierto (licencia MIT) y se basa en estándares web como
el protocolo W3C WebDriver. La Figura 10-9 ilustra su arquitectura. WebDriverIO está
escrito en JavaScript y se ejecuta en Node.js. Utiliza varios servicios para soportar la
automatización: chromedriver (para navegadores Chrome locales), Selenium Server
(para otros navegadores), Appium Server (para dispositivos móviles), Chrome DevTools
(para navegadores locales basados en Chromium usando el CDP) y proveedores en la
nube (como Sauce Labs, BrowserStack o TestingBot). Estos servicios manipulan los
navegadores y dispositivos móviles correspondientes. La Tabla 10-6 resume algunos de
los pros y contras de WebDriverIO.
Figura 10-9. Arquitectura de WebDriverIO
Tabla 10-6. Pros y contras de WebDriverIO
El siguiente comando de npm instala WebDriverIO localmente. Este instalador muestra
un asistente en la línea de comandos que solicita varias opciones, como los servicios
(chromedriver, Selenium Server, Appium Server, CDP o proveedores de nube), el marco
de pruebas (Mocha, Jasmine o Cucumber) o la herramienta de reportes (JUnit o Allure,
entre otros):
Cuando el comando anterior termina, podemos crear nuestras pruebas personalizadas.
Por ejemplo, el Ejemplo 10-5 muestra una prueba básica de WebDriverIO usando
Mocha. Ubicamos esta prueba en la carpeta `test` de la estructura del proyecto y la
ejecutamos mediante el siguiente comando:
Ejemplo 10-5. Prueba de hello world usando WebDriverIO
TestCafe
TestCafe es una herramienta de automatización de pruebas cruzadas de navegador de
código abierto (licencia MIT). La idea principal de TestCafe es evitar el uso de
controladores externos para soportar el proceso de automatización y emular las
acciones del usuario utilizando una arquitectura híbrida cliente-servidor (ver Figura 10-
10). El lado del servidor está implementado en Node.js y contiene un proxy que
intercepta el tráfico HTTP con la aplicación web que se está probando. Las pruebas de
TestCafe también están escritas como scripts de Node.js y se ejecutan en el lado del
servidor. Los scripts de automatización que emulan la actividad del usuario se ejecutan
en el lado del cliente en la página probada en el navegador. La Tabla 10-7 resume
algunas ventajas y limitaciones de TestCafe.
Figura 10-10. Arquitectura de TestCafe
Tabla 10-7. Ventajas y desventajas de TestCafe
Podemos instalar TestCafe fácilmente usando npm. Luego, podemos utilizar la
herramienta de línea de comandos de TestCafe para ejecutar los scripts de TestCafe
desde la línea de comandos. El siguiente fragmento ilustra cómo:
1. Instalamos TestCafe globalmente.
2. Ejecutamos un script básico de TestCafe (Ejemplo 10-6) usando Chrome como
navegador.
Ejemplo 10-6. Prueba Hello World usando TestCafe
Puppeteer
Puppeteer es una biblioteca de Node.js de código abierto (licencia MIT) que proporciona
una API de alto nivel para controlar navegadores basados en Chromium a través del
Protocolo DevTools. Puppeteer es mantenido por el equipo de DevTools de Chrome en
Google. La Figura 10-11 ilustra la arquitectura de Puppeteer. La Tabla 10-8 presenta las
principales ventajas y desventajas de Puppeteer.
Figura 10-11. Arquitectura de Puppeteer
Tabla 10-8. Ventajas y desventajas de Puppeteer
Podemos instalar Puppeteer usando npm. Luego, necesitamos usar Node.js para
ejecutar las pruebas con Puppeteer (por ejemplo, el Ejemplo 10-7). El siguiente
fragmento muestra estos comandos:
Ejemplo 10-7. Prueba de "Hola Mundo" usando Puppeteer
1. Puppeteer ejecuta los navegadores en modo sin cabeza (headless) por defecto.
Se puede configurar para usar navegadores con interfaz gráfica simplemente
cambiando esta declaración a:
Playwright
Playwright es una biblioteca de código abierto (licencia Apache 2.0) para la
automatización de navegadores, respaldada por Microsoft. Playwright se inició
originalmente como una biblioteca de Node.js. Además de JavaScript, ahora soporta
otros lenguajes de programación, como Python, Java y .NET C#.
Playwright admite tres tipos de motores de navegador: Chromium, Firefox y WebKit (es
decir, el motor de navegador utilizado por Safari). La idea de soportar estos motores es
que cubren la mayor parte del mercado de navegadores. Por lo tanto, el equipo de
Playwright mantiene una versión parcheada de estos navegadores que expone las
capacidades necesarias para habilitar la automatización. Estas versiones parcheadas
proporcionan una arquitectura basada en eventos para acceder a diferentes procesos
internos del navegador (por ejemplo, los procesos de renderizado, red, navegador o de
servicio). La Figura 10-12 ilustra esta arquitectura. La Tabla 10-9 contiene algunos de los
pros y los contras más relevantes de Playwright.
Figura 10-12. Arquitectura de Playwright
Tabla 10-9. Pros y contras de Playwright
Para usar Playwright, primero necesitamos instalar los binarios de navegador
parcheados. Podemos usar npm para ello. El siguiente comando descarga los binarios
de navegador adecuados para Chromium, Firefox y WebKit para el sistema operativo en
el que se ejecute este comando (Windows, Linux y macOS son compatibles):
Luego podemos implementar scripts de Playwright usando una API soportada. Por
ejemplo, al usar la API de JavaScript, podemos usar un corredor de pruebas de terceros
(por ejemplo, Jest, Jasmine, Mocha, etc.) o usar Playwright Test (es decir, el corredor de
pruebas proporcionado por el equipo de Playwright). Para usar este último, necesitamos
instalarlo de la siguiente manera:
El Ejemplo 10-8 contiene una prueba básica de Playwright en JavaScript para ser
ejecutada con el corredor de Playwright. Este comando supone que esta prueba
(llamada `helloworldplaywright.spec.mjs`) se encuentra en el directorio `tests`.
Podemos invocar el corredor de Playwright como se muestra en el siguiente fragmento
para ejecutar esta prueba. Este comando ejecuta las pruebas de Playwright en modo
headless por defecto. Para ejecutar los navegadores en modo no headless, debes incluir
la opción `--headed` al final del comando:
Ejemplo 10-8. Prueba de "Hola Mundo" usando Playwright
Resumen y Comentarios Finales
El desarrollo web es una disciplina heterogénea que involucra muchas tecnologías
diferentes, como el lado del cliente, el lado del servidor o la integración con servicios
externos, por nombrar algunas. Por esta razón, en este capítulo se introdujeron dos
tecnologías complementarias a Selenium que pueden ser útiles para probar aplicaciones
web: Appium (un marco de automatización de pruebas de código abierto para
aplicaciones móviles) y REST Assured (una biblioteca de Java de código abierto para
probar servicios REST). También se aprendieron los conceptos básicos de herramientas
alternativas para implementar pruebas de extremo a extremo para aplicaciones web, a
saber, Cypress, WebDriverIO, TestCafe, Puppeteer y Playwright. Aunque estas
alternativas ofrecen ventajas notables en comparación con Selenium (por ejemplo,
espera automática), en mi opinión, Selenium proporciona un modelo de automatización
más completo ya que está construido sobre estándares web, como el W3C WebDriver y
WebDriver BiDi. Además, el proyecto Selenium participa activamente en el desarrollo
de estas especificaciones.
Este capítulo concluye tu recorrido a través del desarrollo de pruebas de extremo a
extremo con Selenium. El siguiente paso es poner en práctica todo el conocimiento
presentado en este libro en tus proyectos. De esta manera, podrás construir tu propio
marco de automatización para tu equipo, proyecto, empresa, etc. Hay muchas
decisiones que necesitas tomar, como la configuración del proyecto (por ejemplo,
Maven, Gradle), el marco de pruebas unitarias (por ejemplo, JUnit, TestNG), la
infraestructura del navegador (por ejemplo, Docker, proveedores de nube) y la
integración con utilidades de terceros. Para manejar toda esta complejidad, como
palabra final, te recomiendo experimentar con los ejemplos proporcionados en este
libro. En otras palabras: clona el repositorio, ejecuta las pruebas y edita el código para
satisfacer tus necesidades. Mantendré el repositorio en GitHub después de la
publicación del libro. Y recuerda: es un proyecto de software de código abierto, así que,
si deseas contribuir, siéntete libre de crear una solicitud de extracción para mejorarlo.
APÉNDICE A
Novedades en Selenium 4
Este apéndice proporciona un resumen de las novedades disponibles en Selenium 4. El
objetivo de este contenido es doble. Primero, enumera las nuevas características en los
componentes centrales de la suite de Selenium (es decir, WebDriver, Driver e IDE),
proporcionando un enlace al capítulo del libro que explica cada aspecto. Además, este
apéndice describe otros aspectos del proyecto Selenium que cambiaron con Selenium
4, como la documentación y la gobernanza. El segundo objetivo es identificar las partes
obsoletas y las nuevas características correspondientes al migrar de Selenium 3 a 4.
Selenium WebDriver
La primera versión estable de Selenium WebDriver 4.0.0 fue lanzada el 13 de octubre de
2021. La Tabla A-1 resume las características nuevas más relevantes en esta versión en
comparación con la versión estable anterior (es decir, Selenium WebDriver 3.141.59).
Tabla A-1. Novedades en Selenium WebDriver 4
Guía de Migración
Esta sección resume los cambios que necesitas hacer para migrar una base de código
existente que utiliza Selenium WebDriver 3 a la versión 4.
Localizadores
Los métodos de utilidad para encontrar elementos (interfaces `FindsBy`) han sido
eliminados en Selenium WebDriver 4. La Tabla A-2 compara la API antigua y nueva para
encontrar Web Elements en Selenium WebDriver. Puedes encontrar más detalles sobre
esta característica en “Ubicación de WebElements” en la página 59.
Tabla A-2. Migración de la ubicación de elementos web en Selenium WebDriver 4
Gestos del usuario
La clase `Actions` permite emular gestos complejos del usuario (como arrastrar y soltar,
pasar el cursor, movimientos del ratón, etc.). Como se ilustra en la Tabla A-3, la API
expuesta por esta clase se ha simplificado en Selenium WebDriver 4. “Gestos del
Usuario” en la página 86 contiene más información y ejemplos sobre esta clase.
Tabla A-3. Migración de la clase Actions en Selenium WebDriver 4
Esperas y tiempos de espera (Waits and timeouts)
Los parámetros para especificar los tiempos de espera han cambiado de `TimeUnit` a
`Duration`. La Tabla A-4 describe este cambio. Puedes ver más detalles en “Estrategias
de Espera” en la página 94 y “Tiempos de Espera” en la página 110.
Tabla A-4. Migración de esperas y tiempos de espera en Selenium WebDriver 4
Listeners de eventos
En Selenium WebDriver 3, utilizábamos la clase `EventFiringWebDriver` para crear
listeners de eventos. Esta clase ha sido marcada como obsoleta en Selenium WebDriver
4, y en su lugar, se recomienda la clase `EventFiringDecorator`. La Tabla A-5 resume este
cambio. Puedes encontrar un ejemplo completo de esta funcionalidad en la sección
“Listeners de Eventos” en la página 138.
Tabla A-5. Migración de listeners de eventos en Selenium WebDriver 4
Capacidades
Los métodos estáticos para seleccionar diferentes tipos de navegadores han sido
eliminados en Selenium WebDriver 4. En su lugar, debemos utilizar opciones específicas
para cada navegador. La Tabla A-6 resume este cambio. La sección “Capacidades del
Navegador” en la página 145 contiene más detalles y ejemplos.
Tabla A-6. Migración de capacidades deseadas en Selenium WebDriver 4
Luego, los constructores de WebDriver basados en capacidades están obsoletos en lugar
de las opciones específicas para cada navegador. La Tabla A-7 muestra un ejemplo de
este proceso. Puedes encontrar ejemplos utilizando este constructor en la sección
“Capacidades del Navegador” en la página 145.
Tabla A-7. Migración de la instancia de WebDriver utilizando capacidades en Selenium
WebDriver 4
Además, la forma en que se fusionan las capacidades ha cambiado en Selenium
WebDriver 4. En Selenium WebDriver 3, era posible combinar capacidades modificando
el objeto que llamaba. Este proceso es diferente en Selenium WebDriver 4, en el que la
operación de fusión debe ser asignada. La Tabla A-8 proporciona un ejemplo de este
cambio.
Tabla A-8. Migración de la fusión de capacidades en Selenium WebDriver 4
Finalmente, la interfaz `BrowserType` ha sido marcada como obsoleta en favor de la
nueva interfaz `Browser`. La Tabla A-9 ilustra la diferencia entre estas interfaces al
especificar capacidades. Puedes encontrar más detalles sobre este aspecto en “Creación
de objetos RemoteWebDriver” en la página 199.
Tabla A-9. Migración de capacidades en Selenium WebDriver 4
Selenium Grid
Selenium Grid 4 ha sido completamente reescrito desde cero. La nueva base de código
se ha creado con todos los aprendizajes de Selenium Grid 3, mejorando la
mantenibilidad del código fuente. Selenium Grid 4 admite el modo clásico
implementado en la versión 3, es decir, la arquitectura de standalone y hub-nodes.
Además, Selenium Grid proporciona una arquitectura completamente distribuida para
mejorar su rendimiento y escalabilidad generales. Finalmente, Selenium Grid 4 utiliza
tecnologías modernas de infraestructura como Docker, Kubernetes o trazado
distribuido utilizando OpenTelemetry.
“Selenium Grid” en la página 6 ofrece una introducción a Selenium Grid. Luego,
“Selenium Grid” en la página 203 explica los detalles de los diferentes modos de
Selenium Grid (es decir, standalone, hub-nodes y completamente distribuido) y cómo
usarlo en las pruebas de Selenium WebDriver.
Guía de Migración
Al usar Selenium Grid como una dependencia de Java y actualizar a la versión 4, además
de la actualización de versión, debes saber que las coordenadas del proyecto cambiaron
en Selenium Grid 4. Anteriormente, el `artifactId` era `selenium-server`. Este valor ha
cambiado a `selenium-grid` en Selenium Grid 4. La Tabla A-10 contiene las nuevas
coordenadas de Selenium Grid 4 en Maven y Gradle.
Tabla A-10. Migración de Selenium Grid 4 como dependencia de Maven y Gradle
Selenium IDE
Selenium IDE se introduce en “Selenium IDE” en la página 8. Algunas de las nuevas
características disponibles desde Selenium 4 son:
Seleccionadores de elementos de respaldo
Selenium IDE graba múltiples localizadores (por ejemplo, por id, XPath o selector
CSS) para cada elemento. De esta manera, si la ejecución del test no encuentra un
elemento utilizando el primer localizador, recurrirá a los siguientes hasta que lo
encuentre.
Flujos de control
Selenium IDE mejora la ejecución de scripts utilizando condicionales (es decir, if,
else, else if, y end) y bucles (es decir, do, while, times y forEach).
Exportación de código
Selenium IDE permite exportar la grabación a varios lenguajes de enlace de
Selenium WebDriver (es decir, C#, Java, JavaScript, Python y Ruby) y frameworks de
pruebas unitarias (es decir, NUnit, xUnit, JUnit, Mocha, pytest y RSpec).
Complementos
Podemos extender el Selenium IDE (por ejemplo, introduciendo nuevos comandos
o integraciones de terceros) utilizando complementos personalizados.
Otras Novedades
La documentación oficial de Selenium ha sido significativamente mejorada con Selenium
4. El nuevo sitio está disponible en
[https://www.selenium.dev](https://www.selenium.dev) y cubre los subproyectos de
Selenium (WebDriver, IDE y Grid), la guía del usuario, el blog, el soporte y otra
información del proyecto.
Por último, pero no menos importante, Simon Stewart, el co-creador de Selenium
WebDriver y líder del proyecto Selenium desde 2009, dejó su puesto como líder del
proyecto Selenium el 27 de octubre de 2021. Puedes encontrar la estructura actual del
proyecto (compuesta por el comité de liderazgo del proyecto, el comité de liderazgo
técnico y los contribuyentes y activadores de Selenium) y la gobernanza (es decir,
modelo, filosofía, roles del proyecto, proceso de toma de decisiones, etc.) en el sitio web
de Selenium.
APÉNDICE B
Gestión de Drivers
Como se discutió en el Capítulo 1, la gestión de drivers implica tres pasos: descarga,
configuración y mantenimiento. La gestión manual de drivers es costosa en términos de
esfuerzo y puede ser problemática en cuanto a mantenibilidad. Por esta razón, utilizo
WebDriverManager para llevar a cabo este proceso de manera automatizada y de auto-
mantenimiento en todos los ejemplos de este libro. Para completar la información, este
apéndice también describe los pasos involucrados (descarga, configuración y
mantenimiento) en la gestión manual de drivers.
WebDriverManager: Gestión Automatizada de Drivers
WebDriverManager es una biblioteca de Java de código abierto que gestiona los drivers
requeridos por Selenium WebDriver (por ejemplo, chromedriver, geckodriver,
msedgedriver, etc.) de manera automatizada. WebDriverManager proporciona un
conjunto de gestores para diferentes navegadores, a saber: Chrome, Firefox, Edge,
Opera, Chromium e Internet Explorer.
WebDriverManager ejecuta internamente un algoritmo de resolución para gestionar los
drivers requeridos por cada navegador. Este algoritmo tiene como objetivo descubrir,
descargar, configurar y mantener automáticamente estos drivers.
La Figura B-1 representa este algoritmo en el contexto de la metodología implementada
por WebDriverManager. Para cada gestor (por ejemplo, `chromedriver()`,
`firefoxdriver()`, etc.), el algoritmo de resolución funciona de la siguiente manera:
1. WebDriverManager intenta encontrar la versión del navegador (por ejemplo,
Chrome) instalada en la máquina local. Para ello, utiliza una base de datos de
conocimientos interna llamada **base de datos de comandos**. Esta base de datos
contiene una lista de comandos de shell (en diferentes sistemas operativos) que
permiten descubrir las versiones del navegador (por ejemplo, `googlechrome --version`
en Linux).
2. Usando la versión principal del navegador encontrada (por ejemplo, Chrome 89),
WebDriverManager determina la versión correcta del driver (por ejemplo,
`chromedriver 89.0.4389.23`). A este proceso lo llamo resolución de versión. Para
facilitar este proceso, varios mantenedores de drivers (es decir, `chromedriver` y
`msedgedriver`) publican la versión específica del driver en sus repositorios en línea
utilizando archivos de texto simples (por ejemplo,
`https://chromedriver.storage.googleapis.com/LATEST_RELEASE_89`).
Desafortunadamente, esta información no está disponible para otros drivers, como
`geckodriver` u `operadriver`. Por esta razón, WebDriverManager utiliza otra base de
datos de conocimientos interna (llamada **base de datos de versiones**) para
mantener la asociación entre las versiones de navegadores y drivers. Ambas bases de
datos, la de versiones y la de comandos, sincronizan sus valores usando una referencia
maestra en línea almacenada en GitHub.
3. WebDriverManager descarga el driver específico para el sistema operativo local
(Windows, Linux o macOS) y lo almacena en el sistema de archivos local en la caché de
drivers (por defecto, en la ruta `~/.cache/selenium`).
4. Finalmente, WebDriverManager exporta la ruta del driver descargado utilizando la
propiedad del sistema Java adecuada (por ejemplo, `webdriver.chrome.driver`).
En aras del rendimiento y la mantenibilidad, WebDriverManager utiliza internamente
una caché de resolución. Esta caché (por defecto almacenada en la caché de drivers
como un archivo de propiedades) mantiene la relación entre las versiones de drivers
resueltas. Esta relación es válida siguiendo un enfoque de tiempo de vida (TTL). El valor
predeterminado de este TTL es un día para los drivers (por ejemplo, `chromedriver
89.0.4389.23`) y una hora para los navegadores (por ejemplo, Chrome 89). El algoritmo
de resolución resuelve los drivers utilizando los archivos en caché en las invocaciones
posteriores (esto suele ocurrir en un conjunto de pruebas de Selenium WebDriver).
Luego, cuando un TTL expira, el algoritmo de resolución intenta resolver una nueva
versión del driver. Finalmente, cuando se detecta una versión diferente del navegador,
WebDriverManager descarga el nuevo driver (si es necesario). Gracias a este proceso, la
conformidad de versión entre el navegador y el driver está garantizada incluso para
navegadores en evolución.
Figura B-1. Metodología de WebDriverManager
Gestor Genérico
Además de los gestores específicos para navegadores (por ejemplo, `chromedriver()`,
`firefoxdriver()`, etc.), WebDriverManager proporciona un gestor genérico, es decir, un
gestor que puede ser parametrizado para actuar como un gestor específico (para
Chrome, Firefox, etc.). Esta funcionalidad está disponible mediante el método
`getInstance()` de la API de WebDriverManager.
Existen diferentes opciones para invocar este método:
getInstance(Class<? extends WebDriver> webDriverClass)
Donde `webDriverClass` es una clase de la jerarquía de WebDriver, como
`ChromeDriver.class`, `FirefoxDriver.class`, etc.
getInstance(DriverManagerType driverManagerType)
Donde `driverManagerType` es una enumeración proporcionada por
WebDriverManager para identificar los gestores disponibles. Los posibles valores
de esta enumeración son `CHROME`, `FIREFOX`, `EDGE`, `OPERA`, `CHROMIUM`,
`IEXPLORER` y `SAFARI`.
getInstance(String browserName)
Donde `browserName` es el nombre del navegador como una cadena insensible a
mayúsculas y minúsculas. Los posibles valores son `Chrome`, `Firefox`, `Edge`,
`Opera`, `Chromium`, `IExplorer` y `Safari`.
getInstance()
Cuando no se especifica ningún parámetro, se usa la clave de configuración
`wdm.defaultBrowser` para seleccionar el gestor (Chrome por defecto).
Configuración Avanzada
WebDriverManager ofrece diferentes formas de configuración. Primero, puedes usar su
API de Java a través de cada gestor. Esta API permite concatenar varios métodos para
especificar opciones o preferencias personalizadas. Puedes encontrar la descripción
completa de la API de WebDriverManager en su documentación. Por ejemplo, el
siguiente comando muestra cómo configurar un proxy para la conexión de red:
La segunda forma de configurar WebDriverManager es usando propiedades del sistema
Java. Cada método de la API de WebDriverManager tiene una clave de configuración
equivalente. Por ejemplo, el método de API `cachePath()` (usado para especificar la
carpeta de caché del driver) funciona de la misma manera que la clave de configuración
`wdm.cachePath`. Estos tipos de claves de configuración pueden ser pasadas, por
ejemplo, usando la línea de comandos:
Finalmente, también puedes usar variables de entorno para configurar
WebDriverManager. Los nombres de las variables derivan de cada clave de
configuración (por ejemplo, `wdm.cachePath`), convirtiéndolas a mayúsculas y
reemplazando el símbolo `.` con `_` (por ejemplo, `WDM_CACHEPATH`). Este
mecanismo puede ser conveniente para configurar parámetros globales a nivel del
sistema operativo.
Otros Usos
Además de servir como una dependencia de Java, WebDriverManager se puede usar de
otras maneras, a saber:
Como una herramienta de interfaz de línea de comandos (CLI)
Este modo permite resolver drivers (por ejemplo, `chromedriver`, `geckodriver`).
Además, este modo permite ejecutar navegadores en contenedores Docker e
interactuar con ellos a través de una sesión de escritorio remoto.
Como un servidor
El Servidor WebDriverManager se basa en HTTP y ofrece dos tipos de servicios.
Primero, expone una API REST simple para resolver drivers. Segundo, actúa como
un servidor Selenium regular, por lo que puedes usarlo con diferentes lenguajes
de enlace que no sean Java.
Como un agente Java
En este caso, y usando la API de instrumentación de JVM, WebDriverManager
utiliza la API de instrumentación de Java para verificar los objetos que se crean en
la JVM. Cuando se instancian objetos WebDriver (por ejemplo, `ChromeDriver`,
`FirefoxDriver`, etc.), se utiliza el gestor requerido para resolver su driver (por
ejemplo, `chromedriver`, `geckodriver`, etc.). Gracias a este enfoque, puedes
prescindir de la llamada a WebDriverManager en tus pruebas.
Gestión Manual de Drivers
Esta sección describe cómo implementar manualmente el proceso de gestión de drivers
(descarga, configuración y mantenimiento).
Descarga
El primer paso para la gestión de drivers es descargar el driver adecuado. La Tabla B-1
muestra los sitios web para obtener los drivers para los principales navegadores.
Necesitas encontrar la versión correcta del driver y la plataforma (Windows, Linux,
macOS) para el navegador que planeas usar. En cuanto a la versión, los mantenedores
de Chrome y Edge (aunque no Firefox, desafortunadamente) siguen el mismo esquema
de versionado para drivers y navegadores para facilitar este proceso. Por lo tanto, por
ejemplo, si usas Chrome o Edge 91.x, también debes usar `chromedriver` y
`msedgedriver` 91.x. Encontrarás la versión específica del driver en la documentación
proporcionada en los sitios web. Por ejemplo, para usar Chrome 91, necesitas descargar
`ChromeDriver 91.0.4472.19`.
Tabla B-1. Propiedades del sistema Java para configurar drivers
Configuración
Una vez que tengas el driver requerido para tu script de WebDriver, necesitas
configurarlo correctamente. Hay dos formas de llevar a cabo este proceso. La primera
es añadir la ubicación del driver (el camino completo o la carpeta principal que contiene
el driver) a tu variable de entorno PATH. La variable PATH es estándar en sistemas
operativos similares a Unix (por ejemplo, Linux y macOS) y en Windows. Esta variable de
entorno permite especificar un conjunto de carpetas donde el sistema operativo localiza
los programas ejecutables.
La forma en que configuramos PATH (y otras variables de entorno) depende del sistema
operativo específico. Por ejemplo, en sistemas Windows, podemos hacerlo utilizando su
interfaz gráfica (Panel de Control → Sistema → Configuración Avanzada → Variables de
Entorno). En un sistema similar a Unix, podemos usar la línea de comandos para llevar a
cabo este proceso, por ejemplo, usando el siguiente comando (o equivalente):
La segunda forma de configurar el driver es utilizando propiedades del sistema Java, que
son atributos de configuración (en forma de nombre/valor) pasados a la JVM. La Tabla
B-2 resume los nombres para los principales drivers en Selenium WebDriver. El valor
para estas propiedades es el camino completo de un driver dado (por ejemplo,
`/path/to/drivers/chromedriver`).
Tabla B-2. Propiedades del sistema Java para configurar drivers
Hay dos formas de configurar estas propiedades: la línea de comandos (pasando la
propiedad del sistema utilizando la sintaxis `-Dname=value`) o código Java. Por ejemplo,
el Ejemplo B-1 muestra los comandos de Maven y Gradle para ejecutar todas las pruebas
de un proyecto dado mientras se pasan las propiedades para configurar los drivers de
Chrome, Edge y Firefox. Luego, el Ejemplo B-2 muestra cómo realizar la misma
configuración, pero esta vez usando Java.
Ejemplo B-1. Comandos de Maven y Gradle para configurar propiedades del sistema en
la línea de comandos
Ejemplo B-2. Comandos de Java para configurar propiedades del sistema
Mantenimiento
Por último, pero no menos importante, el paso final en la gestión de controladores es
mantener estos controladores. Este mantenimiento es necesario porque los
navegadores evergreen (como Chrome, Edge o Firefox) se actualizan automáticamente.
Aunque esto resulta atractivo desde la perspectiva del usuario, esta actualización
automática es problemática para los scripts de Selenium WebDriver donde la gestión
del controlador es manual. En este caso, la compatibilidad entre el controlador y el
navegador no está garantizada a largo plazo.
Un controlador específico (por ejemplo, chromedriver versión 84.0.4147.30) es
típicamente compatible con una versión dada del navegador (por ejemplo, Chrome 84).
Eventualmente, esta compatibilidad no está garantizada debido a la actualización
automática. Como resultado, un script de Selenium WebDriver basado en este
controlador deja de funcionar (es decir, el test se considera roto). En la práctica, los
desarrolladores de Selenium WebDriver experimentan este problema cuando las
pruebas fallan debido a la incompatibilidad entre el controlador y el navegador. Por
ejemplo, al usar Chrome como navegador, un test roto debido a la incompatibilidad del
controlador informa el siguiente mensaje de error: “esta versión de chromedriver solo
es compatible con la versión N de Chrome” (donde N es la versión más reciente de
Chrome compatible con una versión particular de chromedriver). Para ilustrar este
problema, la Figura B-2 muestra el interés de búsqueda mundial de ese mensaje de error
en Google durante 2019 y 2020, junto con la fecha de lanzamiento de las diferentes
versiones de Chrome en este período. Como se puede ver, el interés a lo largo del tiempo
con respecto a este mensaje de error está relacionado con algunos lanzamientos de
Chrome.
Figura B-2. Interés relativo mundial a lo largo del tiempo del término de búsqueda “esta
versión de chromedriver solo soporta la versión de Chrome” en Google Trends junto con
las fechas de lanzamiento de Chrome durante 2019 y 2020
Resumen
Selenium WebDriver es una biblioteca que permite controlar los navegadores web de
manera programática. La automatización se basa en las capacidades nativas de cada
navegador. Por lo tanto, necesitamos colocar un archivo binario dependiente de la
plataforma llamado driver entre el script/prueba que utiliza la API de Selenium
WebDriver y el navegador. Algunos ejemplos de controladores son chromedriver (para
Chrome), geckodriver (para Firefox) y msedgedriver (para Edge). Este apéndice presenta
el proceso de gestión de controladores. Este proceso tiene tres pasos (descarga,
configuración y mantenimiento), y puede hacerse de manera manual o automática. Por
defecto, te recomiendo utilizar un enfoque automatizado para la gestión de
controladores. Con ese fin, la herramienta de referencia en Java es WebDriverManager.
APÉNDICE C
Configuración del Repositorio de Ejemplos
El repositorio de ejemplos es un ingrediente vital de este libro, ya que contiene todos
los ejemplos cubiertos y la configuración completa de Maven y Gradle. Además, este
repositorio utiliza varios servicios proporcionados por GitHub, tales como:
GitHub Pages
Un servicio que permite alojar sitios web públicos configurados directamente desde un
repositorio de GitHub. Utilizo un sitio web simple vinculado al repositorio de ejemplos
para mostrar las páginas web utilizadas como SUT en los ejemplos de prueba de
Selenium WebDriver: https://bonigarcia.dev/selenium-webdriver-java. Como puedes
ver, contiene diferentes páginas HTML que utilizan Bootstrap como marco CSS.
GitHub Actions
Un servidor de integración continua/entrega continua (CI/CD) para repositorios de
GitHub. Utilizo este servicio para construir y probar todo el repositorio con cada nuevo
commit. Puedes ver los detalles sobre la configuración del flujo de trabajo al final de
esta sección.
Dependabot
Un bot que actualiza automáticamente las dependencias del proyecto. Cuando este bot
detecta una nueva versión para cualquier dependencia de Maven y Gradle (ver la
siguiente subsección para más detalles), crea una solicitud de extracción con la
actualización correspondiente.
En el resto de este apéndice, encontrarás los detalles de configuración para el
repositorio de ejemplos. Esta configuración incluye la declaración de dependencias de
Maven y Gradle y otros aspectos, y debería ser suficiente para proyectos estándar que
utilicen Selenium WebDriver. Además, la parte final de este apéndice explica cómo
configurar las bibliotecas de registro, Dependabot y GitHub Actions (para construir y
probar el proyecto siguiendo un enfoque CI).
Diseño del Proyecto
La Figura C-1 muestra la representación esquemática del diseño del repositorio de
ejemplos.
Figura C-1. Diseño del repositorio de ejemplos (alojado en GitHub)
Dado que estoy proporcionando cada ejemplo en cuatro versiones (JUnit 4, JUnit 5, JUnit
5 más Selenium-Jupiter y TestNG), la configuración en Maven y Gradle se basa en
multiproyectos. De esta manera, el repositorio de ejemplos tiene cuatro módulos, uno
para cada marco de pruebas: selenium-webdriver-junit4, selenium-webdriver-junit5,
selenium-webdriver-junit5-seljup y selenium-webdriver-testng. En Maven, la
configuración de multiproyecto se encuentra en el archivo `pom.xml` ubicado en la
carpeta raíz, y en el archivo `settings.gradle` en Gradle.
Como se puede ver en la Figura C-1, cada módulo tiene la misma estructura. Puedes
encontrar el código fuente de las pruebas en la carpeta `src/test/java`. Utilizo paquetes
de Java para dividir los ejemplos por capítulo (por ejemplo,
`io.github.bonigarcia.webdriver.jupiter.ch02.helloworld`). Luego, cada proyecto
necesita su propio archivo de configuración de Logback. Utilizo el archivo de
configuración general (es decir, `logback.xml`) ubicado en la carpeta
`src/main/resources`. Sigo esta convención ya que es bastante común usar logging
también para la aplicación, y en caso de que planees reutilizar esta estructura de
proyecto, este es el enfoque estándar. Finalmente, en la raíz de cada subproyecto,
puedes encontrar el archivo de configuración específico para Maven (`pom.xml`) y
Gradle (`build.gradle`). Puedes encontrar la declaración de las dependencias en estos
archivos, como se explica en la sección siguiente.
Maven
Un concepto clave en Maven es el ciclo de vida de construcción, el nombre dado al
proceso de construcción y distribución de un proyecto en particular. Hay tres ciclos de
vida de construcción estándar en Maven: default (para la implementación del proyecto),
clean (para la limpieza del proyecto) y site (para la documentación). Estos ciclos de vida
de construcción tienen una lista de fases de construcción, donde cada fase representa
una etapa en el ciclo de vida. Las fases principales del ciclo de vida default son:
validate
Evaluar que el proyecto es correcto y que toda la información necesaria está
disponible.
compile
Compilar el código fuente.
test
Ejecutar pruebas utilizando un marco de pruebas unitarias.
package
Empaquetar el código compilado en un formato distribuible, como un archivo
Java ARchive (JAR).
verify
Ejecutar pruebas adicionales (típicamente pruebas de integración u otras
pruebas de alto nivel).
install
Instalar el paquete en el repositorio local.
deploy
Instalar el paquete en un repositorio o servidor remoto.
Podemos usar la línea de comandos para invocar Maven, utilizando el comando `mvn`.
Por ejemplo, el siguiente comando invoca el ciclo de vida clean (es decir, limpia la
carpeta de destino y todo su contenido) y luego invoca en cascada todas las fases del
ciclo de vida default hasta package (es decir, validate, compile, test y finalmente
package):
Otro elemento clave en Maven es el concepto de complementos (plug-ins). Un
complemento es un artefacto incorporado que ejecuta las fases mencionadas
anteriormente. En este libro, estamos particularmente interesados en las pruebas. Por
lo tanto, nos enfocamos en las fases `test` y `verify` y en sus correspondientes
complementos: `maven-surefire-plugin` y `maven-failsafe-plugin`, respectivamente. La
Tabla C-1 resume las principales diferencias entre estos dos complementos.
Tabla C-1. Diferencias entre los complementos Surefire y Failsafe de Maven
Para simplificar, utilizo solo el `maven-surefire-plugin` para ejecutar pruebas en el
repositorio de ejemplos. Aunque estas pruebas no son unitarias (de hecho, son pruebas
de extremo a extremo), no es un problema ejecutarlas con `maven-surefire-plugin` (es
decir, después de la compilación y antes del empaquetado). La Tabla C-2 resume los
comandos básicos para ejecutar pruebas desde la línea de comandos usando este
complemento.
Tabla C-2. Comandos básicos para ejecutar pruebas con el `maven-surefire-plugin`
Sin embargo, si deseas usar `maven-failsafe-plugin` para ejecutar las pruebas, necesitas
usar la configuración mostrada en el Ejemplo C-1 en tu archivo `pom.xml`. Finalmente,
puedes ejecutar las pruebas utilizando el comando `mvn verify` (es decir, ejecutar
pruebas después del empaquetado).
Ejemplo C-1. Configuración de Maven requerida para usar `maven-failsafe-plugin`
Configuración Común
El Ejemplo C-2 contiene las partes comunes de la configuración de Maven en el
repositorio de ejemplos.
Ejemplo C-2. Dependencias comunes de Maven en el repositorio de ejemplos
1. Usamos Java 8 en este proyecto.
2. Especificamos las dependencias comunes. Por un lado, declaramos Selenium
WebDriver, AssertJ y WebDriverManager usando el ámbito de prueba. De esta
manera, podemos usar estas dependencias únicamente desde la lógica de
prueba (es decir, las clases Java en la carpeta `src/test/java`). Por otro lado, el
ámbito de Simple Logging Facade for Java (SLF4J) y Logback está ausente, y por
lo tanto Maven usa el valor predeterminado, que es `compile`. Esto significa que
podemos usar estas dependencias tanto desde la aplicación como desde la lógica
de prueba. Finalmente, notamos que usamos propiedades de Maven para
declarar las versiones de las dependencias (por ejemplo, `${selenium.version}`).
Puedes encontrar la versión precisa en el repositorio en línea.
3. Necesitamos declarar una versión específica de `maven-surefire-plugin`. Como
se explicó en la Tabla C-1, la versión utilizada para este complemento se define
internamente por Maven. Pero para aprovechar al máximo este complemento,
necesitamos especificar una versión más reciente.
JUnit 4
En un proyecto Maven que utiliza JUnit 4 como el marco de pruebas unitarias, también
necesitamos declarar la siguiente dependencia:
JUnit 5
Aunque JUnit 5 es un marco modular, podemos declarar una sola dependencia para usar
el modelo de programación Jupiter en un proyecto Maven. Como se puede ver en el
fragmento a continuación, este artefacto se llama `junit-jupiter`, y transciende los
siguientes artefactos de JUnit 5:
junit-jupiter-api
Para desarrollar pruebas.
junit-jupiter-engine
Para ejecutar pruebas en la Plataforma JUnit.
junit-jupiter-params
Para desarrollar pruebas parametrizadas (ver Capítulo 8).
Selenium-Jupiter
Al usar Jupiter en combinación con Selenium-Jupiter, además de los artefactos
anteriores (`junit-jupiter` y `maven-surefire-plugin`), necesitamos incluir las
coordenadas de Selenium-Jupiter (ver el siguiente ejemplo de código). En este caso,
podemos eliminar las coordenadas de WebDriverManager ya que Selenium-Jupiter lo
incluye de forma transitoria.
TestNG
Finalmente, las coordenadas que necesitamos incluir en nuestro archivo `pom.xml` para
usar TestNG son:
Aunque no se usa en el repositorio de ejemplos, las pruebas de TestNG también se
pueden ejecutar en la plataforma JUnit. Si deseas habilitar este modo, debes agregar el
motor de TestNG para la plataforma JUnit a la configuración de tu proyecto. Puedes
encontrar más información sobre esto en la página del motor de TestNG.
Otras dependencias
Este libro explica otras dependencias utilizadas en conjunto con Selenium WebDriver.
La Tabla C-3 resume estas dependencias y el capítulo en el que se presentan.
Tabla C-3. Dependencias para la integración con terceros en el repositorio de ejemplos
Además, la declaración de los complementos requiere una configuración adicional para
usar algunas de estas dependencias de terceros. El siguiente fragmento muestra esta
nueva configuración.
1. Esta configuración (desde esta línea hasta <!-- /Allure -->) solo es necesaria si
planeas usar Allure para generar un informe de pruebas. Si no lo usas, puedes
eliminarlo de tu proyecto sin problemas.
2. La clase de oyente cambia para los diferentes marcos de pruebas unitarias:
• `io.qameta.allure.junit4.AllureJunit4` para JUnit 4
• `io.qameta.allure.junit5.AllureJunit5` para JUnit 5 (y JUnit 5 más Selenium-
Jupiter)
• No se requiere ningún oyente para TestNG
3. Además del oyente, el complemento Allure es necesario cuando se utiliza esta
herramienta de informes.
4. El complemento Spring-Boot se recomienda cuando se usa Spring-Boot.
Gradle
Cada proyecto Gradle se compone de varias tareas. Cada tarea representa una unidad
de trabajo dentro de la construcción. Ejemplos típicos de tareas en un proyecto Java
son:
compileJava
Compila la lógica de la aplicación (es decir, las clases Java en la carpeta
`src/main/java`).
processResources
Copia los recursos de la aplicación (es decir, archivos en la carpeta
`src/main/resources`) en la carpeta de salida (`build`).
compileTestJava
Compila la lógica de pruebas (es decir, las clases Java en la carpeta
`src/test/java`).
processTestResources
Copia los recursos de prueba (es decir, archivos en la carpeta
`src/test/resources`) en la carpeta de salida.
test
Ejecuta las pruebas usando JUnit o TestNG. La Tabla C-4 resume los comandos
comunes para ejecutar pruebas de Gradle en la terminal.
clean
Elimina la carpeta de salida del proyecto y su contenido.
Tabla C-4. Comandos básicos para ejecutar pruebas con Gradl
Ejemplo C-3 contiene la configuración común para todos los subproyectos del
repositorio de ejemplos. A continuación, explico las partes relevantes de este
fragmento.
Ejemplo C-3. Configuración común para proyectos Gradle
1. Dado que estamos implementando un proyecto Java, necesitamos declarar el
complemento de Java.
2. Para compilar las pruebas, usamos Java 8.
3. Aunque no es obligatorio, forzamos la escritura de los registros de prueba en la
salida estándar.
4. Esto permite pasar propiedades del sistema Java en la línea de comandos (como
se explicó en el Ejemplo B-1).
5. Esta cláusula permite usar la propiedad `excludeTests` en la línea de comandos
para excluir algunas pruebas. Por ejemplo, el siguiente comando excluye
aquellas pruebas que comienzan con la palabra "Docker": `gradle test -
PexcludeTests=**/Docker*`.
6. Estas líneas permiten ejecutar pruebas en paralelo usando el comando `gradle
test -Pparallel`.
7. Las siguientes cláusulas reúnen las pruebas fallidas en la propiedad `failedTests`
y muestran esta información en la salida estándar al final de la ejecución del
conjunto de pruebas.
8. Usamos Maven Central para descargar las dependencias.
9. Las dependencias comunes son Selenium WebDriver, AssertJ,
WebDriverManager (para pruebas) y SLF4J y Logback (para todo el proyecto).
JUnit 4
La configuración específica para JUnit 4 es la siguiente:
1. Usamos una configuración adicional para permitir el filtrado de pruebas
utilizando el nombre de la clase (ver “Categorizar y Filtrar Pruebas” en la página
256).
2. Incluimos la dependencia de JUnit 4.
JUnit 5
Al usar JUnit 5, necesitamos especificar el artefacto `junit-jupiter` (al igual que en
Maven, depende de `junit-jupiter-api`, `junit-jupiter-engine`, y `junit-jupiter-params`).
Además, debemos seleccionar la Plataforma JUnit para su ejecución utilizando la
cláusula `useJUnitPlatform()` en la configuración de la tarea de prueba.
Selenium-Jupiter
Si utilizas Selenium-Jupiter, además de la configuración anterior para JUnit 5, necesitas
incluir la siguiente dependencia. En este caso, podemos eliminar WebDriverManager ya
que es incluido transitivamente por Selenium-Jupiter.
TestNG
Finalmente, para usar TestNG como el marco de pruebas unitarias, necesitamos incluir
la siguiente configuración:
Otras Dependencias
Necesitamos incluir dependencias adicionales en la configuración de Gradle para usar
bibliotecas de terceros. La Tabla C-3 (en la sección anterior) resume las coordenadas
para estas dependencias y el capítulo en el que se presentan. Además, se requieren un
par de complementos adicionales en la configuración de Gradle (para usar Allure y
Spring Boot, respectivamente). Para usar Allure, también necesitas definir un repositorio
adicional, como se indica a continuación:
Registro
Utilizo dos bibliotecas de registro en el repositorio de ejemplos:
Logback
Este es el marco de registro real (también llamado logger). Logback es utilizado
por muchos proyectos Java relevantes, como Spring Framework y Groovy, por
nombrar algunos.
Simple Logging Facade for Java (SLF4J)
Esta es una utilidad popular basada en el patrón de diseño de fachada que
desacopla el logger subyacente. Soporta los principales marcos de registro (por
ejemplo, Logback, Log4j o SimpleLogger, entre otros). Como se resume en la
Tabla C-5, SLF4J define cinco niveles de registro dependiendo de la severidad del
mensaje.
Tabla C-5. Niveles de registro en SLF4J
Como es habitual, para utilizar estas bibliotecas, necesitamos resolver las dependencias
correspondientes (ver la siguiente sección para detalles en Maven y Gradle). Luego,
debemos configurar Logback correctamente. Para ello, necesitamos incluir un archivo
de configuración XML en el classpath de nuestro proyecto. Si estamos configurando el
registro para todo el proyecto (es decir, la aplicación más la lógica de prueba), el nombre
de este archivo debe ser `logback.xml`. En este caso, debería estar disponible dentro de
los recursos de la aplicación, típicamente bajo la carpeta `src/main/resources` (ver la
siguiente sección para más información sobre la estructura del proyecto). Si solo
estamos configurando el registro para pruebas, el nombre del archivo de configuración
es `logback-test.xml` y se almacena dentro de los recursos de prueba (por ejemplo, en
la carpeta `src/test/resources`).
La sintaxis en ambos casos (logback.xml y logback-test.xml) es la misma. Este archivo
XML establece el patrón para cada línea de registro, compuesto por la marca de tiempo,
el nombre del hilo, el nivel de traza, la fuente (paquete, nombre de la clase y línea de
código) y el mensaje. En este ejemplo, `INFO` es el nivel de registro predeterminado. De
esta manera, se muestra cada traza de este nivel o niveles más severos (es decir,
`WARN`, `ERROR` y `FATAL`), pero no las siguientes (es decir, `DEBUG` y `TRACE`).
Además, las trazas del paquete `io.github.bonigarcia` (utilizado en los ejemplos de
prueba, WebDriverManager y Selenium-Jupiter) son de nivel `DEBUG`.
Ejemplo C-4. Archivo de configuración de Logback
El paso final es utilizar una variable para el registro en nuestras clases Java. Para ello,
podemos usar el código del Ejemplo C-5. Este fragmento proporciona una forma
conveniente de obtener la clase actual usando reflexión a través del método `lookup()`.
Luego, declaramos la variable para el registro (llamada `log` en este ejemplo) y usamos
el método `getLogger()` de SLF4J. Finalmente, podemos usar la variable `log` en
cualquier método de esta clase para registrar mensajes de diferentes niveles.
Ejemplo C-5. Ejemplo de registro de mensajes
GitHub Actions
Uso GitHub Actions como el servidor CI para el repositorio de ejemplos. De esta manera,
cada vez que hago un nuevo commit en el repositorio, GitHub Actions construye el
proyecto y ejecuta todas las pruebas. El Ejemplo C-6 muestra la configuración para llevar
a cabo este proceso.
Ejemplo C-6. Configuración del flujo de trabajo de GitHub Actions
1. Los eventos que activan el flujo de trabajo son `push` (nuevos commits en el
repositorio) y `pull_request` (commits propuestos por otros desarrolladores).
2. Se requieren dos variables de entorno:
DISPLAY
Los navegadores controlados por Selenium WebDriver, por defecto, necesitan
ser ejecutados en un sistema operativo con una interfaz gráfica de usuario. Por
otro lado, las distribuciones de Linux disponibles en GitHub Actions son sin
cabeza (es decir, sin una interfaz gráfica de usuario). Por lo tanto, utilizamos Xvfb
(X virtual framebuffer) para ejecutar las pruebas de WebDriver en estas
distribuciones de Linux. Xvfb es un servidor de visualización en memoria para
sistemas tipo Unix que requiere la declaración de la variable de entorno DISPLAY
con el número de pantalla para el sistema gráfico en Linux (X11).
WDM_GITHUBTOKEN
GitHub aloja algunos de los controladores requeridos por Selenium WebDriver
(por ejemplo, `geckodriver` o `operadriver`). Cuando clientes externos (como
WebDriverManager) hacen muchas solicitudes consecutivas a GitHub,
eventualmente se recibe una respuesta de error HTTP (403, prohibido) debido a
su límite de tasa. WebDriverManager puede hacer solicitudes autenticadas
utilizando un token de acceso personal para evitar este problema. La Figura C-2
muestra los permisos concedidos a este token en el repositorio de ejemplos. En
resumen, esta variable de entorno exporta el valor de este token. Mantengo el
valor real de este token como un secreto del repositorio de GitHub.
3. Para ser completo, ejecuto el flujo de trabajo en tres sistemas operativos
diferentes: Ubuntu (es decir, Linux), Windows y macOS, utilizando Java 8 en
todos ellos.
4. El flujo de trabajo tiene cinco pasos:
a) Clonar el repositorio.
b) Configurar Java 8 usando Eclipse Adoptium.
c) Iniciar X virtual framebuffer.
d) Ejecutar todas las pruebas con Maven.
e) Ejecutar todas las pruebas con Gradle.
Figura C-2. Permisos del token de acceso personal de GitHub utilizado en el repositorio
de ejemplos
Dependabot
Para configurar Dependabot, necesitamos incluir un archivo llamado `dependabot.yml`
en la carpeta `.github` de nuestro repositorio. El Ejemplo C-7 muestra este contenido en
el repositorio de ejemplos.
Ejemplo C-7. Configuración de Dependabot
1. Verificamos diariamente las actualizaciones para las dependencias de Maven.
2. Verificamos diariamente las actualizaciones para las dependencias de Gradle.
3. Verificamos diariamente las actualizaciones para la configuración de GitHub
Actions.
Resumen
Todos los ejemplos presentados en este libro están disponibles en un repositorio público
de GitHub. Este apéndice mostró los detalles de configuración precisos para las
herramientas de construcción (Maven y Gradle), las dependencias (Selenium
WebDriver, JUnit, TestNG, Selenium-Jupiter, WebDriverManager, etc.), el registro
(Logback y SLF4J) y otros servicios (GitHub Actions, GitHub Pages y Dependabot).
Sobre el Autor
Boni García (https://bonigarcia.dev) es profesor visitante en la Universidad Carlos III de
Madrid (UC3M) en España. Es un apasionado de la ingeniería de software con énfasis en
pruebas automatizadas. Es autor de más de 45 publicaciones, incluyendo conferencias
internacionales, revistas, capítulos de libros y el libro *Mastering Software Testing with
JUnit 5* (Packt). Es el creador y mantenedor de diferentes proyectos de código abierto
relacionados con Selenium, incluyendo WebDriverManager (una conocida biblioteca
auxiliar para Selenium WebDriver en Java) y Selenium-Jupiter (una extensión de JUnit 5
para Selenium WebDriver).
Presentó su tesis doctoral, enfocada en navegación web automatizada y pruebas, en
2011. La implementación de referencia de este trabajo utilizó Selenium IDE y Remote
Control como herramientas fundamentales. Continuó investigando pruebas
automatizadas de 2013 a 2020, participando en los proyectos de código abierto Kurento,
OpenVidu y ElasTest. Durante este período, adoptó Selenium WebDriver como el marco
fundamental para evaluar aplicaciones WebRTC y Calidad de Experiencia. Actualmente,
sigue trabajando activamente con Selenium. Sus últimas publicaciones se centran en el
ecosistema de Selenium y en la gestión automatizada de controladores en Selenium
WebDriver. Comenzó a colaborar con Sauce Labs como ingeniero de código abierto en
2022.
Colofón
El animal en la portada de Hands-On Selenium WebDriver with Java es el crested shriketit
(Falcunculus frontatus). Endémico del continente australiano, estos llamativos pájaros
se pueden encontrar en bosques de eucalipto y áreas arboladas, barrancos forestales y
a lo largo de ríos en zonas más secas. Esta especie a veces se localiza escuchando su
repetido y lastimero silbido, pero como a menudo imita a otros pájaros, la identificación
puede ser difícil.
El crested shriketit es un pájaro de tamaño mediano con una cabeza rayada en blanco y
negro, una pequeña cresta y un pico corto con una punta ganchuda. Tiene alas amplias
y redondeadas y una cola cuadrada, ligeramente bifurcada. Los machos son más grandes
que las hembras, y también se pueden distinguir por sus gargantas negras (las hembras
tienen gargantas de color verde oliva). El shriketit se alimenta principalmente de
insectos, pero a veces también consume frutas y semillas. El pájaro adquiere su alimento
usando su fuerte pico para desgarrar o sondear la corteza de los árboles en busca de
insectos. Los shriketits suelen forrajear solos, pero se pueden encontrar en parejas,
grupos de hasta cinco individuos (generalmente un grupo familiar), o en bandadas
mixtas con otros pájaros insectívoros.
La temporada de cría del crested shriketit generalmente dura desde agosto hasta enero.
El macho selecciona un sitio para el nido en una horquilla alta de un eucalipto, atrayendo
a la hembra hacia él con alas temblorosas y moviendo. La hembra construye un nido
profundo en forma de cono a partir de hierba seca y tiras de corteza, cubriendo el
exterior con telarañas, musgo y líquenes. El macho ayuda a recolectar materiales, y
ambos sexos incuban los huevos y alimentan a los polluelos. El período de incubación
dura aproximadamente 20 días. Se pueden criar dos nidadas en una temporada, y los
polluelos a menudo permanecen con sus padres hasta el comienzo de la siguiente
temporada de cría. Los nidos son a menudo parasitados por diversas especies de cucos.
Entre todas las especies del crested shriketit, la subespecie del norte, whitei, es la que
más riesgo de extinción tiene. Está en peligro en Australia Occidental, ocurriendo en
densidades tan bajas en algunas áreas que las poblaciones pueden no ser capaces de
renovarse y están aisladas entre sí. Más importante aún, los incendios intensos y
generalizados en la temporada seca disminuyen la capacidad de los insectos para
establecerse bajo la corteza, reduciendo así la principal fuente de alimento del shriketit.
La subespecie occidental, leucogaster, también está en peligro cercano y se ve afectada
por la limpieza de tierras en la zona de trigo. La subespecie oriental, frontatus, está
amenazada por el desarrollo urbano. Muchos de los animales en las portadas de O’Reilly
están en peligro; todos ellos son importantes para el mundo.
La ilustración de la portada es de Karen Montgomery, basada en un grabado antiguo de
Lydekker’s Royal Natural History. Las fuentes de la portada son Gilroy Semibold y
Guardian Sans. La fuente del texto es Adobe Minion Pro; la fuente de los encabezados
es Adobe Myriad Condensed; y la fuente del código es Ubuntu Mono de Dalton Maag.