Selenium

Tutorial Selenium: Esperas implícitas y explícitas

Aprende a evitar problemas de sincronización y localización de elementos aplicando las mejores prácticas de esperas implícitas y explícitas en Selenium.

Aprende Selenium GRATIS y certifícate

Esperas implícitas y explícitas

En Selenium, manejar correctamente las esperas es esencial para asegurar que nuestros tests interactúen adecuadamente con la página web bajo prueba. Las aplicaciones web a menudo tardan en cargar elementos o en completar operaciones asíncronas, por lo que es fundamental sincronizar nuestras pruebas con el estado real de la aplicación. Aquí es donde entran en juego las esperas implícitas y explícitas.

Las esperas implícitas le indican al WebDriver que espere un tiempo determinado antes de lanzar una excepción por no encontrar un elemento en el DOM. Esta espera se aplica globalmente a todas las búsquedas de elementos que se realicen después de establecerla.

WebDriver driver = new ChromeDriver();
driver.manage().timeouts().implicitlyWait(Duration.ofSeconds(10));

En este ejemplo, hemos configurado una espera implícita de 10 segundos. Esto significa que, al buscar cualquier elemento, el WebDriver esperará hasta 10 segundos a que el elemento esté presente en el DOM antes de lanzar una excepción NoSuchElementException.

Es importante destacar que las esperas implícitas pueden afectar al rendimiento de las pruebas si se establecen tiempos excesivamente largos, ya que aplican a todas las operaciones de búsqueda de elementos.

Por otro lado, las esperas explícitas permiten definir condiciones específicas para un elemento en particular. Utilizamos la clase WebDriverWait junto con algunas condiciones para esperar hasta que se cumpla un determinado estado del elemento.

WebDriver driver = new ChromeDriver();
WebDriverWait espera = new WebDriverWait(driver, Duration.ofSeconds(10));

driver.get("https://ejemplo.com");

WebElement boton = espera.until(driver -> driver.findElement(By.id("boton-enviar")));
boton.click();

En este caso, la espera explícita espera hasta que el elemento con id="boton-enviar" esté presente en el DOM y sea localizable. La función until acepta una Lambda que representa la condición de espera.

Las esperas explícitas son más precisas y recomendables cuando necesitamos sincronizar acciones específicas con el estado de ciertos elementos o condiciones de la página. Podemos esperar diferentes condiciones, como que un elemento sea clicable, que esté visible o que contenga un texto determinado.

Por ejemplo, para esperar a que un elemento sea clicable:

WebDriverWait espera = new WebDriverWait(driver, Duration.ofSeconds(10));

WebElement elementoClicable = espera.until(driver ->
    {
        WebElement elemento = driver.findElement(By.id("mi-elemento"));
        return elemento.isEnabled() && elemento.isDisplayed() ? elemento : null;
    }
);

elementoClicable.click();

En este fragmento, estamos esperando hasta que el elemento esté visible y habilitado para poder interactuar con él.

Es crucial utilizar las esperas explícitas adecuadamente para evitar excepciones y asegurar que nuestras pruebas son confiables. Combinar esperas implícitas y explícitas puede llevar a comportamientos impredecibles y es generalmente desaconsejado. Por ello, es preferible utilizar únicamente esperas explícitas para gestionar la sincronización en nuestras pruebas.

Finalmente, recuerda cerrar siempre el WebDriver al finalizar las pruebas para liberar los recursos:

driver.quit();

Al dominar el uso de las esperas implícitas y explícitas, mejorarás la estabilidad y eficiencia de tus tests automatizados con Selenium.

Esperas Fluent

En Selenium, las esperas Fluent permiten definir una sincronización más personalizada durante la ejecución de las pruebas. A diferencia de las esperas explícitas convencionales, las esperas Fluent ofrecen mayor flexibilidad al permitir configurar no solo el tiempo máximo de espera, sino también la frecuencia con la que se verifica una condición y las excepciones que se deben ignorar durante la espera.

La clase FluentWait nos ayuda a manejar situaciones en las que un elemento puede tardar un tiempo variable en cumplir una condición dada. Podemos especificar el tiempo de espera total, el intervalo de sondeo y las excepciones que no queremos que interrumpan la espera, lo que es especialmente útil en aplicaciones web con comportamientos asíncronos o tiempos de carga impredecibles.

A continuación, se presenta un ejemplo de cómo utilizar una espera Fluent en una prueba con JUnit 5:

import org.junit.jupiter.api.Test;
import org.openqa.selenium.By;
import org.openqa.selenium.WebDriver;
import org.openqa.selenium.WebElement;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.FluentWait;

import java.time.Duration;
import java.util.NoSuchElementException;

public class PruebaFluentWait {

    @Test
    public void testFluentWait() {
        WebDriver driver = new ChromeDriver();

        driver.get("https://ejemplo.com");

        FluentWait<WebDriver> espera = new FluentWait<>(driver)
                .withTimeout(Duration.ofSeconds(30))
                .pollingEvery(Duration.ofSeconds(5))
                .ignoring(NoSuchElementException.class);

        WebElement elemento = espera.until(dr -> dr.findElement(By.id("elemento-dinamico")));

        elemento.click();

        driver.quit();
    }
}

En este ejemplo, la espera Fluent está configurada para esperar hasta un máximo de 30 segundos, verificando la condición cada 5 segundos. La condición es encontrar un elemento con id="elemento-dinamico". Si durante la espera se lanza una excepción NoSuchElementException, esta se ignora y continúa esperando hasta que se cumpla la condición o se agote el tiempo.

La flexibilidad de las esperas Fluent nos permite manejar mejor las condiciones dinámicas de una aplicación web. Podemos, por ejemplo, personalizar las excepciones a ignorar o definir acciones a realizar durante la espera, lo que mejora la robustez de nuestras pruebas automatizadas.

Es importante destacar que, con las esperas Fluent, podemos utilizar funciones lambda para definir condiciones más complejas. Esto nos permite evaluar múltiples condiciones o verificar atributos específicos de los elementos, adaptando la sincronización a las necesidades precisas de cada prueba.

Por ejemplo, si necesitamos esperar a que un elemento sea visible y contenga un texto específico, podemos hacerlo de la siguiente manera:

WebElement elemento = espera.until(dr -> {
    WebElement elem = dr.findElement(By.id("mensaje"));
    return elem.isDisplayed() && elem.getText().equals("Operación exitosa") ? elem : null;
});

En este fragmento, la espera verifica que el elemento con id="mensaje" esté visible y contenga el texto "Operación exitosa". Si ambas condiciones se cumplen, la función devuelve el elemento; de lo contrario, devuelve null y continúa esperando.

Las esperas Fluent son especialmente útiles en escenarios donde la aplicación web tiene comportamientos asíncronos o cuando interactúa con servicios externos que pueden introducir retrasos variables. Al ajustar el intervalo de sondeo y las excepciones ignoradas, podemos optimizar el tiempo de espera y evitar fallos intermitentes en las pruebas.

Al utilizar esperas Fluent, es recomendable ser prudente con los tiempos configurados para no prolongar innecesariamente la duración de las pruebas. Es fundamental encontrar un equilibrio entre la robustez de la sincronización y la eficiencia en la ejecución de los tests.

En resumen, las esperas Fluent proporcionan un control preciso sobre la sincronización en las pruebas automatizadas con Selenium, adaptándose a las necesidades específicas de cada caso y mejorando la fiabilidad de los tests.

Uso de ExpectedConditions

En las pruebas automatizadas con Selenium WebDriver, es común encontrarse con situaciones donde los elementos de una página web no están disponibles inmediatamente. Para manejar estos casos, Selenium proporciona la clase ExpectedConditions, que facilita la espera hasta que se cumpla una condición específica antes de continuar con la ejecución del test.

La clase ExpectedConditions ofrece una variedad de métodos estáticos que representan condiciones predefinidas. Estas condiciones pueden utilizarse con WebDriverWait para sincronizar las acciones de las pruebas con el estado real de la página web.

A continuación, se muestra cómo utilizar ExpectedConditions en un test con JUnit 5:

import org.junit.jupiter.api.Test;
import org.openqa.selenium.*;
import org.openqa.selenium.chrome.ChromeDriver;
import org.openqa.selenium.support.ui.WebDriverWait;
import static org.openqa.selenium.support.ui.ExpectedConditions.*;

import java.time.Duration;

public class PruebaExpectedConditions {

    @Test
    public void testEsperarElementoVisible() {
        WebDriver driver = new ChromeDriver();
        driver.get("https://ejemplo.com");

        WebDriverWait espera = new WebDriverWait(driver, Duration.ofSeconds(10));

        WebElement botonEnviar = espera.until(visibilityOfElementLocated(By.id("boton-enviar")));
        botonEnviar.click();

        driver.quit();
    }
}

En este ejemplo, se utiliza **visibilityOfElementLocated** para esperar hasta que el elemento con id="boton-enviar" sea visible en la página. La espera tiene un tiempo máximo de 10 segundos. Si la condición no se cumple en ese lapso, se lanzará una excepción TimeoutException.

Además de esperar por la visibilidad de un elemento, ExpectedConditions permite esperar por diversas condiciones, como que un elemento sea clicable, que esté presente en el DOM o que contenga cierto texto. Esto aporta flexibilidad al manejar diferentes situaciones en nuestras pruebas.

Por ejemplo, si necesitamos esperar a que un elemento sea clicable antes de interactuar con él:

WebElement enlaceDescarga = espera.until(elementToBeClickable(By.linkText("Descargar")));
enlaceDescarga.click();

En este caso, utilizamos **elementToBeClickable** para asegurarnos de que el elemento esté presente, visible y habilitado para recibir clics.

Para esperar hasta que un elemento contenga un texto específico, podemos utilizar:

espera.until(textToBePresentInElementLocated(By.id("mensaje"), "Operación exitosa"));

Aquí, la espera se mantiene hasta que el elemento con id="mensaje" contenga el texto "Operación exitosa". Esto es útil cuando necesitamos validar que cierta acción produjo el resultado esperado en la interfaz.

Si necesitamos esperar a que un elemento desaparezca del DOM, podemos utilizar:

boolean desaparecio = espera.until(invisibilityOfElementLocated(By.className("cargando")));

Esta condición es útil cuando queremos asegurarnos de que una animación de carga o un indicador de proceso haya finalizado antes de continuar con el test.

También es posible combinar condiciones utilizando **ExpectedConditions.or** y **ExpectedConditions.and**. Por ejemplo:

espera.until(or(
    visibilityOfElementLocated(By.id("resultado")),
    textToBePresentInElementLocated(By.id("error"), "No se encontraron datos")
));

En este ejemplo, la espera finaliza cuando se cumple al menos una de las dos condiciones: el elemento con id="resultado" es visible o el elemento con id="error" contiene el texto "No se encontraron datos".

Es importante mencionar que, desde Selenium 4.26, es recomendable utilizar las funciones predefinidas de ExpectedConditions en lugar de implementar nuestras propias condiciones. Esto garantiza una mayor fiabilidad y facilita el mantenimiento de las pruebas.

Cuando trabajamos con formularios o elementos dinámicos que se cargan vía AJAX, las ExpectedConditions son especialmente útiles para sincronizar las acciones y evitar excepciones como ElementNotInteractableException o StaleElementReferenceException.

Por ejemplo, si un botón se habilita solo después de completar ciertos campos, podemos esperar a que esté habilitado:

WebElement botonConfirmar = espera.until(elementToBeClickable(By.id("boton-confirmar")));
botonConfirmar.click();

En caso de necesitar esperar a que un elemento esté presente en el DOM sin importar su visibilidad, utilizamos:

WebElement elemento = espera.until(presenceOfElementLocated(By.name("usuario")));

La condición **presenceOfElementLocated** es útil cuando el elemento existe en el DOM pero aún no es visible o interactivo.

Para manejar ventanas emergentes o alertas, podemos utilizar:

espera.until(alertIsPresent());
Alert alerta = driver.switchTo().alert();
alerta.accept();

Aquí, esperamos hasta que una alerta esté presente y luego la aceptamos. Esto es esencial en pruebas donde se espera la aparición de mensajes de confirmación.

En situaciones donde necesitamos verificar que un frame esté disponible para interactuar, empleamos:

espera.until(frameToBeAvailableAndSwitchToIt(By.tagName("iframe")));

Con esta condición, esperamos hasta que el frame esté listo y automáticamente cambiamos el contexto del driver al frame especificado.

Es fundamental utilizar ExpectedConditions para mejorar la estabilidad y confiabilidad de las pruebas. Al sincronizar adecuadamente las acciones con el estado de la página, evitamos fallos intermitentes y obtenemos resultados más consistentes.

Recuerda que es buena práctica manejar las excepciones que puedan surgir durante las esperas, especialmente en casos donde el elemento podría no aparecer y eso sea un comportamiento esperado en la prueba.

Finalmente, siempre es recomendable cerrar el driver tras finalizar el test para liberar recursos:

driver.quit();

Al dominar el uso de ExpectedConditions, podrás crear pruebas más robustas y eficaces, aprovechando al máximo las capacidades que ofrece Selenium WebDriver.

Mejores prácticas para evitar problemas de sincronización

Para garantizar la fiabilidad y eficiencia de las pruebas automatizadas con Selenium, es fundamental adoptar buenas prácticas que eviten problemas de sincronización. A continuación, se presentan recomendaciones clave para lograr una interacción fluida con las aplicaciones web bajo prueba.

Es recomendable priorizar las esperas explícitas sobre las implícitas. Las esperas explícitas permiten sincronizar de manera precisa acciones específicas con condiciones particulares de la página. Al utilizar WebDriverWait junto con condiciones adecuadas, se mejora la estabilidad de los tests y se evitan tiempos de espera innecesarios.

Evita combinar esperas implícitas y explícitas en el mismo test. La mezcla de ambos tipos de esperas puede generar comportamientos impredecibles y complicar la depuración de errores. Es preferible utilizar únicamente esperas explícitas y manejar cada condición de forma individual.

Utiliza selectores de elementos concretos y eficientes. Al emplear localizadores precisos, como identificadores únicos o selectores CSS optimizados, se reduce el riesgo de que las pruebas fallen debido a cambios en la estructura de la página. Un buen selector mejora la velocidad de búsqueda de elementos y contribuye a la fiabilidad del test.

Maneja adecuadamente los elementos dinámicos que pueden aparecer o desaparecer durante la carga de la página. Para interactuar con ellos, espera hasta que cumplan las condiciones necesarias, como estar visibles o ser clicables. Esto se logra mediante condiciones de espera específicas que consideren el estado del elemento.

Evita el uso de **Thread.sleep()**, ya que introduce esperas fijas que pueden ralentizar las pruebas y no garantizan la sincronización adecuada. En su lugar, utiliza esperas explícitas con condiciones que reflejen el estado real de la aplicación. De esta manera, las pruebas serán más rápidas y robustas.

Implementa el manejo de excepciones para anticipar y controlar posibles errores durante la ejecución. Por ejemplo, captura excepciones como StaleElementReferenceException y aplica estrategias para reintentar la acción o actualizar la referencia al elemento. Esto ayuda a mantener la continuidad de la prueba ante cambios dinámicos en el DOM.

Aprovecha el uso de funciones lambda en las esperas para definir condiciones personalizadas de manera concisa. Esto mejora la legibilidad del código y facilita la creación de condiciones más complejas cuando las situaciones lo requieren.

Mantén los tiempos de espera lo más cortos posible sin comprometer la estabilidad. Esperas excesivamente largas pueden ocultar problemas en la sincronización y prolongar innecesariamente la ejecución de las pruebas. Ajusta los tiempos según el rendimiento esperado de la aplicación.

Utiliza el patrón Page Object Model para estructurar el código de las pruebas. Este enfoque separa la lógica de interacción con la página de las aserciones de los tests, promoviendo la reutilización y facilitando el mantenimiento. Al encapsular las esperas y acciones en objetos de página, se mejora la legibilidad y organizacion del código.

Asegúrate de que las pruebas simulen el comportamiento del usuario de manera realista. Incluye interacciones como desplazamientos hasta los elementos antes de hacer clic o llenar campos, ya que algunos elementos pueden no ser interactuables hasta que son visibles en pantalla. Esto se puede lograr utilizando acciones como actions.moveToElement(elemento).

Considera el uso de logs y registros para monitorear el flujo de ejecución de las pruebas. Registrar información relevante en puntos clave ayuda a identificar y solucionar problemas de sincronización. Esto es especialmente útil en pruebas complejas o cuando se ejecutan en entornos de integración continua.

Mantén siempre el driver actualizado a la versión más reciente compatible con Selenium 4.26. Las actualizaciones suelen incluir mejoras en el manejo de sincronización y corrección de errores, lo que contribuye a la estabilidad de las pruebas.

Por último, realiza pruebas periódicas y ajustes en tus scripts para adaptarte a los cambios en la aplicación. Las aplicaciones web están en constante evolución, y es esencial que las pruebas reflejen estos cambios para mantener su eficacia.

Aplicando estas prácticas, se minimizan los problemas de sincronización y se mejora significativamente la calidad de las pruebas automatizadas con Selenium.

Aprende Selenium GRATIS online

Todas las lecciones de Selenium

Accede a todas las lecciones de Selenium y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.

Accede GRATIS a Selenium y certifícate

En esta lección

Objetivos de aprendizaje de esta lección

  • Entender el concepto de espera y por qué es importante
  • Conocer los problemas derivados de una mala sincronización
  • Saber programar esperas implícitas
  • Saber programar esperas explícitas
  • Sintaxis Fluent
  • Uso de ExpectedConditions
  • Buenas prácticas de sincronización