Capítulo 4. Funciones para el navegador
Este trabajo se ha traducido utilizando IA. Agradecemos tus opiniones y comentarios: translation-feedback@oreilly.com
En este capítulo se revisan aquellas características de de Selenium WebDriver que son interoperables en distintos navegadores web. En este grupo, una característica polivalente 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 la pantalla del navegador, o sólo de la parte correspondiente a un elemento determinado. A continuación, podemos gestionar distintos aspectos del navegador controlado mediante WebDriver, como el tamaño y la posición del navegador, el historial o las cookies. A continuación, WebDriver proporciona diversos recursos para controlar elementos web específicos, como listas desplegables (es decir, campos de selección 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, avisos, confirmaciones y diálogos modales). Por último, descubriremos cómo manejar datos locales y de sesión utilizando almacenamiento web, implementar escuchadores de eventos y utilizar las excepciones que proporciona la API de Selenium WebDriver.
Ejecutar JavaScript
JavaScript es un lenguaje de programación de alto nivel soportado por los principales navegadores. Podemos utilizar JavaScript en el lado cliente de las aplicaciones web para una gran variedad de operaciones, como la manipulación del DOM, la interacción con el usuario, el manejo de peticiones-respuestas de servidores remotos o el trabajo con expresiones regulares, entre otras muchas funciones. Por suerte para la automatización de pruebas, Selenium WebDriver permite inyectar y ejecutar trozos 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 mediante distintos ejemplos.
Cualquier objeto controlador que herede de la clase RemoteWebDriver
también implementa la interfaz JavascriptExecutor
. Por lo tanto, cuando utilicemos un navegador principal (por ejemplo, ChromeDriver
, FirefoxDriver
, etc.) declarado utilizando la interfaz genérica WebDriver
, podemos lanzarlo a JavascriptExecutor
como se muestra en el siguiente fragmento. A continuación, podemos utilizar el ejecutor (utilizando la variable js
en el ejemplo) para invocar los métodos presentados en la Tabla 4-1.
WebDriver
driver
=
new
ChromeDriver
();
JavascriptExecutor
js
=
(
JavascriptExecutor
)
driver
;
Scripts sincrónicos
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 finaliza el script. Por lo tanto, normalmente utilizamos este método para ejecutar scripts síncronos en una página web bajo prueba. El método executeScript()
admite dos argumentos:
String script
-
Obligatorio Fragmento de JavaScript que se ejecutará. Este código se ejecuta en el cuerpo de la página actual como una función anónima (es decir, una función JavaScript sin nombre).
Object... args
-
Argumentos opcionales script. Estos argumentos deben ser uno de los siguientes tipos: número, booleano, cadena,
WebElement
, o unList
de estos tipos (de lo contrario, WebDriver lanza una excepción). Estos argumentos están disponibles en el script inyectado mediante la variable JavaScript incorporadaarguments
.
Cuando el script devuelve algún valor (es decir, el código contiene una sentencia return
), el método Selenium WebDriver executeScript()
también devuelve un valor en Java (de lo contrario, executeScript()
devuelve null
). Los posibles tipos devueltos son:
WebElement
-
Al devolver un elemento HTML
Double
-
Para decimales
Long
-
Para números no decimales
Boolean
-
Para valores booleanos
List<Object>
-
Para matrices
Map<String, Object>
-
Para colecciones clave-valor
String
-
Para todos los demás casos
Las situaciones que requieren ejecutar JavaScript con Selenium WebDriver son muy heterogéneas. En los siguientes subapartados se revisan dos casos en los que Selenium WebDriver no proporciona funciones integradas y, en su lugar, necesitamos utilizar JavaScript para automatizarlas: el desplazamiento por una página web y el manejo de un selector de color en un formulario web.
Desplazamiento
Como se explica en el Capítulo 3, Selenium WebDriver permite imitar distintas acciones del ratón, como hacer clic, clic con el botón derecho o doble clic, entre otras. Sin embargo, desplazarse hacia abajo o hacia arriba por una página web no es posible mediante la API de Selenium WebDriver. En su lugar, podemos lograr esta automatización fácilmente ejecutando una simple línea JavaScript. En el Ejemplo 4-1 se muestra un ejemplo básico utilizando 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 de ejecución de JavaScript para desplazarse hacia abajo una cantidad de píxeles
@Test
void
testScrollBy
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/long-page.html"
)
;
JavascriptExecutor
js
=
(
JavascriptExecutor
)
driver
;
String
script
=
"window.scrollBy(0, 1000);"
;
js
.
executeScript
(
script
)
;
}
Abre una página web de la práctica que contenga un texto muy largo (ver Figura 4-1).
Transfiere el objeto
driver
aJavascriptExecutor
. Utilizaremos la variablejs
para ejecutar JavaScript en el navegador.Ejecuta un fragmento de código JavaScript. En este caso, llama a la función de JavaScript
scrollBy()
para desplazar el documento una cantidad determinada (en este caso, 1.000 px hacia abajo). Observa que este fragmento no utilizareturn
, y por tanto, no recibimos ningún objeto devuelto en la lógica Java. Además, no pasamos ningún argumento al script.
El Ejemplo 4-2 muestra otra prueba utilizando el desplazamiento y la misma página web de ejemplo que antes. Esta vez, en lugar de desplazarnos un número fijo de píxeles, desplazamos el scroll del documento hasta el último párrafo de la página web.
Ejemplo 4-2. Prueba de ejecución de JavaScript para desplazarse hacia abajo hasta un elemento determinado
@Test
void
testScrollIntoView
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/long-page.html"
)
;
JavascriptExecutor
js
=
(
JavascriptExecutor
)
driver
;
driver
.
manage
(
)
.
timeouts
(
)
.
implicitlyWait
(
Duration
.
ofSeconds
(
10
)
)
;
WebElement
lastElememt
=
driver
.
findElement
(
By
.
cssSelector
(
"p:last-child"
)
)
;
String
script
=
"arguments[0].scrollIntoView();"
;
js
.
executeScript
(
script
,
lastElememt
)
;
}
Para que esta prueba sea robusta, especificamos en 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 posteriores.
Localizamos el último párrafo en la página web utilizando un selector CSS.
Definimos el script que se inyectará en la página. Observa que el script no devuelve ningún valor, pero como novedad, utiliza el primer argumento de la función para invocar la función JavaScript
scrollIntoView()
.Ejecutamos el script anterior, pasando como argumento el
WebElement
localizado. Este elemento será el primer argumento del 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 utilizar un enfoque similar para rastrear páginas web utilizando Selenium WebDriver. El Ejemplo 4-3 muestra una prueba utilizando una página de desplazamiento infinito.
Ejemplo 4-3. Prueba de ejecución de JavaScript en una página de desplazamiento infinito
@Test
void
testInfiniteScroll
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/infinite-scroll.html"
)
;
JavascriptExecutor
js
=
(
JavascriptExecutor
)
driver
;
WebDriverWait
wait
=
new
WebDriverWait
(
driver
,
Duration
.
ofSeconds
(
10
)
)
;
By
pLocator
=
By
.
tagName
(
"p"
)
;
List
<
WebElement
>
paragraphs
=
wait
.
until
(
ExpectedConditions
.
numberOfElementsToBeMoreThan
(
pLocator
,
0
)
)
;
int
initParagraphsNumber
=
paragraphs
.
size
(
)
;
WebElement
lastParagraph
=
driver
.
findElement
(
By
.
xpath
(
String
.
format
(
"//p[%d]"
,
initParagraphsNumber
)
)
)
;
String
script
=
"arguments[0].scrollIntoView();"
;
js
.
executeScript
(
script
,
lastParagraph
)
;
wait
.
until
(
ExpectedConditions
.
numberOfElementsToBeMoreThan
(
pLocator
,
initParagraphsNumber
)
)
;
}
Definimos una espera explícita ya que necesitamos pausar la prueba hasta que se cargue el nuevo contenido.
Encontramos el número inicial de párrafos de la página.
Localizamos el último párrafo de la página.
Nos desplazamos hacia abajo en este elemento.
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 por un área gráfica. El formulario web de la práctica contiene uno de estos elementos (ver Figura 4-2).
El siguiente código muestra el código HTML del selector de color. Observa que establece un valor de color inicial (de lo contrario, el color por defecto es el negro).
<
input
type
=
"color"
class
=
"form-control form-control-color"
name
=
"my-colors"
value
=
"#563d7c"
>
El Ejemplo 4-4 ilustra cómo interactuar con este selector de color. Como la API de Selenium WebDriver no proporciona ningún activo para controlar los selectores de color, utilizamos 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 de ejecución de JavaScript para interactuar con un selector de color
@Test
void
testColorPicker
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/web-form.html"
)
;
JavascriptExecutor
js
=
(
JavascriptExecutor
)
driver
;
WebElement
colorPicker
=
driver
.
findElement
(
By
.
name
(
"my-colors"
)
)
;
String
initColor
=
colorPicker
.
getAttribute
(
"value"
)
;
log
.
debug
(
"The initial color is {}"
,
initColor
)
;
Color
red
=
new
Color
(
255
,
0
,
0
,
1
)
;
String
script
=
String
.
format
(
"arguments[0].setAttribute('value', '%s');"
,
red
.
asHex
(
)
)
;
js
.
executeScript
(
script
,
colorPicker
)
;
String
finalColor
=
colorPicker
.
getAttribute
(
"value"
)
;
log
.
debug
(
"The final color is {}"
,
finalColor
)
;
assertThat
(
finalColor
)
.
isNotEqualTo
(
initColor
)
;
assertThat
(
Color
.
fromString
(
finalColor
)
)
.
isEqualTo
(
red
)
;
}
Localizamos el selector de color por su nombre.
Leemos el valor inicial del selector de color (debe ser
#563d7c
).Definimos un color con el que trabajar utilizando 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, totalmente opaco).
Utilizamos JavaScript para cambiar el valor seleccionado en el selector de color. Alternativamente, podemos cambiar el color seleccionado invocando la sentencia
colorPicker.sendKeys(red.asHex());
.Leemos el valor resultante del selector de color (debe ser
#ff0000
).Afirmamos que el color es diferente del valor inicial , pero como era de esperar.
Guiones anclados
La API de Selenium WebDriver te permite fijar secuencias de comandos en Selenium WebDriver 4. Esta función permite anclar fragmentos de JavaScript a una sesión de WebDriver, asignar una clave única a cada fragmento y ejecutar estos fragmentos bajo demanda (incluso en páginas web diferentes). El Ejemplo 4-5 muestra una prueba en la que se utilizan secuencias de comandos ancladas.
Ejemplo 4-5. Prueba de ejecución de JavaScript como scripts anclados
@Test
void
testPinnedScripts
(
)
{
String
initPage
=
"https://bonigarcia.dev/selenium-webdriver-java/"
;
driver
.
get
(
initPage
)
;
JavascriptExecutor
js
=
(
JavascriptExecutor
)
driver
;
ScriptKey
linkKey
=
js
.
pin
(
"return document.getElementsByTagName('a')[2];"
)
;
ScriptKey
firstArgKey
=
js
.
pin
(
"return arguments[0];"
)
;
Set
<
ScriptKey
>
pinnedScripts
=
js
.
getPinnedScripts
(
)
;
assertThat
(
pinnedScripts
)
.
hasSize
(
2
)
;
WebElement
formLink
=
(
WebElement
)
js
.
executeScript
(
linkKey
)
;
formLink
.
click
(
)
;
assertThat
(
driver
.
getCurrentUrl
(
)
)
.
isNotEqualTo
(
initPage
)
;
String
message
=
"Hello world!"
;
String
executeScript
=
(
String
)
js
.
executeScript
(
firstArgKey
,
message
)
;
assertThat
(
executeScript
)
.
isEqualTo
(
message
)
;
js
.
unpin
(
linkKey
)
;
assertThat
(
js
.
getPinnedScripts
(
)
)
.
hasSize
(
1
)
;
}
Adjuntamos un fragmento de JavaScript para localizar un elemento en la página web. Ten en cuenta que podríamos hacer lo mismo con la API estándar de WebDriver. No obstante, utilizaremos este método para la demostración.
Adjuntamos otro fragmento de JavaScript que devuelve lo que le pasemos como primer parámetro.
Leemos el conjunto de guiones fijados.
Afirmamos que el número de guiones anclados es el esperado (es decir,
2
).Ejecutamos el primer script fijado. Como resultado, obtenemos el tercer enlace de la página web como
WebElement
en Java.Hacemos clic en este enlace, que debería corresponder al enlace web de la práctica. Como resultado, el navegador debería navegar hasta esa página.
Afirmamos que la URL actual es diferente de la inicial.
Ejecutamos el segundo script anclado. Observa que es posible ejecutar el script anclado aunque la página haya cambiado en el navegador (ya que el script está anclado a la sesión y no a una sola página).
Afirmamos que el mensaje devuelto es el esperado.
Desenganchamos uno de los guiones.
Comprobamos que el número de guiones anclados es el esperado (es decir,
1
en este punto).
Scripts asíncronos
El método executeAsyncScript()
de la interfaz JavascriptExecutor
permite ejecutar scripts JavaScript en el contexto de una página web utilizando Selenium WebDriver. De la misma forma que executeScript()
explicada 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 finalización del script invocando una llamada de retorno a hecho. Esta llamada de retorno se inyecta en el script ejecutado como último argumento (es decir, arguments[arguments.length - 1]
) en la función anónima correspondiente. El Ejemplo 4-6 muestra una prueba que utiliza este mecanismo.
Ejemplo 4-6. Prueba de ejecución de JavaScript asíncrono
@Test
void
testAsyncScript
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/"
)
;
JavascriptExecutor
js
=
(
JavascriptExecutor
)
driver
;
Duration
pause
=
Duration
.
ofSeconds
(
2
)
;
String
script
=
"const callback = arguments[arguments.length - 1];"
+
"window.setTimeout(callback, "
+
pause
.
toMillis
(
)
+
");"
;
long
initMillis
=
System
.
currentTimeMillis
(
)
;
js
.
executeAsyncScript
(
script
)
;
Duration
elapsed
=
Duration
.
ofMillis
(
System
.
currentTimeMillis
(
)
-
initMillis
)
;
log
.
debug
(
"The script took {} ms to be executed"
,
elapsed
.
toMillis
(
)
)
;
assertThat
(
elapsed
)
.
isGreaterThanOrEqualTo
(
pause
)
;
}
Definimos un tiempo de pausa de
2
segundos.Definimos el script que se va a ejecutar. En la primera línea, definimos una constante para la llamada de retorno (es decir, el último argumento del script). Después, utilizamos la función de JavaScript
window.setTimeout()
para pausar la ejecución del script durante un tiempo determinado.Obtenemos la hora actual del sistema (en milisegundos).
Ejecutamos el script. Si todo funciona como se espera, la ejecución de la prueba se bloquea en esta línea durante segundos (como se definió en el paso 1).
Calculamos el tiempo necesario para ejecutar la línea anterior.
Afirmamos que el tiempo transcurrido es el esperado (normalmente, unos milisegundos por encima del tiempo de pausa definido).
Consejo
Puedes encontrar un ejemplo adicional que ejecuta un script asíncrono en "Notificaciones".
Tiempos muertos
Selenium WebDriver permite especificar tres tipos de tiempos de espera. Podemos utilizarlos invocando al método manage().timeouts()
en la API de Selenium WebDriver. El primer tiempo de espera es la espera implícita, ya explicada en "Espera implícita" (como parte de las estrategias de espera). Las otras opciones son los tiempos de espera de carga de página y de carga de script, que se explican a continuación.
Tiempo de espera de carga de la página
El tiempo de espera de carga de la página proporciona un límite de tiempo para interrumpir un intento de navegación. En otras palabras, este tiempo de espera limita el tiempo en el que se carga una página web. Cuando se supera este tiempo de espera (que tiene un valor por defecto de 30 segundos), se lanza una excepción. El Ejemplo 4-7 muestra un ejemplo de este tiempo de espera. Como puedes ver, este fragmento de código de es una implementación ficticia de una prueba negativa. En otras palabras, comprueba condiciones inesperadas en el SUT.
Ejemplo 4-7. Prueba utilizando un tiempo de espera de carga de página
@Test
void
testPageLoadTimeout
(
)
{
driver
.
manage
(
)
.
timeouts
(
)
.
pageLoadTimeout
(
Duration
.
ofMillis
(
1
)
)
;
assertThatThrownBy
(
(
)
-
>
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/"
)
)
.
isInstanceOf
(
TimeoutException
.
class
)
;
}
Especificamos el tiempo mínimo posible de espera de carga de la página, que es de un milisegundo.
Cargamos una página web. Esta invocación (implementada como lambda de Java) fallará, ya que es imposible cargar esa página web en menos de un milisegundo. Por este motivo, se espera que se lance la excepción
TimeoutException
en la lambda, utilizando el método AssertJassertThatThrownBy
.
Nota
Puedes jugar con esta prueba eliminando la declaración de tiempo de espera (es decir, el paso 1). Si lo haces, la prueba fallará, ya que se espera una excepción, pero no se lanza.
Tiempo de espera de carga del script
El tiempo de espera de carga del script proporciona un límite de tiempo para interrumpir un script que se está evaluando. Este tiempo de espera tiene un valor por defecto de trescientos segundos. El Ejemplo 4-8 muestra una prueba que utiliza un tiempo de espera de carga de script.
Ejemplo 4-8. Prueba utilizando un tiempo de espera de carga de script
@Test
void
testScriptTimeout
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/"
)
;
JavascriptExecutor
js
=
(
JavascriptExecutor
)
driver
;
driver
.
manage
(
)
.
timeouts
(
)
.
scriptTimeout
(
Duration
.
ofSeconds
(
3
)
)
;
assertThatThrownBy
(
(
)
-
>
{
long
waitMillis
=
Duration
.
ofSeconds
(
5
)
.
toMillis
(
)
;
String
script
=
"const callback = arguments[arguments.length - 1];"
+
"window.setTimeout(callback, "
+
waitMillis
+
");"
;
js
.
executeAsyncScript
(
script
)
;
}
)
.
isInstanceOf
(
ScriptTimeoutException
.
class
)
;
}
Definimos un tiempo de espera del script de tres segundos. Esto significa que un script que dure más de ese tiempo lanzará una excepción.
Ejecutamos un script asíncrono que pausa la ejecución de cinco segundos.
El tiempo de ejecución del script es mayor que el tiempo de espera del script configurado, lo que provoca una
ScriptTimeoutException
. De nuevo, este ejemplo es una prueba negativa, es decir, diseñada 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 utilizamos para verificar que las aplicaciones web se comportan como se espera interactuando con su interfaz de usuario (es decir, utilizando un navegador web). Este enfoque es muy cómodo para automatizar escenarios de usuario de alto nivel, pero también presenta distintas dificultades. Uno de los principales retos de las pruebas de extremo a extremo es diagnosticar la causa subyacente de una prueba fallida. Suponiendo que el fallo sea legítimo (es decir, no inducido por una prueba mal implementada), la causa raíz puede ser diversa: el lado del cliente (por ejemplo, una lógica JavaScript incorrecta), el lado del servidor (por ejemplo, una excepción interna), o la integración con otros componentes (por ejemplo, un acceso inadecuado a la base de datos), entre otras razones. Uno de los mecanismos más utilizados en Selenium WebDriver para el análisis de fallos es hacer capturas de pantalla del navegador. Esta sección presenta los mecanismos que proporciona la API de Selenium WebDriver.
Consejo
" Análisis de fallos" repasa las técnicas específicas del marco para determinar cuándo ha fallado una prueba 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 controlador que herede de RemoteWebDriver
(ver Figura 2-2) también implementa esta interfaz. Así, podemos lanzar un objeto WebDriver
que instancie uno de los principales navegadores (por ejemplo, ChromeDriver
, FirefoxDriver
, etc.) de la siguiente manera:
WebDriver
driver
=
new
ChromeDriver
();
TakesScreenshot
ts
=
(
TakesScreenshot
)
driver
;
La interfaz TakesScreenshot
sólo 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 devuelto. La Tabla 4-2 muestra las alternativas disponibles para este parámetro.
Consejo
El método getScreenshotAs()
permite hacer capturas de pantalla de la ventana del navegador. Además, Selenium WebDriver 4 permite crear capturas de pantalla de página completa utilizando distintos mecanismos (consulta "Captura de pantalla de página completa").
El Ejemplo 4-9 muestra una prueba para hacer una captura de pantalla del navegador en formato PNG. El Ejemplo 4-10 muestra otra prueba para crear una captura de pantalla como cadena Base64. La captura de pantalla resultante se muestra en la Figura 4-3.
Ejemplo 4-9. Prueba de hacer una captura de pantalla como archivo PNG
@Test
void
testScreenshotPng
(
)
throws
IOException
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/"
)
;
TakesScreenshot
ts
=
(
TakesScreenshot
)
driver
;
File
screenshot
=
ts
.
getScreenshotAs
(
OutputType
.
FILE
)
;
log
.
debug
(
"Screenshot created on {}"
,
screenshot
)
;
Path
destination
=
Paths
.
get
(
"screenshot.png"
)
;
Files
.
move
(
screenshot
.
toPath
(
)
,
destination
,
REPLACE_EXISTING
)
;
log
.
debug
(
"Screenshot moved to {}"
,
destination
)
;
assertThat
(
destination
)
.
exists
(
)
;
}
Hacemos que la pantalla del navegador sea un archivo PNG.
Este archivo se encuentra por defecto en una carpeta temporal, así que lo movemos a un nuevo archivo llamado
screenshot.png
(en la carpeta raíz del proyecto).Utilizamos Java estándar para mover el archivo de captura de pantalla a la nueva ubicación.
Utilizamos aserciones para verificar que el archivo de destino existe.
Ejemplo 4-10. Prueba de hacer una captura de pantalla como Base64
@Test
void
testScreenshotBase64
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/"
)
;
TakesScreenshot
ts
=
(
TakesScreenshot
)
driver
;
String
screenshot
=
ts
.
getScreenshotAs
(
OutputType
.
BASE64
)
;
log
.
debug
(
"Screenshot in base64 "
+
"(you can copy and paste it into a browser navigation bar to watch it)\n"
+
"data:image/png;base64,{}"
,
screenshot
)
;
assertThat
(
screenshot
)
.
isNotEmpty
(
)
;
}
Hacemos la pantalla del navegador en formato Base64.
Añadimos 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.Afirmamos que la cadena de capturas de pantalla tiene contenido.
Nota
Registrar la captura de pantalla de en Base64 como se presenta en el ejemplo anterior podría ser muy útil para diagnosticar fallos al ejecutar pruebas en servidores CI en los que no tenemos acceso al sistema de archivos (por ejemplo, Acciones de GitHub).
Capturas de pantalla de WebElement
La interfaz WebElement
amplía la interfaz TakesScreenshot
. De esta forma, es posible realizar capturas parciales del contenido visible de un elemento web determinado. (Ver Ejemplo 4-11.) Observa que esta prueba es muy similar a la anterior utilizando archivos PNG, pero en este caso, invocamos directamente al método getScreenshotAs()
utilizando un elemento web. La Figura 4-4 muestra la captura de pantalla resultante.
Ejemplo 4-11. Prueba de hacer una captura de pantalla parcial como archivo PNG
@Test
void
testWebElementScreenshot
()
throws
IOException
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/web-form.html"
);
WebElement
form
=
driver
.
findElement
(
By
.
tagName
(
"form"
));
File
screenshot
=
form
.
getScreenshotAs
(
OutputType
.
FILE
);
Path
destination
=
Paths
.
get
(
"webelement-screenshot.png"
);
Files
.
move
(
screenshot
.
toPath
(),
destination
,
REPLACE_EXISTING
);
assertThat
(
destination
).
exists
();
}
Tamaño y posición de la ventana
La API de Selenium WebDriver permite manipular el tamaño y la posición del navegador muy fácilmente utilizando la interfaz Window
. Este tipo es accesible desde un objeto controlador mediante la siguiente sentencia. La Tabla 4-3 muestra los métodos disponibles en esta interfaz. A continuación, el Ejemplo 4-12 muestra una prueba básica sobre esta función.
Window
window
=
driver
.
manage
().
window
();
Método | Devuelve | Descripción |
---|---|---|
|
|
Obtiene el tamaño actual de la ventana. Devuelve la dimensión exterior de la ventana, no sólo la ventana gráfica (es decir, el área visible de una página web para los usuarios finales). |
|
|
Cambia el tamaño actual de la ventana (de nuevo, su dimensión exterior, y no la ventana gráfica). |
|
|
Obtiene la posición actual de la ventana (relativa a la esquina superior izquierda de la pantalla). |
|
|
Cambia la posición actual de la ventana (de nuevo, relativa a la esquina superior izquierda de la pantalla). |
|
|
Maximiza la ventana actual. |
|
|
Minimiza la ventana actual. |
|
|
Pone a pantalla completa la ventana actual. |
Ejemplo 4-12. Prueba de lectura y cambio de tamaño y posición del navegador
@Test
void
testWindow
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/"
)
;
Window
window
=
driver
.
manage
(
)
.
window
(
)
;
Point
initialPosition
=
window
.
getPosition
(
)
;
Dimension
initialSize
=
window
.
getSize
(
)
;
log
.
debug
(
"Initial window: position {} -- size {}"
,
initialPosition
,
initialSize
)
;
window
.
maximize
(
)
;
Point
maximizedPosition
=
window
.
getPosition
(
)
;
Dimension
maximizedSize
=
window
.
getSize
(
)
;
log
.
debug
(
"Maximized window: position {} -- size {}"
,
maximizedPosition
,
maximizedSize
)
;
assertThat
(
initialPosition
)
.
isNotEqualTo
(
maximizedPosition
)
;
assertThat
(
initialSize
)
.
isNotEqualTo
(
maximizedSize
)
;
}
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
. Utilizar 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. Observa que esta prueba navega por diferentes páginas web utilizando estos métodos, y al final de la prueba, verifica que la URL de la página web es la esperada.
Navigation
navigation
=
driver
.
navigate
();
Método | Devuelve | Descripción |
---|---|---|
|
|
Retroceder en el historial del navegador |
|
|
Avanzar en el historial del navegador |
|
|
Cargar una nueva página web en la ventana actual |
|
|
Actualizar la página actual |
La sombra DOM
Como se introdujo en "El Modelo de Objetos del Documento (DOM)", el DOM es una interfaz de programación que nos permite representar y manipular una página web utilizando una estructura de árbol. El shadow DOM es una característica de esta interfaz de programación que permite la creación de subárboles de ámbito dentro del árbol DOM normal. El shadow DOM permite encapsular un grupo de un subárbol DOM (llamado árbol sombra, como se representa en la Figura 4-5) que puede especificar estilos CSS diferentes del DOM original. El nodo del DOM normal en el que se adjunta el árbol sombra se denomina anfitrión sombra. El nodo raíz del árbol de sombra se denomina raíz de sombra. Como se representa en la Figura 4-5, el árbol de sombra se aplana en el DOM original en un único árbol compuesto para ser renderizado en el navegador.
Nota
El shadow DOM forma parte del conjunto estándar (junto con las plantillas HTML o elementos personalizados) que permite implementar componentes web (es decir, elementos personalizados reutilizables para aplicaciones web).
El DOM en la sombra permite crear 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 reto para las pruebas automatizadas con Selenium WebDriver (ya que las estrategias de localización normales no pueden encontrar elementos web dentro del árbol sombra). Por suerte, Selenium WebDriver 4 proporciona un método WebElement
que permite acceder al shadow DOM. El Ejemplo 4-14 demuestra este uso.
Ejemplo 4-14. Prueba de lectura de la sombra DOM
@Test
void
testShadowDom
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/shadow-dom.html"
)
;
WebElement
content
=
driver
.
findElement
(
By
.
id
(
"content"
)
)
;
SearchContext
shadowRoot
=
content
.
getShadowRoot
(
)
;
WebElement
textElement
=
shadowRoot
.
findElement
(
By
.
cssSelector
(
"p"
)
)
;
assertThat
(
textElement
.
getText
(
)
)
.
contains
(
"Hello Shadow DOM"
)
;
}
Abrimos la página web de prácticas que contiene un árbol de sombras. Puedes inspeccionar el código fuente de esta página para comprobar el método JavaScript utilizado para crear un árbol sombra.
Obtenemos la raíz sombra del elemento anfitrión. Como resultado, obtenemos una instancia de
SearchContext
, una interfaz implementada porWebDriver
yWebElement
, que nos permite encontrar elementos utilizando los métodosfindElement()
yfindElements()
.Encontramos el primer elemento del párrafo en el árbol de sombras.
Verificamos que el contenido del texto del elemento sombra es el esperado.
Advertencia
Esta función de la especificación W3C WebDriver es reciente en el momento de escribir este artículo, por lo que es posible que no esté implementada en todos los controladores (por ejemplo, chromedriver, geckodriver). Por ejemplo, está disponible a partir de la versión 96 tanto de Chrome como de 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 peticiones. El mecanismo de cookies de es una extensión de HTTP que permite rastrear a los usuarios enviando pequeños fragmentos de texto llamados cookies del servidor al cliente. Estas cookies deben ser devueltas por los clientes, y de esta forma, los servidores recuerdan a sus clientes. Las cookies 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 permite una manipulación equivalente a , pero de forma programática. La API de Selenium WebDriver proporciona los métodos que se muestran en la Tabla 4-5 para conseguirlo. Son accesibles a través de la función manage()
de un objeto WebDriver
.
Método | Devuelve | Descripción |
---|---|---|
|
|
Añadir una nueva cookie |
|
|
Eliminar una cookie existente por su nombre |
|
|
Eliminar una cookie existente por ejemplo |
|
|
Eliminar todas las cookies |
|
|
Obtener todas las cookies |
|
|
Obtener una cookie por su nombre |
Como muestra esta tabla , la clase Cookie
proporciona una abstracción de una única 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 está visible la cookie (opcional)
Date expiry
-
Fecha de caducidad de la cookie (opcional)
boolean isSecure
-
Si la cookie requiere una conexión segura (opcional)
boolean isHttpOnly
-
Si esta cookie es una cookie sólo HTTP, 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 del mismo sitio, es decir, la cookie está restringida a un contexto de primera parte o del mismo sitio (opcional)
Método | Devuelve | Descripción |
---|---|---|
|
|
Leer el nombre de la cookie |
|
|
Leer el valor de la cookie |
|
|
Leer dominio de cookies |
|
|
Leer ruta de cookies |
|
|
Leer si la cookie requiere una conexión segura |
|
|
Leer si la cookie es sólo HTTP |
|
|
Leer la fecha de caducidad de las cookies |
|
|
Leer cookie contexto mismo sitio |
|
|
Comprueba los diferentes campos de la cookie y lanza un |
|
|
Mapea los valores de las cookies como un mapa clave-valor |
Los siguientes ejemplos muestran diferentes pruebas que gestionan cookies web con la API Selenium WebDriver. Estos ejemplos utilizan una página web de práctica que muestra las cookies del sitio en la GUI (ver Figura 4-6):
-
El Ejemplo 4-15 ilustra cómo leer las cookies existentes de un sitio web.
-
El Ejemplo 4-16 muestra cómo añadir nuevas cookies.
-
El Ejemplo 4-17 explica cómo editar las cookies existentes.
-
El Ejemplo 4-18 muestra cómo eliminar las cookies.
Obtenemos el objeto
Options
utilizado para gestionar las cookies.Leemos todas las cookies disponibles en esta página. Debe contener dos cookies.
Leemos la galleta con el nombre
username
.El valor de la cookie anterior debe ser
John Doe
.La última declaración no afecta a la prueba. Invocamos este comando para comprobar las cookies en la GUI del navegador.
Creamos una nueva cookie.
Añadimos la cookie a la página actual.
Leemos el valor de la cookie que acabamos de añadir.
Comprobamos que este valor es el esperado.
Listas desplegables
Un elemento típico de 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 representar estos campos son <select>
y <options>
. Como es habitual, el formulario web de la práctica contiene uno de estos elementos (ver Figura 4-7), definido en HTML como sigue:
<
select
class
=
"form-select"
name
=
"my-select"
>
<
option
selected
>
Open this select menu</
option
>
<
option
value
=
"1"
>
One</
option
>
<
option
value
=
"2"
>
Two</
option
>
<
option
value
=
"3"
>
Three</
option
>
</
select
>
Estos elementos están muy dispersos en los formularios web. Por esta razón, Selenium WebDriver proporciona una clase de ayuda llamada Select
para simplificar su manipulación. Esta clase envuelve un WebElement
selecto y proporciona una amplia variedad de funciones. La Tabla 4-7 resume los métodos públicos disponibles en la clase Select
. A continuación, el Ejemplo 4-19 muestra una prueba básica utilizando esta clase.
Ejemplo 4-19. Prueba de interacción con un campo de selección
@Test
void
test
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/web-form.html"
)
;
Select
select
=
new
Select
(
driver
.
findElement
(
By
.
name
(
"my-select"
)
)
)
;
String
optionLabel
=
"Three"
;
select
.
selectByVisibleText
(
optionLabel
)
;
assertThat
(
select
.
getFirstSelectedOption
(
)
.
getText
(
)
)
.
isEqualTo
(
optionLabel
)
;
}
Buscamos el elemento selecto por su nombre y utilizamos el
WebElement
resultante para instanciar un objetoSelect
.Seleccionamos una de las opciones disponibles en esta selección, utilizando una estrategia por texto.
Verificamos que el texto de la opción seleccionada es el esperado.
Elementos de la lista de datos
Otra forma de implementar listas desplegables en HTML es utilizando listas de datos. Aunque las listas de datos de son muy similares a los elementos de selección desde un punto de vista gráfico, existe una clara distinción entre ellas. Por un lado, los campos de selección 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 a un campo de formulario de entrada (texto), y los usuarios son libres de seleccionar uno de esos valores sugeridos o escribir un valor personalizado. El formulario web de la 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.
<
input
class
=
"form-control"
list
=
"my-options"
name
=
"my-datalist"
placeholder
=
"Type to search..."
>
<
datalist
id
=
"my-options"
>
<
option
value
=
"San Francisco"
>
<
option
value
=
"New York"
>
<
option
value
=
"Seattle"
>
<
option
value
=
"Los Angeles"
>
<
option
value
=
"Chicago"
>
</
datalist
>
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
@Test
void
testDatalist
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/web-form.html"
)
;
WebElement
datalist
=
driver
.
findElement
(
By
.
name
(
"my-datalist"
)
)
;
datalist
.
click
(
)
;
WebElement
option
=
driver
.
findElement
(
By
.
xpath
(
"//datalist/option[2]"
)
)
;
String
optionValue
=
option
.
getAttribute
(
"value"
)
;
datalist
.
sendKeys
(
optionValue
)
;
assertThat
(
optionValue
)
.
isEqualTo
(
"New York"
)
;
}
Objetivos de navegación
Cuando navegamos por páginas web utilizando un navegador, por defecto, utilizamos una única página correspondiente a la URL de la barra de navegación. Entonces, 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, normalmente utilizando la tecla modificadora Ctrl (o Cmd en macOS) junto con el clic del ratón en un enlace web. Otra posibilidad es abriendo páginas web en ventanas nuevas. Para ello, las páginas web suelen utilizar el comando JavaScript window.open(url)
. Otra forma de mostrar diferentes páginas al mismo tiempo es utilizando marcos e iframes. Un marco es un tipo de elemento HTML que define un área concreta (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 dentro de la actual.
Advertencia
No se recomienda utilizar marcos, ya que estos elementos tienen muchos inconvenientes, como problemas de rendimiento y accesibilidad. Explico cómo utilizarlos a través de Selenium WebDriver por motivos de compatibilidad. No obstante, recomiendo encarecidamente evitar los marcos en aplicaciones web completamente nuevas.
La API de Selenium WebDriver proporciona la interfaz TargetLocator
para tratar con los objetivos mencionados anteriormente (es decir, pestañas, ventanas, marcos e iframes). Esta interfaz permite cambiar el foco de los comandos futuros de un objeto WebDriver
(a una nueva pestaña, ventana, etc.). Se puede acceder a esta interfaz invocando el método switchTo()
en un objeto WebDriver
.En la Tabla 4-8 se describen sus métodos públicos.
Método | Devuelve | Descripción |
---|---|---|
|
|
Cambia el enfoque a un marco (o iframe) de por número de índice. |
|
|
Cambia el enfoque a un marco (o iframe) por nombre o id. |
|
|
Cambia el foco a un marco (o iframe) situado previamente como un WebElement. |
|
|
Cambia el enfoque al contexto padre. |
|
|
Cambia el foco a otra ventana, por nombre o manejador. Un manejador de ventana es una cadena hexadecimal que identifica unívocamente una ventana o pestaña. |
|
|
Crea una nueva ventana del navegador (utilizando |
|
|
Selecciona el documento principal (cuando utilices iframes) o el primer marco de la página (cuando utilices un conjunto de marcos). |
|
|
Obtiene el elemento actualmente seleccionado. |
|
|
Cambia el foco a una ventana de alerta (consulta "Cuadros de diálogo" para más detalles). |
Pestañas y ventanas
El Ejemplo 4-21 muestra una prueba en la que abrimos una nueva pestaña para navegar por 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. Observa que la diferencia entre estos ejemplos es sólo el parámetro WindowType.TAB
y WindowType.WINDOW
.
Ejemplo 4-21. Probar la apertura de una nueva pestaña
@Test
void
testNewTab
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/"
)
;
String
initHandle
=
driver
.
getWindowHandle
(
)
;
driver
.
switchTo
(
)
.
newWindow
(
WindowType
.
TAB
)
;
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/web-form.html"
)
;
assertThat
(
driver
.
getWindowHandles
(
)
.
size
(
)
)
.
isEqualTo
(
2
)
;
driver
.
switchTo
(
)
.
window
(
initHandle
)
;
driver
.
close
(
)
;
assertThat
(
driver
.
getWindowHandles
(
)
.
size
(
)
)
.
isEqualTo
(
1
)
;
}
Navegamos a una página web.
Obtenemos el asa de la ventana actual.
Abrimos otra página web (como el foco está en la segunda pestaña, la página se abre en la segunda pestaña).
Comprobamos que el número de asas de ventana en este punto es 2.
Cambiamos el foco de a la ventana inicial (utilizando su manejador).
Cerramos sólo la ventana actual. La segunda pestaña permanece abierta.
Comprobamos que ahora el número de asas de ventana es 1.
Ejemplo 4-22. Probar la apertura de una nueva ventana
@Test
void
testNewWindow
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/"
)
;
String
initHandle
=
driver
.
getWindowHandle
(
)
;
driver
.
switchTo
(
)
.
newWindow
(
WindowType
.
WINDOW
)
;
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/web-form.html"
)
;
assertThat
(
driver
.
getWindowHandles
(
)
.
size
(
)
)
.
isEqualTo
(
2
)
;
driver
.
switchTo
(
)
.
window
(
initHandle
)
;
driver
.
close
(
)
;
assertThat
(
driver
.
getWindowHandles
(
)
.
size
(
)
)
.
isEqualTo
(
1
)
;
}
Marcos e iframes
El Ejemplo 4-23 muestra una prueba en la que la página web sometida a prueba contiene un iframe. El Ejemplo 4-24 muestra el caso equivalente a pero utilizando un conjunto de marcos.
Ejemplo 4-23. Prueba de manejo de iframes
@Test
void
testIFrames
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/iframes.html"
)
;
WebDriverWait
wait
=
new
WebDriverWait
(
driver
,
Duration
.
ofSeconds
(
10
)
)
;
wait
.
until
(
ExpectedConditions
.
frameToBeAvailableAndSwitchToIt
(
"my-iframe"
)
)
;
By
pName
=
By
.
tagName
(
"p"
)
;
wait
.
until
(
ExpectedConditions
.
numberOfElementsToBeMoreThan
(
pName
,
0
)
)
;
List
<
WebElement
>
paragraphs
=
driver
.
findElements
(
pName
)
;
assertThat
(
paragraphs
)
.
hasSize
(
20
)
;
}
Abrimos una página web que contiene un iframe (ver Figura 4-9).
Utilizamos una espera explícita para esperar la trama y pasar a ella.
Utilizamos otra espera explícita para hacer una pausa hasta que los párrafos contenidos en el iframe estén disponibles.
Afirmamos que el número de párrafos es el esperado.
Ejemplo 4-24. Prueba de manipulación de tramas
@Test
void
testFrames
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/frames.html"
)
;
WebDriverWait
wait
=
new
WebDriverWait
(
driver
,
Duration
.
ofSeconds
(
10
)
)
;
String
frameName
=
"frame-body"
;
wait
.
until
(
ExpectedConditions
.
presenceOfElementLocated
(
By
.
name
(
frameName
)
)
)
;
driver
.
switchTo
(
)
.
frame
(
frameName
)
;
By
pName
=
By
.
tagName
(
"p"
)
;
wait
.
until
(
ExpectedConditions
.
numberOfElementsToBeMoreThan
(
pName
,
0
)
)
;
List
<
WebElement
>
paragraphs
=
driver
.
findElements
(
pName
)
;
assertThat
(
paragraphs
)
.
hasSize
(
20
)
;
}
Abrimos una página web que contiene un conjunto de marcos (ver Figura 4-10).
Esperamos a que la trama esté disponible. Observa que los pasos 2 y 3 del Ejemplo 4-23 son equivalentes a este paso.
Cambiamos el enfoque a este marco.
Cuadros de diálogo
JavaScript proporciona diferentes cuadros de diálogo (a veces llamados ventanas emergentes) para interactuar con el usuario, a saber:
- Alerta
-
Para mostrar un mensaje y esperar a que el usuario pulse el botón Aceptar (única opción del diálogo). Por ejemplo, el siguiente código abrirá un diálogo que muestra "¡Hola mundo!" y espera a que el usuario pulse el botón Aceptar.
alert
(
"Hello world!"
);
- Confirma
-
Para mostrar un cuadro de diálogo con una pregunta y dos botones: Aceptar y Cancelar. Por ejemplo, el siguiente código abrirá un cuadro de diálogo mostrando el mensaje "¿Es esto correcto?" e invitando al usuario a pulsar OK o Cancelar.
let
correct
=
confirm
(
"Is this correct?"
);
- Pregunta
-
Para mostrar un cuadro de diálogo con un mensaje de texto, un campo de texto de entrada y los botones Aceptar y Cancelar. Por ejemplo, el siguiente código muestra una ventana emergente con el mensaje "Por favor, introduce tu nombre", un cuadro de diálogo en el que el usuario puede escribir, y dos botones (Aceptar y Cancelar).
let
username
=
prompt
(
"Please enter your name"
);
Además, CSS permite implementar en otro tipo de cuadro de diálogo llamado ventana modal. Este cuadro de diálogo desactiva la ventana principal (pero la mantiene visible) mientras superpone una ventana emergente hija, que suele mostrar un mensaje y algunos botones. Puedes encontrar una página de ejemplo en la página web de prácticas que contiene todos estos cuadros de diálogo (alerta, confirmar, preguntar y modal). La Figura 4-11 muestra una captura de pantalla de esta página cuando el cuadro de diálogo modal está activo.
Alertas, confirmaciones y avisos
La API de Selenium WebDriver proporciona la interfaz Alert
para manipular diálogos JavaScript (es decir, alertas, confirmaciones y avisos). En la Tabla 4-9 se describen los métodos que proporciona esta interfaz. A continuación, el Ejemplo 4-25 muestra una prueba básica que interactúa con una alerta.
Ejemplo 4-25. Probar el manejo de un diálogo de alerta
@Test
void
testAlert
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/dialog-boxes.html"
)
;
WebDriverWait
wait
=
new
WebDriverWait
(
driver
,
Duration
.
ofSeconds
(
5
)
)
;
driver
.
findElement
(
By
.
id
(
"my-alert"
)
)
.
click
(
)
;
wait
.
until
(
ExpectedConditions
.
alertIsPresent
(
)
)
;
Alert
alert
=
driver
.
switchTo
(
)
.
alert
(
)
;
assertThat
(
alert
.
getText
(
)
)
.
isEqualTo
(
"Hello world!"
)
;
alert
.
accept
(
)
;
}
Abrimos la página web de prácticas que lanza cuadros de diálogo.
Hacemos clic en el botón izquierdo para lanzar una alerta JavaScript.
Esperamos hasta que aparezca el diálogo de alerta en la pantalla.
Cambiamos el foco a la ventana emergente de alerta.
Verificamos que el texto de la alerta es el esperado.
Hacemos clic en el botón Aceptar del cuadro de diálogo de alerta.
Podemos sustituir los pasos 3 y 4 por una única sentencia wait explícita, como se indica a continuación (puedes encontrarla en una segunda prueba de la misma clase en el repositorio de ejemplos):
Alert
alert
=
wait
.
until
(
ExpectedConditions
.
alertIsPresent
());
La siguiente prueba (Ejemplo 4-26) ilustra cómo tratar un diálogo de confirmación. Observa que este ejemplo es bastante similar al anterior, pero en este caso, podemos invocar al método dismiss()
para pulsar el botón Cancelar disponible en el diálogo de confirmación. Por último, el Ejemplo 4-27 muestra cómo gestionar un diálogo de aviso. En este caso, podemos escribir una cadena en el texto de entrada.
Ejemplo 4-26. Probar el manejo de un diálogo de confirmación
@Test
void
testConfirm
()
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/dialog-boxes.html"
);
WebDriverWait
wait
=
new
WebDriverWait
(
driver
,
Duration
.
ofSeconds
(
5
));
driver
.
findElement
(
By
.
id
(
"my-confirm"
)).
click
();
wait
.
until
(
ExpectedConditions
.
alertIsPresent
());
Alert
confirm
=
driver
.
switchTo
().
alert
();
assertThat
(
confirm
.
getText
()).
isEqualTo
(
"Is this correct?"
);
confirm
.
dismiss
();
}
Ejemplo 4-27. Probar el manejo de un diálogo de aviso
@Test
void
testPrompt
()
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/dialog-boxes.html"
);
WebDriverWait
wait
=
new
WebDriverWait
(
driver
,
Duration
.
ofSeconds
(
5
));
driver
.
findElement
(
By
.
id
(
"my-prompt"
)).
click
();
wait
.
until
(
ExpectedConditions
.
alertIsPresent
());
Alert
prompt
=
driver
.
switchTo
().
alert
();
prompt
.
sendKeys
(
"John Doe"
);
assertThat
(
prompt
.
getText
()).
isEqualTo
(
"Please enter your name"
);
prompt
.
accept
();
}
Ventanas modales
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 la práctica que contiene cuadros de diálogo.
Ejemplo 4-28. Prueba de manejo de un diálogo modal
@Test
void
testModal
()
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/dialog-boxes.html"
);
WebDriverWait
wait
=
new
WebDriverWait
(
driver
,
Duration
.
ofSeconds
(
5
));
driver
.
findElement
(
By
.
id
(
"my-modal"
)).
click
();
WebElement
close
=
driver
.
findElement
(
By
.
xpath
(
"//button[text() = 'Close']"
));
assertThat
(
close
.
getTagName
()).
isEqualTo
(
"button"
);
wait
.
until
(
ExpectedConditions
.
elementToBeClickable
(
close
));
close
.
click
();
}
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 JavaScript:
window.localStorage
window.sessionStorage
-
Para almacenar datos durante el tiempo de la sesión (los datos se borran 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 WebDriver
soportados por Selenium WebDriver heredan esta interfaz: ChromeDriver
, EdgeDriver
, FirefoxDriver
, OperaDriver
, y SafariDriver
. De este modo, podemos utilizar esta característica de 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
@Test
void
testWebStorage
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/web-storage.html"
)
;
WebStorage
webStorage
=
(
WebStorage
)
driver
;
LocalStorage
localStorage
=
webStorage
.
getLocalStorage
(
)
;
log
.
debug
(
"Local storage elements: {}"
,
localStorage
.
size
(
)
)
;
SessionStorage
sessionStorage
=
webStorage
.
getSessionStorage
(
)
;
sessionStorage
.
keySet
(
)
.
forEach
(
key
-
>
log
.
debug
(
"Session storage: {}={}"
,
key
,
sessionStorage
.
getItem
(
key
)
)
)
;
assertThat
(
sessionStorage
.
size
(
)
)
.
isEqualTo
(
2
)
;
sessionStorage
.
setItem
(
"new element"
,
"new value"
)
;
assertThat
(
sessionStorage
.
size
(
)
)
.
isEqualTo
(
3
)
;
driver
.
findElement
(
By
.
id
(
"display-session"
)
)
.
click
(
)
;
}
Receptores de eventos
La API de Selenium WebDriver permite crear escuchas que notifican eventos que ocurren en WebDriver
y objetos derivados. En versiones anteriores de Selenium WebDriver, esta función era accesible a través de la clase EventFiringWebDriver
. Esta clase está obsoleta a partir de Selenium WebDriver 4, y en su lugar, debemos utilizar lo siguiente:
EventFiringDecorator
-
Clase envolvente para
WebDriver
y objetos derivados (por ejemplo,WebElement
,TargetLocator
, etc.). Permite registrar uno o varios oyentes (es decir, instancias deWebDriverListener
). WebDriverListener
-
Interfaz que debe implementar los oyentes registrados en el decorador. Admite tres tipos de eventos:
- Antes de los acontecimientos
-
Lógica insertada justo antes de que comience algún evento
- Después de los acontecimientos
-
Lógica insertada justo después de que finalice algún evento
- Eventos deerror
-
Lógica insertada antes de que se lance una excepción
Para implementar un oyente de eventos, en primer lugar, debemos crear una clase oyente. En otras palabras, necesitamos crear una clase que implemente la interfaz WebDriverListener
. Esta interfaz define todos sus métodos mediante la palabra clave default
y, por tanto, es opcional anular sus métodos. Gracias a esa característica (disponible a partir de Java 8), nuestra clase sólo debería implementar el método que necesitamos. Hay un montón de métodos de escucha disponibles, por ejemplo, afterGet()
(se ejecuta después de llamar al método get()
en una instancia de WebDriver
), o beforeQuit()
(se ejecuta antes de llamar al método quit()
en una instancia de WebDriver
), por nombrar algunos. Mi recomendación para comprobar todas estas escuchas es que utilices tu IDE favorito para descubrir los posibles métodos a anular/implementar. La Figura 4-12 muestra el asistente para hacerlo en Eclipse.
Una vez que hayamos implementado nuestro oyente, tenemos que crear la clase decorador. Hay dos formas de hacerlo. Si queremos decorar un objeto WebDriver
, podemos crear una instancia de EventFiringDecorator
(pasando el oyente como argumento al constructor) y luego invocar el método decorate()
para pasar el objeto WebDriver
. Por ejemplo
WebDriver
decoratedDriver
=
new
EventFiringDecorator
(
myListener
)
.
decorate
(
originalDriver
);
La segunda forma consiste en decorar con otros objetos de la API Selenium WebDriver, concretamente 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 utilizando un WebElement
como parámetro:
Decorated
<
WebElement
>
decoratedWebElement
=
new
EventFiringDecorator
(
listener
).
createDecorated
(
myWebElement
);
Veamos un ejemplo completo. En primer lugar, 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 hacer una captura de pantalla del navegador. En total, estamos recogiendo capturas de pantalla del navegador justo después de cargar una página web (normalmente al principio de la prueba) y antes de salir (normalmente al final de la prueba). A continuación, el Ejemplo 4-31 muestra la prueba que utiliza esta escucha.
Ejemplo 4-30. Escuchador de eventos que implementa los métodos afterGet() y beforeQuit()
public
class
MyEventListener
implements
WebDriverListener
{
static
final
Logger
log
=
getLogger
(
lookup
(
)
.
lookupClass
(
)
)
;
@Override
public
void
afterGet
(
WebDriver
driver
,
String
url
)
{
WebDriverListener
.
super
.
afterGet
(
driver
,
url
)
;
takeScreenshot
(
driver
)
;
}
@Override
public
void
beforeQuit
(
WebDriver
driver
)
{
takeScreenshot
(
driver
)
;
}
private
void
takeScreenshot
(
WebDriver
driver
)
{
TakesScreenshot
ts
=
(
TakesScreenshot
)
driver
;
File
screenshot
=
ts
.
getScreenshotAs
(
OutputType
.
FILE
)
;
SessionId
sessionId
=
(
(
RemoteWebDriver
)
driver
)
.
getSessionId
(
)
;
Date
today
=
new
Date
(
)
;
SimpleDateFormat
dateFormat
=
new
SimpleDateFormat
(
"yyyy.MM.dd_HH.mm.ss.SSS"
)
;
String
screenshotFileName
=
String
.
format
(
"%s-%s.png"
,
dateFormat
.
format
(
today
)
,
sessionId
.
toString
(
)
)
;
Path
destination
=
Paths
.
get
(
screenshotFileName
)
;
try
{
Files
.
move
(
screenshot
.
toPath
(
)
,
destination
)
;
}
catch
(
IOException
e
)
{
log
.
error
(
"Exception moving screenshot from {} to {}"
,
screenshot
,
destination
,
e
)
;
}
}
}
Reemplazamos este método para ejecutar lógica personalizada después de cargar páginas web con el objeto
WebDriver
.Reemplazamos este método para ejecutar una lógica personalizada antes de salir del objeto
WebDriver
.Utilizamos un nombre único para las capturas de pantalla PNG. Para ello, obtenemos la fecha del sistema (fecha y hora) más el identificador de sesión .
Ejemplo 4-31. Prueba utilizando EventFiringDecorator y el oyente anterior
class
EventListenerJupiterTest
{
WebDriver
driver
;
@BeforeEach
void
setup
(
)
{
MyEventListener
listener
=
new
MyEventListener
(
)
;
WebDriver
originalDriver
=
WebDriverManager
.
chromedriver
(
)
.
create
(
)
;
driver
=
new
EventFiringDecorator
(
listener
)
.
decorate
(
originalDriver
)
;
}
@AfterEach
void
teardown
(
)
{
driver
.
quit
(
)
;
}
@Test
void
testEventListener
(
)
{
driver
.
get
(
"https://bonigarcia.dev/selenium-webdriver-java/"
)
;
assertThat
(
driver
.
getTitle
(
)
)
.
isEqualTo
(
"Hands-On Selenium WebDriver with Java"
)
;
driver
.
findElement
(
By
.
linkText
(
"Web form"
)
)
.
click
(
)
;
}
}
Creamos un objeto decorado
WebDriver
utilizando una instancia enMyEventListener
. Utilizamos eldriver
resultante para controlar el navegador en la lógica@Test
.Hacemos clic en un enlace web para cambiar de página. Las dos capturas de pantalla resultantes tomadas en el oyente deberían ser diferentes.
Excepciones de WebDriver
Todas las excepciones proporcionadas por la API de WebDriver heredan de la clase WebDriverException
y están desmarcadas (consulta la siguiente barra lateral si no estás familiarizado con esta terminología). La Figura 4-13 muestra estas excepciones en Selenium WebDriver 4. Como muestra esta imagen, hay muchos tipos de excepción diferentes. La Tabla 4-10 resume algunas de las causas más comunes.
Resumen y perspectivas
Este capítulo proporcionó una revisión exhaustiva en de las funciones de la API de WebDriver interoperables en distintos navegadores web. Entre ellas, descubriste cómo ejecutar JavaScript con Selenium WebDriver, con scripts síncronos, anclados (es decir, adjuntos a una sesión de WebDriver) y asíncronos. A continuación, aprendiste sobre los tiempos de espera, utilizados para especificar un intervalo de tiempo límite para la carga de la página y la ejecución del script. Además, viste cómo gestionar varios aspectos del navegador, como el tamaño y la posición, el historial de navegación, la sombra DOM y las cookies. A continuación, descubriste cómo interactuar con elementos web específicos, como listas desplegables (listas de selección y de datos), objetivos de navegación (ventanas, pestañas, marcos e iframes) y cuadros de diálogo (alertas, avisos, confirmaciones y modales). Por último, revisamos el mecanismo de implementación del almacenamiento web y los escuchadores de eventos en Selenium WebDriver 4, así como las excepciones WebDriver más relevantes (y sus causas habituales).
El siguiente capítulo continúa exponiendo las características de la API de Selenium WebDriver. El capítulo explica los aspectos específicos de un navegador determinado (por ejemplo, Chrome, Firefox, etc.), incluidas las capacidades del navegador (por ejemplo, ChromeOptions
, FirefoxOptions
, etc.), el Protocolo DevTools de Chrome (CDP), la interceptación de red, la imitación de coordenadas de geolocalización, el protocolo WebDriver BiDireccional (BiDi), los mecanismos de autenticación o la impresión de páginas web en PDF, entre otras características.
Get Selenium WebDriver práctico con Java now with the O’Reilly learning platform.
O’Reilly members experience books, live events, courses curated by job role, and more from O’Reilly and nearly 200 top publishers.