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