Cuando las suites de pruebas Selenium funcionan en tu portátil pero se desploman sin aviso en un nodo de Jenkins, no es “mala suerte”: es una combinación de compatibilidad de binarios, puertos efímeros saturados y recursos de Windows que se agotan. En esta guía aprenderás a detectar la causa real y a estabilizar tus lotes de 20‑30 tests sin que vuelva a aparecer el temido “target machine actively refused it”.
Resumen del problema
En un servidor Windows alojado en AWS se lanza una pipeline Jenkins (o un simple pytest -n auto
) que dispara decenas de casos en paralelo. De forma aleatoria, algunos navegadores fallan con:
MaxRetryError: HTTPConnectionPool(host='localhost', port=62210)…
urllib3.exceptions.NewConnectionError:
Failed to establish a new connection: [WinError 10061]
El Visor de eventos muestra un crash de chromedriver.exe con la excepción 0xc0000005
. La combinación instalada es Google Chrome 123.0.6312.59
+ ChromeDriver 123.0.6312.59
.
Por qué ocurre este fallo intermitente
- Incompatibilidad de versiones Chromedriver y Chrome comparten librerías nativas; una discrepancia menor basta para provocar segfault.
- Asignación masiva de puertos locales Cada
driver = webdriver.Chrome()
requiere un puerto libre. Con 30 instancias simultáneas, el rango 49152‑65535 puede agotarse o colisionar con el firewall local. - Fugas de memoria en la rama 123 Google reconoció problemas en Chrome 123 bajo Windows Server al ejecutar en headless.
- Recursos del host Si la VM tiene poca RAM/CPU, el sistema operativo termina procesos “silenciosamente”.
Diagnóstico rápido antes de tocar código
Prueba | Resultado esperado | Interpretación |
---|---|---|
Ejecutar 1 test aislado | Éxito constante | La lógica del test no es el problema |
Ejecutar 30 tests en secuencia (sin parallel) | Éxito | El fallo se relaciona con concurrencia/recursos |
Monitorear puertos con netstat -ano | Picos en >60000 | Posible agotamiento de puertos efímeros |
Activar --verbose en chromedriver | Stack‑trace antes del crash | Indica fuga de memoria o incompatibilidad |
Solución paso a paso
Actualizar Selenium y usar Selenium Manager
Selenium 4.11+ incorpora Selenium Manager, que descarga automáticamente la versión de driver que coincide con el navegador detectado:
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
options = Options()
options.add\_argument("--headless=new") # Modo headless moderno
options.add\_argument("--disable-dev-shm-usage")
options.add\_argument("--no-sandbox")
options.add\_argument("--disable-gpu")
driver = webdriver.Chrome(options=options) # Selenium Manager resuelve el driver
Con esto eliminas el riesgo de instalar un chromedriver equivocado y reduces líneas de mantenimiento.
Controlar los recursos del servidor
- Asignar CPU y RAM suficientes Para 30 navegadores headless se recomiendan al menos 4 vCPU y 8 GB de RAM.
- Desactivar antivirus en rutas de build El análisis en tiempo real de Windows Defender ralentiza la apertura de cada driver.
- Configurar
Page‑File
Un archivo de paginación fijo (p. ej. 4 GB) evita que Windows “congele” procesos.
Optimizar la estrategia de concurrencia
En pytest‑xdist puedes reducir trabajadores:
pytest -n 10 --max-worker-restart=2 -q
o incluso probar -n logical
, que adapta el paralelismo al número de cores.
En Jenkins, separa la ejecución en stages:
stage('UI Tests Batch 1') {
steps { sh 'pytest -k "group1" -n 10' }
}
stage('UI Tests Batch 2') {
steps { sh 'pytest -k "group2" -n 10' }
}
Evitar puertos en conflicto
Windows asigna puertos efímeros a partir de 49152. Para ampliarlo:
reg add "HKLM\\SYSTEM\\CurrentControlSet\\Services\\Tcpip\\Parameters" ^
/v MaxUserPort /t REG_DWORD /d 65534 /f
Tras reiniciar, el sistema podrá abrir hasta 16 382 puertos adicionales antes de rechazar conexiones.
Configurar timeouts y reintentos
Un driver saturado responde tarde. Amplía los márgenes e implementa reintentos:
from selenium.webdriver.support.ui import WebDriverWait
from selenium.common.exceptions import TimeoutException
from tenacity import retry, stopafterattempt, wait_fixed
@retry(stop=stop\after\attempt(3), wait=wait\_fixed(5))
def wait\for\element(driver, locator):
return WebDriverWait(driver, 20).until(
lambda d: d.find\_element(\*locator)
)
Con pytest-rerunfailures (--reruns 2 --reruns-delay 5
) puedes reejecutar casos esporádicos sin false negatives.
Registrar logs detallados
Guardar los logs como artefactos facilita el post‑mortem. Añade en tu pipeline:
CHROMEDRIVERVERBOSELOG=1
pytest --log-cli-level=INFO --log-file=test_run.log
Actualizar/retroceder versión de Chrome
Si actualizas a Chrome 124+ o retrocedes a 122, desaparecen fugas vistas en 123. Usa winget
para versiones estables:
winget install --id Google.Chrome -v 124.0.6367.60
Considerar una arquitectura distribuida
En lugar de abrir decenas de ventanas en la misma VM, levanta un Selenium Grid (o utiliza AWS Device Farm). Cada nodo ejecuta 3‑5 navegadores y devuelve resultados al hub central, evitando conflictos de puertos.
Checklist de mejores prácticas
Ítem | Acción recomendada |
---|---|
Versiones | Chrome y ChromeDriver idénticos o usar Selenium Manager |
Paralelismo | Límitar a nº cores o lotes de 10 |
RAM/CPU | >8 GB RAM y 4 vCPU para 30 navegadores |
Puertos | Ampliar MaxUserPort y revisar firewall |
Timeouts | implicitwait 5‑10 s , pageload_timeout 60 s |
Liberar procesos | driver.quit() en tearDown |
Logs | Enlace a test_run.log como artefacto Jenkins |
Versión Chrome | Actualizar a >=124 o retroceder a 122 |
Grid | Distribuir carga en varios nodos |
Preguntas frecuentes
¿Puedo usar Edge en vez de Chrome? Sí. EdgeDriver tiene menos problemas de puertos en Windows Server 2022, pero asegúrate de emparejar versiones.
¿Por qué ocurre solo en AWS y no en mi PC? Las instancias pequeñas (t3.medium) limitan bursts de CPU; cuando la métrica Credit balance llega a cero, el rendimiento se desploma y los drivers no responden.
¿Reciclar el driver acelera los tests? No. Reutilizar webdriver
entre casos ahorra segundos, pero aumenta fugas. Crea y destruye un driver por fixture siempre que tengas recursos.
Conclusión
La combinación de compatibilidad estricta, control de recursos y una estrategia de paralelismo adecuada resuelve casi todos los cierres inesperados de ChromeDriver. Inicia tus pruebas con versiones correctas, monitoriza puertos/CPU, amplía timeouts y registra logs: tu pipeline Jenkins dejará de fallar con el odioso mensaje “target machine actively refused it”. Y si la carga crece, escalar a un Selenium Grid evitará cuellos de botella mientras mantienes la estabilidad de tus entregas CI/CD.