Explicit Wait

Avanzado
Selenium
Selenium
Actualizado: 05/09/2025

¡Desbloquea el curso de Selenium completo!

IA
Ejercicios
Certificado
Entrar

Mira la lección en vídeo

Accede al vídeo completo de esta lección y a más contenido exclusivo con el Plan Plus.

Desbloquear Plan Plus

WebDriverWait con ExpectedConditions

Las esperas explícitas representan la aproximación profesional para gestionar la sincronización en aplicaciones web modernas. A diferencia de las esperas implícitas, las esperas explícitas nos permiten definir condiciones específicas que el navegador debe cumplir antes de continuar con la ejecución del test.

WebDriverWait es la clase principal que implementa este mecanismo. Funciona en combinación con la clase ExpectedConditions, que proporciona un conjunto de condiciones predefinidas para los escenarios más habituales en automatización web.

La sintaxis moderna utiliza Duration.ofSeconds() para especificar el tiempo máximo de espera, reemplazando las implementaciones legacy que usaban unidades de tiempo menos expresivas:

@Test
void testExplicitWait() {
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    
    WebElement element = wait.until(
        ExpectedConditions.visibilityOfElementLocated(By.id("dynamic-content"))
    );
    
    element.click();
}

Condiciones fundamentales con ExpectedConditions

El conjunto de condiciones predefinidas cubre la mayoría de escenarios que encontramos en aplicaciones web reales. Cada condición está optimizada para verificar un estado específico del elemento o de la página.

visibilityOfElementLocated() espera hasta que un elemento sea visible en la página. Esta condición verifica tanto que el elemento exista en el DOM como que sea visible para el usuario:

@Test
void testElementVisibility() {
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
    
    // Esperar a que aparezca un mensaje de confirmación
    WebElement successMessage = wait.until(
        ExpectedConditions.visibilityOfElementLocated(
            By.className("alert-success")
        )
    );
    
    assertTrue(successMessage.isDisplayed());
}

elementToBeClickable() garantiza que un elemento no solo sea visible, sino que también esté habilitado para recibir clics. Esta condición es especialmente útil con botones que pueden estar deshabilitados temporalmente:

@Test
void testClickableButton() {
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    
    // Esperar a que un botón esté disponible para hacer clic
    WebElement submitButton = wait.until(
        ExpectedConditions.elementToBeClickable(By.id("submit-btn"))
    );
    
    submitButton.click();
}

presenceOfElementLocated() verifica únicamente que el elemento exista en el DOM, sin importar si es visible. Esta condición es útil para elementos que sabemos que se cargan pero pueden estar ocultos inicialmente:

@Test
void testElementPresence() {
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(8));
    
    // Verificar que existe un elemento oculto en el DOM
    wait.until(
        ExpectedConditions.presenceOfElementLocated(
            By.id("hidden-data")
        )
    );
    
    // El elemento existe, aunque no sea visible
    WebElement hiddenElement = driver.findElement(By.id("hidden-data"));
    assertNotNull(hiddenElement);
}

Condiciones avanzadas para contenido dinámico

Las aplicaciones modernas frecuentemente actualizan el contenido de forma asíncrona. textToBePresentInElement() permite esperar hasta que un texto específico aparezca dentro de un elemento determinado:

@Test
void testDynamicTextContent() {
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(12));
    
    WebElement statusDiv = driver.findElement(By.id("status"));
    
    // Esperar a que el estado cambie a "Procesado"
    wait.until(
        ExpectedConditions.textToBePresentInElement(
            statusDiv, "Procesado"
        )
    );
    
    assertEquals("Procesado", statusDiv.getText());
}

Para situaciones que involucran alertas del navegador, la condición alertIsPresent() proporciona una forma elegante de manejar estos diálogos:

@Test
void testAlertHandling() {
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(5));
    
    // Ejecutar acción que genera una alerta
    driver.findElement(By.id("delete-btn")).click();
    
    // Esperar y manejar la alerta
    Alert alert = wait.until(ExpectedConditions.alertIsPresent());
    
    assertEquals("¿Confirmar eliminación?", alert.getText());
    alert.accept();
}

Manejo profesional de timeouts

Cuando una condición no se cumple dentro del tiempo especificado, WebDriverWait lanza una TimeoutException. El manejo adecuado de esta excepción es fundamental para crear tests robustos y proporcionar información útil sobre fallos:

@Test
void testTimeoutHandling() {
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(3));
    
    try {
        wait.until(
            ExpectedConditions.visibilityOfElementLocated(
                By.id("slow-loading-element")
            )
        );
    } catch (TimeoutException e) {
        // Logging específico para debugging
        System.out.println("Elemento no apareció en el tiempo esperado: " + e.getMessage());
        
        // Verificar estado alternativo o realizar acción de contingencia
        assertTrue(driver.findElements(By.className("loading-spinner")).isEmpty(),
                  "El spinner de carga sigue presente");
    }
}

La combinación de múltiples condiciones permite crear esperas más sofisticadas utilizando métodos de utilidad. Podemos encadenar condiciones o usar operadores lógicos para escenarios complejos:

@Test
void testMultipleConditions() {
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
    
    // Esperar a que un formulario esté completamente cargado
    wait.until(ExpectedConditions.and(
        ExpectedConditions.visibilityOfElementLocated(By.id("username")),
        ExpectedConditions.elementToBeClickable(By.id("login-btn"))
    ));
    
    // Ahora podemos interactuar con el formulario de forma segura
    driver.findElement(By.id("username")).sendKeys("usuario@test.com");
    driver.findElement(By.id("login-btn")).click();
}

Esta aproximación sistemática con ExpectedConditions elimina la mayoría de problemas de sincronización en tests automatizados, proporcionando una base sólida para construir suites de pruebas confiables y mantenibles.

Custom ExpectedConditions

Guarda tu progreso

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

Aunque las condiciones predefinidas cubren la mayoría de escenarios comunes, las aplicaciones empresariales frecuentemente requieren lógica de espera específica que no está contemplada en el conjunto estándar de ExpectedConditions. La creación de condiciones personalizadas nos permite encapsular reglas de negocio complejas y reutilizar lógica de sincronización específica del dominio.

Las condiciones personalizadas se implementan mediante la interfaz funcional Function<WebDriver, Boolean>, donde el parámetro WebDriver representa el estado actual del navegador y el valor de retorno Boolean indica si la condición se ha cumplido. Esta aproximación funcional se alinea perfectamente con las características modernas de Java.

Implementación básica con Function

La forma más directa de crear una condición personalizada es implementar directamente la interfaz Function. Esta aproximación es ideal cuando necesitamos encapsular lógica compleja que puede reutilizarse en múltiples tests:

@Test
void testCustomConditionWithFunction() {
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    
    Function<WebDriver, Boolean> pageFullyLoaded = webDriver -> {
        // Verificar que el DOM esté completo
        JavascriptExecutor js = (JavascriptExecutor) webDriver;
        String readyState = js.executeScript("return document.readyState").toString();
        
        // Verificar que no haya spinners de carga activos
        List<WebElement> spinners = webDriver.findElements(By.className("loading-spinner"));
        
        return "complete".equals(readyState) && spinners.isEmpty();
    };
    
    wait.until(pageFullyLoaded);
}

Condiciones con expresiones lambda

Las expresiones lambda proporcionan una sintaxis más concisa para condiciones que se usan una sola vez o tienen lógica simple. Esta aproximación es especialmente efectiva para validaciones específicas del contexto:

@Test
void testLambdaConditions() {
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(8));
    
    // Esperar a que una tabla tenga al menos 3 filas de datos
    wait.until(driver -> {
        List<WebElement> rows = driver.findElements(By.cssSelector("table tbody tr"));
        return rows.size() >= 3;
    });
    
    // Verificar que un elemento contenga texto no vacío
    wait.until(driver -> {
        WebElement content = driver.findElement(By.id("dynamic-content"));
        return !content.getText().trim().isEmpty();
    });
}

Condiciones complejas para reglas de negocio

Las aplicaciones empresariales a menudo requieren validaciones específicas del dominio que combinan múltiples elementos y estados. Podemos crear condiciones que encapsulen estas reglas complejas:

@Test
void testBusinessRuleCondition() {
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
    
    Function<WebDriver, Boolean> orderProcessingComplete = webDriver -> {
        try {
            // Verificar estado del pedido
            WebElement statusElement = webDriver.findElement(By.id("order-status"));
            String status = statusElement.getText();
            
            // Verificar que el botón de pago esté habilitado
            WebElement paymentButton = webDriver.findElement(By.id("payment-btn"));
            boolean isPaymentEnabled = paymentButton.isEnabled();
            
            // Verificar que el precio total esté calculado
            WebElement totalElement = webDriver.findElement(By.id("total-amount"));
            String totalText = totalElement.getText();
            
            return "Confirmado".equals(status) && 
                   isPaymentEnabled && 
                   !totalText.isEmpty() && 
                   !totalText.equals("0,00 €");
                   
        } catch (NoSuchElementException e) {
            return false; // Si algún elemento no existe, la condición no se cumple
        }
    };
    
    wait.until(orderProcessingComplete);
}

Condiciones parametrizables y reutilizables

Para maximizar la reutilización y mantener el código DRY (Don't Repeat Yourself), podemos crear métodos que generen condiciones personalizadas basadas en parámetros. Esta aproximación es especialmente valiosa en suites de pruebas grandes:

public class CustomConditions {
    
    public static Function<WebDriver, Boolean> elementHasMinimumChildren(
            By locator, int minimumCount) {
        return driver -> {
            try {
                WebElement parent = driver.findElement(locator);
                List<WebElement> children = parent.findElements(By.xpath("./*"));
                return children.size() >= minimumCount;
            } catch (NoSuchElementException e) {
                return false;
            }
        };
    }
    
    public static Function<WebDriver, Boolean> textMatchesPattern(
            By locator, String regex) {
        return driver -> {
            try {
                WebElement element = driver.findElement(locator);
                return element.getText().matches(regex);
            } catch (NoSuchElementException e) {
                return false;
            }
        };
    }
}

@Test
void testParametrizableConditions() {
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(12));
    
    // Esperar a que una lista tenga al menos 5 elementos
    wait.until(CustomConditions.elementHasMinimumChildren(
        By.id("product-list"), 5
    ));
    
    // Esperar a que un código de seguimiento tenga formato válido
    wait.until(CustomConditions.textMatchesPattern(
        By.id("tracking-code"), "^[A-Z]{2}\\d{10}$"
    ));
}

Manejo de excepciones en condiciones personalizadas

Las condiciones personalizadas deben manejar adecuadamente las excepciones que pueden surgir durante su evaluación. Un manejo robusto de excepciones evita fallos inesperados y proporciona mejor información de debugging:

@Test
void testRobustCustomCondition() {
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(10));
    
    Function<WebDriver, Boolean> safeElementCondition = driver -> {
        try {
            WebElement element = driver.findElement(By.id("volatile-element"));
            
            // Verificar múltiples propiedades de forma segura
            return element.isDisplayed() && 
                   element.isEnabled() && 
                   element.getAttribute("class").contains("ready");
                   
        } catch (NoSuchElementException | StaleElementReferenceException e) {
            // Estas excepciones son esperadas durante la evaluación
            return false;
        } catch (WebDriverException e) {
            // Log para debugging en caso de errores inesperados
            System.err.println("Error inesperado en condición personalizada: " + e.getMessage());
            return false;
        }
    };
    
    try {
        wait.until(safeElementCondition);
    } catch (TimeoutException e) {
        // Proporcionar contexto adicional sobre el fallo
        fail("El elemento no alcanzó el estado esperado en el tiempo límite. " +
             "Verificar que el elemento con ID 'volatile-element' existe y se carga correctamente.");
    }
}

Integración con JavaScript para condiciones avanzadas

Algunas condiciones requieren evaluación de JavaScript para acceder a propiedades del DOM o estados de la aplicación que no están directamente disponibles a través de la API de WebDriver:

@Test
void testJavaScriptBasedCondition() {
    WebDriverWait wait = new WebDriverWait(driver, Duration.ofSeconds(15));
    
    Function<WebDriver, Boolean> ajaxRequestsCompleted = driver -> {
        JavascriptExecutor js = (JavascriptExecutor) driver;
        
        // Verificar que jQuery (si está presente) no tenga requests activos
        Object jqueryActive = js.executeScript(
            "return typeof jQuery !== 'undefined' ? jQuery.active : 0"
        );
        
        // Verificar que fetch requests personalizados hayan terminado
        Object customRequestActive = js.executeScript(
            "return window.activeRequests || 0"
        );
        
        return ((Long) jqueryActive) == 0 && ((Long) customRequestActive) == 0;
    };
    
    wait.until(ajaxRequestsCompleted);
    
    // Proceder con acciones que requieren que todas las requests hayan terminado
    driver.findElement(By.id("submit-form")).click();
}

Esta aproximación de condiciones personalizadas proporciona la flexibilidad necesaria para manejar los escenarios más complejos en aplicaciones web modernas, manteniendo al mismo tiempo la robustez y legibilidad que caracterizan a las pruebas automatizadas profesionales.

Aprendizajes de esta lección de Selenium

  • Comprender el uso de WebDriverWait y ExpectedConditions para implementar esperas explícitas.
  • Identificar y aplicar condiciones predefinidas comunes para sincronización en pruebas web.
  • Crear condiciones personalizadas usando funciones y expresiones lambda para escenarios específicos.
  • Manejar adecuadamente excepciones y timeouts en esperas explícitas.
  • Integrar evaluaciones JavaScript para condiciones avanzadas en aplicaciones dinámicas.

Completa este curso de Selenium y certifícate

Únete a nuestra plataforma de cursos de programación y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración