Lección 2 de 6 · 2 min
Selectores robustos y esperas explícitas
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()
Ejercicio
En una página con contenido dinámico (por ejemplo 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.