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.

Tabla 4-1. Métodos de JavascriptExecutor
Categoría Método Devuelve Descripción

Guiones sincrónicos

executeScript(
    String script,
    Object... args)
Object

Ejecuta el código JavaScript en la página actual.

Guiones anclados

pin(String
    script)
ScriptKey

Adjunta un fragmento de JavaScript a una sesión de WebDriver. Los scripts fijados pueden utilizarse varias veces mientras la sesión WebDriver esté activa.

unpin(ScriptKey
    key)
void

Separar de la sesión WebDriver un script previamente anclado a .

getPinnedScripts()
Set<ScriptKey>

Recoge todos los guiones de anclados (cada uno identificado por un único ScriptKey).

executeScript(
    ScriptKey key,
    Object... args)
Object

Llama al script previamente anclado (identificado con su ScriptKey).

Guiones asíncronos

executeAsyncScript(
    String script,
    Object... args)
Object

Ejecuta código JavaScript (normalmente una operación asíncrona) en la página actual. La diferencia con executeScript() es que los scripts ejecutados con executeAsyncScript() deben señalar explícitamente su finalización invocando una función de devolución de llamada. Por convención, esta llamada de retorno se inyecta en el script como último argumento.

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 un List de estos tipos (de lo contrario, WebDriver lanza una excepción). Estos argumentos están disponibles en el script inyectado mediante la variable JavaScript incorporada arguments.

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"); 1
    JavascriptExecutor js = (JavascriptExecutor) driver; 2

    String script = "window.scrollBy(0, 1000);";
    js.executeScript(script); 3
}
1

Abre una página web de la práctica que contenga un texto muy largo (ver Figura 4-1).

2

Transfiere el objeto driver a JavascriptExecutor. Utilizaremos la variable js para ejecutar JavaScript en el navegador.

3

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 utiliza return, y por tanto, no recibimos ningún objeto devuelto en la lógica Java. Además, no pasamos ningún argumento al script.

hosw 0401
Figura 4-1. Página web de prácticas con contenido largo

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)); 1

    WebElement lastElememt = driver
            .findElement(By.cssSelector("p:last-child")); 2
    String script = "arguments[0].scrollIntoView();"; 3
    js.executeScript(script, lastElememt); 4
}
1

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.

2

Localizamos el último párrafo en la página web utilizando un selector CSS.

3

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().

4

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)); 1

    By pLocator = By.tagName("p");
    List<WebElement> paragraphs = wait.until(
            ExpectedConditions.numberOfElementsToBeMoreThan(pLocator, 0));
    int initParagraphsNumber = paragraphs.size(); 2

    WebElement lastParagraph = driver.findElement(
            By.xpath(String.format("//p[%d]", initParagraphsNumber))); 3
    String script = "arguments[0].scrollIntoView();";
    js.executeScript(script, lastParagraph); 4

    wait.until(ExpectedConditions.numberOfElementsToBeMoreThan(pLocator,
            initParagraphsNumber)); 5
}
1

Definimos una espera explícita ya que necesitamos pausar la prueba hasta que se cargue el nuevo contenido.

2

Encontramos el número inicial de párrafos de la página.

3

Localizamos el último párrafo de la página.

4

Nos desplazamos hacia abajo en 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 por un área gráfica. El formulario web de la práctica contiene uno de estos elementos (ver Figura 4-2).

hosw 0402
Figura 4-2. Selector de color en el formulario web de prácticas

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")); 1
    String initColor = colorPicker.getAttribute("value"); 2
    log.debug("The initial color is {}", initColor);

    Color red = new Color(255, 0, 0, 1); 3
    String script = String.format(
            "arguments[0].setAttribute('value', '%s');", red.asHex());
    js.executeScript(script, colorPicker); 4

    String finalColor = colorPicker.getAttribute("value"); 5
    log.debug("The final color is {}", finalColor);
    assertThat(finalColor).isNotEqualTo(initColor); 6
    assertThat(Color.fromString(finalColor)).isEqualTo(red);
}
1

Localizamos el selector de color por su nombre.

2

Leemos el valor inicial del selector de color (debe ser #563d7c).

3

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).

4

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());.

5

Leemos el valor resultante del selector de color (debe ser #ff0000).

6

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];"); 1
    ScriptKey firstArgKey = js.pin("return arguments[0];"); 2

    Set<ScriptKey> pinnedScripts = js.getPinnedScripts(); 3
    assertThat(pinnedScripts).hasSize(2); 4

    WebElement formLink = (WebElement) js.executeScript(linkKey); 5
    formLink.click(); 6
    assertThat(driver.getCurrentUrl()).isNotEqualTo(initPage); 7

    String message = "Hello world!";
    String executeScript = (String) js.executeScript(firstArgKey, message); 8
    assertThat(executeScript).isEqualTo(message); 9

    js.unpin(linkKey); 10
    assertThat(js.getPinnedScripts()).hasSize(1); 11
}
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.

2

Adjuntamos otro fragmento de JavaScript que devuelve lo que le pasemos como primer parámetro.

3

Leemos el conjunto de guiones fijados.

4

Afirmamos que el número de guiones anclados es el esperado (es decir, 2).

5

Ejecutamos el primer script fijado. Como resultado, obtenemos el tercer enlace de la página web como WebElement en Java.

6

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.

7

Afirmamos que la URL actual es diferente de la inicial.

8

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).

9

Afirmamos que el mensaje devuelto es el esperado.

10

Desenganchamos uno de los guiones.

11

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); 1
    String script = "const callback = arguments[arguments.length - 1];"
            + "window.setTimeout(callback, " + pause.toMillis() + ");"; 2

    long initMillis = System.currentTimeMillis(); 3
    js.executeAsyncScript(script); 4
    Duration elapsed = Duration
            .ofMillis(System.currentTimeMillis() - initMillis); 5
    log.debug("The script took {} ms to be executed", elapsed.toMillis());
    assertThat(elapsed).isGreaterThanOrEqualTo(pause); 6
}
1

Definimos un tiempo de pausa de 2 segundos.

2

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.

3

Obtenemos la hora actual del sistema (en milisegundos).

4

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).

5

Calculamos el tiempo necesario para ejecutar la línea anterior.

6

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)); 1

    assertThatThrownBy(() -> driver
            .get("https://bonigarcia.dev/selenium-webdriver-java/"))
                    .isInstanceOf(TimeoutException.class); 2
}
1

Especificamos el tiempo mínimo posible de espera de carga de la página, que es de un milisegundo.

2

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 AssertJ assertThatThrownBy.

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)); 1

    assertThatThrownBy(() -> {
        long waitMillis = Duration.ofSeconds(5).toMillis();
        String script = "const callback = arguments[arguments.length - 1];"
                + "window.setTimeout(callback, " + waitMillis + ");"; 2
        js.executeAsyncScript(script);
    }).isInstanceOf(ScriptTimeoutException.class); 3
}
1

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.

2

Ejecutamos un script asíncrono que pausa la ejecución de cinco segundos.

3

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.

Tabla 4-2. Parámetros de OutputType
Parámetro Descripción Devuelve Ejemplo
OutputType.FILE

Haz la captura de pantalla como un archivo PNG (ubicado en un directorio temporal del sistema)

File
File screenshot =
    ts.getScreenshotAs(
    OutputType.FILE);
OutputType.BASE64

Haz una captura de pantalla en formato Base64 (es decir, codificada como una cadena ASCII).

String
String screenshot =
    ts.getScreenshotAs(
    OutputType.BASE64);
OutputType.BYTES

Hacer una captura de pantalla como una matriz de bytes sin procesar

byte[]
byte[] screenshot =
    ts.getScreenshotAs(
    OutputType.BYTES);
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); 1
    log.debug("Screenshot created on {}", screenshot);

    Path destination = Paths.get("screenshot.png"); 2
    Files.move(screenshot.toPath(), destination, REPLACE_EXISTING); 3
    log.debug("Screenshot moved to {}", destination);

    assertThat(destination).exists(); 4
}
1

Hacemos que la pantalla del navegador sea un archivo PNG.

2

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).

3

Utilizamos Java estándar para mover el archivo de captura de pantalla a la nueva ubicación.

4

Utilizamos aserciones para verificar que el archivo de destino existe.

hosw 0403
Figura 4-3. Captura de pantalla del navegador de la página de índice del sitio de prácticas
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); 1
    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); 2
    assertThat(screenshot).isNotEmpty(); 3
}
1

Hacemos la pantalla del navegador en formato Base64.

2

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.

3

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();
}
hosw 0404
Figura 4-4. Captura de pantalla parcial del formulario web de prácticas

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();
Tabla 4-3. Métodos de ventana
Método Devuelve Descripción
getSize()
Dimension

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).

setSize(Dimension
    targetSize)
void

Cambia el tamaño actual de la ventana (de nuevo, su dimensión exterior, y no la ventana gráfica).

getPosition()
Point

Obtiene la posición actual de la ventana (relativa a la esquina superior izquierda de la pantalla).

setPosition(Point
    targetPosition)
void

Cambia la posición actual de la ventana (de nuevo, relativa a la esquina superior izquierda de la pantalla).

maximize()
void

Maximiza la ventana actual.

minimize()
void

Minimiza la ventana actual.

fullscreen()
void

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(); 1
    Dimension initialSize = window.getSize(); 2
    log.debug("Initial window: position {} -- size {}", initialPosition,
            initialSize);

    window.maximize(); 3

    Point maximizedPosition = window.getPosition();
    Dimension maximizedSize = window.getSize();
    log.debug("Maximized window: position {} -- size {}", maximizedPosition,
            maximizedSize);

    assertThat(initialPosition).isNotEqualTo(maximizedPosition); 4
    assertThat(initialSize).isNotEqualTo(maximizedSize);
}
1

Leemos la posición de la ventana.

2

Leemos el tamaño de la ventana.

3

Maximizamos la ventana del navegador.

4

Comprobamos 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. 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();

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.

hosw 0405
Figura 4-5. Representación esquemática de la sombra DOM
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"); 1

    WebElement content = driver.findElement(By.id("content")); 2
    SearchContext shadowRoot = content.getShadowRoot(); 3
    WebElement textElement = shadowRoot.findElement(By.cssSelector("p")); 4
    assertThat(textElement.getText()).contains("Hello Shadow DOM"); 5
}
1

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.

2

Localizamos el elemento anfitrión de la sombra .

3

Obtenemos la raíz sombra del elemento anfitrión. Como resultado, obtenemos una instancia de SearchContext, una interfaz implementada por WebDriver y WebElement, que nos permite encontrar elementos utilizando los métodos findElement() y find​Ele⁠ments().

4

Encontramos el primer elemento del párrafo en el árbol de sombras.

5

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.

Tabla 4-5. Métodos de gestión de cookies
Método Devuelve Descripción
addCookie(Cookie cookie)
void

Añadir una nueva cookie

deleteCookieNamed(String name)
void

Eliminar una cookie existente por su nombre

deleteCookie(Cookie cookie)
void

Eliminar una cookie existente por ejemplo

deleteAllCookies()
void

Eliminar todas las cookies

getCookies()
Set<Cookie>

Obtener todas las cookies

getCookieNamed(String name)
Cookie

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)

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):

hosw 0406
Figura 4-6. Página web de prácticas para cookies web
Ejemplo 4-15. Prueba de lectura de cookies existentes
@Test
void testReadCookies() {
    driver.get(
            "https://bonigarcia.dev/selenium-webdriver-java/cookies.html");

    Options options = driver.manage(); 1
    Set<Cookie> cookies = options.getCookies(); 2
    assertThat(cookies).hasSize(2);

    Cookie username = options.getCookieNamed("username"); 3
    assertThat(username.getValue()).isEqualTo("John Doe"); 4
    assertThat(username.getPath()).isEqualTo("/");

    driver.findElement(By.id("refresh-cookies")).click(); 5
}
1

Obtenemos el objeto Options utilizado para gestionar las cookies.

2

Leemos todas las cookies disponibles en esta página. Debe contener dos cookies.

3

Leemos la galleta con el nombre username.

4

El valor de la cookie anterior debe ser John Doe.

5

La última declaración no afecta a la prueba. Invocamos este comando para comprobar las cookies en la GUI del navegador.

Ejemplo 4-16. Prueba de añadir nuevas cookies
@Test
void testAddCookies() {
    driver.get(
            "https://bonigarcia.dev/selenium-webdriver-java/cookies.html");

    Options options = driver.manage();
    Cookie newCookie = new Cookie("new-cookie-key", "new-cookie-value"); 1
    options.addCookie(newCookie); 2
    String readValue = options.getCookieNamed(newCookie.getName())
            .getValue(); 3
    assertThat(newCookie.getValue()).isEqualTo(readValue); 4

    driver.findElement(By.id("refresh-cookies")).click();
}
1

Creamos una nueva cookie.

2

Añadimos la cookie a la página actual.

3

Leemos el valor de la cookie que acabamos de añadir.

4

Comprobamos que este valor es el esperado.

Ejemplo 4-17. Prueba de edición de cookies existentes
@Test
void testEditCookie() {
    driver.get(
            "https://bonigarcia.dev/selenium-webdriver-java/cookies.html");

    Options options = driver.manage();
    Cookie username = options.getCookieNamed("username"); 1
    Cookie editedCookie = new Cookie(username.getName(), "new-value"); 2
    options.addCookie(editedCookie); 3

    Cookie readCookie = options.getCookieNamed(username.getName()); 4
    assertThat(editedCookie).isEqualTo(readCookie); 5

    driver.findElement(By.id("refresh-cookies")).click();
}
1

Leemos una cookie existente.

2

Creamos una nueva cookie reutilizando el nombre de la cookie anterior.

3

Añadimos la nueva cookie a la página web.

4

Leemos la galleta que acabamos de añadir.

5

Verificamos que la cookie se ha editado correctamente.

Ejemplo 4-18. Prueba de eliminación de cookies existentes
@Test
void testDeleteCookies() {
    driver.get(
            "https://bonigarcia.dev/selenium-webdriver-java/cookies.html");

    Options options = driver.manage();
    Set<Cookie> cookies = options.getCookies(); 1
    Cookie username = options.getCookieNamed("username"); 2
    options.deleteCookie(username); 3

    assertThat(options.getCookies()).hasSize(cookies.size() - 1); 4

    driver.findElement(By.id("refresh-cookies")).click();
}
1

Leemos todas las cookies.

2

Leemos la galleta con el nombre username.

3

Eliminamos la cookie anterior.

4

Verificamos que el tamaño de las cookies es el que esperaba.

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>
hosw 0407
Figura 4-7. Seleccionar campo en el formulario web de prácticas

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.

Tabla 4-7. Seleccionar métodos
Método Devuelve Descripción
Select(WebElement element)
Select

Constructor que utiliza un WebElement como parámetro (debe ser un elemento <select> ); de lo contrario, lanza un error UnexpectedTagNameException

getWrappedElement()
WebElement

Obtén el WebElement envuelto (es decir, el utilizado en el constructor)

isMultiple()
boolean

Si el elemento de selección admite la selección de varias opciones

getOptions()
List<WebElement>

Leer todas las opciones que pertenecen al elemento de selección

getAllSelectedOptions()
List<WebElement>

Leer todas las opciones seleccionadas

getFirstSelectedOption()
WebElement

Lee primero opción seleccionada

selectByVisibleText(String text)
void

Selecciona todas las opciones que coincidan con un determinado texto mostrado

selectByIndex(int index)
void

Selecciona una opción por número de índice

selectByValue(String value)
void

Seleccionar opción(es) por atributo de valor

deselectAll()
void

Deselecciona todas las opciones

deselectByValue(String value)
void

Deseleccionar opción(es) por atributo de valor

deselectByIndex(int index)
void

Deseleccionar por número de índice

deselectByVisibleText(String text)
void

Deselecciona las opciones que coincidan con un texto mostrado dado

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"))); 1
    String optionLabel = "Three";
    select.selectByVisibleText(optionLabel); 2

    assertThat(select.getFirstSelectedOption().getText())
            .isEqualTo(optionLabel); 3
}
1

Buscamos el elemento selecto por su nombre y utilizamos el WebElement resultante para instanciar un objeto Select.

2

Seleccionamos una de las opciones disponibles en esta selección, utilizando una estrategia por texto.

3

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>
hosw 0408
Figura 4-8. Campo de lista de datos en el formulario web de la consulta

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")); 1
    datalist.click(); 2

    WebElement option = driver
            .findElement(By.xpath("//datalist/option[2]")); 3
    String optionValue = option.getAttribute("value"); 4
    datalist.sendKeys(optionValue); 5

    assertThat(optionValue).isEqualTo("New York"); 6
}
1

Localizamos el campo de entrada utilizado para la lista de datos.

2

Hacemos clic sobre é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

Afirmamos que el valor de la opción es el que esperaba.

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.

Tabla 4-8. Métodos de TargetLocator
Método Devuelve Descripción
frame(int index)
WebDriver

Cambia el enfoque a un marco (o iframe) de por número de índice.

frame(String
    nameOrId)
WebDriver

Cambia el enfoque a un marco (o iframe) por nombre o id.

frame(WebElement
    frameElement)
WebDriver

Cambia el foco a un marco (o iframe) situado previamente como un WebElement.

parentFrame()
WebDriver

Cambia el enfoque al contexto padre.

window(String
    nameOrHandle)
WebDriver

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.

newWindow(WindowType
    typeHint)
WebDriver

Crea una nueva ventana del navegador (utilizando WindowType.WINDOW) o pestaña (WindowType.TAB) y cambia el foco a ella.

defaultContent()
WebDriver

Selecciona el documento principal (cuando utilices iframes) o el primer marco de la página (cuando utilices un conjunto de marcos).

activeElement()
WebElement

Obtiene el elemento actualmente seleccionado.

alert()
Alert

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/"); 1
    String initHandle = driver.getWindowHandle(); 2

    driver.switchTo().newWindow(WindowType.TAB); 3
    driver.get(
            "https://bonigarcia.dev/selenium-webdriver-java/web-form.html"); 4
    assertThat(driver.getWindowHandles().size()).isEqualTo(2); 5

    driver.switchTo().window(initHandle); 6
    driver.close(); 7
    assertThat(driver.getWindowHandles().size()).isEqualTo(1); 8
}
1

Navegamos a una página web.

2

Obtenemos el asa de la ventana actual.

3

Abrimos una nueva pestaña y le cambia el foco.

4

Abrimos otra página web (como el foco está en la segunda pestaña, la página se abre en la segunda pestaña).

5

Comprobamos que el número de asas de ventana en este punto es 2.

6

Cambiamos el foco de a la ventana inicial (utilizando su manejador).

7

Cerramos sólo la ventana actual. La segunda pestaña permanece abierta.

8

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); 1
    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);
}
1

Esta línea es diferente en los ejemplos. En este caso, abrimos una nueva ventana (en lugar de una pestaña) y enfocamos en ella.

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"); 1

    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    wait.until(ExpectedConditions
            .frameToBeAvailableAndSwitchToIt("my-iframe")); 2

    By pName = By.tagName("p");
    wait.until(ExpectedConditions.numberOfElementsToBeMoreThan(pName, 0)); 3
    List<WebElement> paragraphs = driver.findElements(pName);
    assertThat(paragraphs).hasSize(20); 4
}
1

Abrimos una página web que contiene un iframe (ver Figura 4-9).

2

Utilizamos una espera explícita para esperar la trama y pasar a ella.

3

Utilizamos otra espera explícita para hacer una pausa hasta que los párrafos contenidos en el iframe estén disponibles.

4

Afirmamos que el número de párrafos es el esperado.

hosw 0409
Figura 4-9. Página web de prácticas utilizando un iframe
Ejemplo 4-24. Prueba de manipulación de tramas
@Test
void testFrames() {
    driver.get(
            "https://bonigarcia.dev/selenium-webdriver-java/frames.html"); 1

    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    String frameName = "frame-body";
    wait.until(ExpectedConditions
            .presenceOfElementLocated(By.name(frameName))); 2
    driver.switchTo().frame(frameName); 3

    By pName = By.tagName("p");
    wait.until(ExpectedConditions.numberOfElementsToBeMoreThan(pName, 0));
    List<WebElement> paragraphs = driver.findElements(pName);
    assertThat(paragraphs).hasSize(20);
}
1

Abrimos una página web que contiene un conjunto de marcos (ver Figura 4-10).

2

Esperamos a que la trama esté disponible. Observa que los pasos 2 y 3 del Ejemplo 4-23 son equivalentes a este paso.

3

Cambiamos el enfoque a este marco.

hosw 0410
Figura 4-10. Práctica de página web con marcos

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.

hosw 0411
Figura 4-11. Página web de práctica con cuadros de diálogo (alerta, confirmar, preguntar y modal)

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.

Tabla 4-9. Métodos de alerta
Método Devuelve Descripción
accept()
void

Para pulsa OK

getText()
String

Para leer el mensaje de diálogo

dismiss()
void

Para pulsar Cancelar (no disponible en alertas)

sendKeys(String text)
void

Para escribir una cadena en el texto de entrada (sólo disponible en los avisos)

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"); 1
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));

    driver.findElement(By.id("my-alert")).click(); 2
    wait.until(ExpectedConditions.alertIsPresent()); 3
    Alert alert = driver.switchTo().alert(); 4
    assertThat(alert.getText()).isEqualTo("Hello world!"); 5
    alert.accept(); 6
}
1

Abrimos la página web de prácticas que lanza cuadros de diálogo.

2

Hacemos clic en el botón izquierdo para lanzar una alerta JavaScript.

3

Esperamos hasta que aparezca el diálogo de alerta en la pantalla.

4

Cambiamos el foco a la ventana emergente de alerta.

5

Verificamos que el texto de la alerta es el esperado.

6

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.

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

Para almacenar permanentemente los datos de

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; 1

    LocalStorage localStorage = webStorage.getLocalStorage();
    log.debug("Local storage elements: {}", localStorage.size()); 2

    SessionStorage sessionStorage = webStorage.getSessionStorage();
    sessionStorage.keySet()
            .forEach(key -> log.debug("Session storage: {}={}", key,
                    sessionStorage.getItem(key))); 3
    assertThat(sessionStorage.size()).isEqualTo(2);

    sessionStorage.setItem("new element", "new value");
    assertThat(sessionStorage.size()).isEqualTo(3); 4

    driver.findElement(By.id("display-session")).click();
}
1

Lanzamos el objeto conductor a WebStorage.

2

Registramos el número de elementos del almacenamiento local.

3

Registramos el almacenamiento de la sesión (debe contener dos elementos).

4

Después de añadir un nuevo elemento, debería haber tres elementos en el almacén de sesiones.

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 de WebDriverListener ).

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.

hosw 0412
Figura 4-12. Métodos WebDriverListener 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) { 1
        WebDriverListener.super.afterGet(driver, url);
        takeScreenshot(driver);
    }

    @Override
    public void beforeQuit(WebDriver driver) { 2
        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); 3

        try {
            Files.move(screenshot.toPath(), destination);
        } catch (IOException e) {
            log.error("Exception moving screenshot from {} to {}", screenshot,
                    destination, e);
        }
    }

}
1

Reemplazamos este método para ejecutar lógica personalizada después de cargar páginas web con el objeto WebDriver.

2

Reemplazamos este método para ejecutar una lógica personalizada antes de salir del objeto WebDriver.

3

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); 1
    }

    @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(); 2
    }

}
1

Creamos un objeto decorado WebDriver utilizando una instancia en MyEventListener. Utilizamos el driver resultante para controlar el navegador en la lógica @Test.

2

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.

hosw 0413
Figura 4-13. Excepciones de Selenium WebDriver
Tabla 4-10. Excepciones habituales de WebDriver y causas comunes
Excepción Descripción Causas comunes
NoSuchElementException

Elemento web no disponible

  • Estrategia de localización no válida

  • El elemento no se ha renderizado (tal vez tengas que esperar a que lo haga)

NoAlertPresentException

Diálogo (alerta, aviso o confirmación) no disponible

Intentar realizar una acción (por ejemplo, accept() o dismiss()) en un diálogo no disponible.

NoSuchWindowException

Ventana o pestaña no disponible

Intentar cambiar a una ventana o pestaña no disponible

NoSuchFrameException

Marco o iframe no disponible

Intentar cambiar a un marco o iframe no disponible

InvalidArgumentException

Argumento incorrecto al llamar a algún método de la API Selenium WebDriver

  • URL incorrecta en los métodos de navegación

  • Ruta inexistente al subir archivos

  • Tipo de argumento incorrecto en un script JavaScript

StaleElementReferenceException

El elemento es obsoleto, es decir, ya no aparece en la página

El DOM se actualiza al intentar interactuar con un elemento localizado previamente

UnreachableBrowserException

Problema de comunicación con el navegador

  • No se ha podido establecer la conexión con el navegador remoto

  • El navegador murió en medio de una sesión WebDriver

TimeoutException

Tiempo de espera de carga de la página

Algunas páginas web tardan más de lo esperado en cargarse

ScriptTimeoutException

Tiempo de espera de carga del script

Algún script tarda más de lo esperado en ejecutarse

ElementNotVisibleException
ElementNotSelectableException
ElementClickInterceptedException

El elemento está en el DOM pero no es visible/seleccionable/clicable

  • Espera insuficiente (o inexistente) hasta que el elemento se muestra/selecciona/se puede pulsar

  • El diseño de la página (quizás causado por el cambio de la ventana gráfica) hace que ese elemento se superponga al elemento con el que intentamos interactuar

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.