Lección 2 de 6 · 2 min
Selectores robustos y esperas explícitas
Al terminar sabrás
11 min- Elegir selectores robustos y descartar XPath absolutos y clases de estilo
- Distinguir presencia, visibilidad y clickeabilidad al esperar
- Diagnosticar y arreglar un StaleElementReferenceException
Construyes: Un test contra contenido dinámico con espera lambda y un stale reference resuelto.
La jerarquía de selectores
De mejor a peor para automatización:
By.ID, "submit-btn" # 1º si son estables
By.CSS_SELECTOR, "[data-test='submit']" # 2º el estándar pro
By.XPATH, "//button[normalize-space()='Enviar']" # 3º por texto visible
By.CSS_SELECTOR, "form.checkout button[type='submit']" # 4º CSS semántico
Prohibido commitear: XPath absolutos (/html/body/div[2]/...) y clases de estilos (.btn-primary-lg). Ambos se rompen con el próximo refactor visual sin que haya cambiado ningún comportamiento.
Los tres niveles de "estar ahí"
El error más común de la semana 2 es confundirlos:
- Presencia — el elemento existe en el DOM (puede estar oculto).
- Visibilidad — existe Y se ve (display, opacity, tamaño).
- Clickeabilidad — se ve Y está habilitado.
wait.until(EC.presence_of_element_located(loc)) # para leer atributos
wait.until(EC.visibility_of_element_located(loc)) # para leer texto
wait.until(EC.element_to_be_clickable(loc)) # para interactuar
¿element.text te devuelve vacío? Casi siempre es que esperaste presencia y necesitabas visibilidad.
Espera implícita en 0, explícitas siempre
driver.implicitly_wait(0) # explícito mejor que implícito
wait = WebDriverWait(driver, 10)
Mezclar implícita global con explícitas produce timeouts que se suman de formas impredecibles — el clásico "el test demora 40 segundos y nadie sabe por qué".
Condiciones propias
Cuando las expected_conditions no alcanzan, una lambda es una condición válida:
wait.until(lambda d: len(d.find_elements(By.CSS_SELECTOR, ".fila")) >= 5)
wait.until(lambda d: d.execute_script("return document.readyState") == "complete")
StaleElementReferenceException: el rito de iniciación
Aparece cuando guardas un elemento y el DOM se re-renderiza (típico en SPAs). La solución nunca es un sleep — es re-buscar el elemento después del cambio:
# ❌ referencia vieja tras el re-render
btn = driver.find_element(By.ID, "like")
btn.click()
btn.click() # StaleElementReferenceException
# ✅ re-localiza tras cada acción que muta el DOM
driver.find_element(By.ID, "like").click()
wait.until(EC.element_to_be_clickable((By.ID, "like"))).click()
Fija los conceptos con el quiz y enfréntate al stale en el reto.
Compruébalo
0/3Responde sin mirar atrás. Verás la explicación al instante.
1. element.text te devuelve vacío. ¿Causa más probable?
2. ¿Qué selectores están prohibidos commitear?
3. Guardas un elemento, el DOM se re-renderiza y al usarlo salta StaleElementReferenceException. ¿Solución?
Reto: dinámico y sin stale
En una página con contenido dinámico (una SPA pública), escribe un test que espere a que una lista cargue al menos N elementos usando una condición lambda, y lee el texto del último.
Provoca a propósito un StaleElementReferenceException y arréglalo re-localizando.