Selenium/Python sur Windows Server AWS : corriger WinError 10061 et les crashs ChromeDriver (guide complet)

Vos tests Selenium/Python plantent par vagues sur Windows Server (AWS) avec des erreurs aléatoires « connection refused » et des crashs ChromeDriver ? Voici un guide opérationnel, pas à pas, pour stabiliser durablement vos exécutions parallèles.

Sommaire

Problème : exécution de lots de tests Selenium/Python instables sur un serveur AWS Windows

Vue d’ensemble

  • Symptôme : lors de l’exécution de 20–30 tests via Pytest ou Jenkins, certains échouent de manière aléatoire avec MaxRetryError / NewConnectionError (WinError 10061 : « connection refused ») au moment des appels HTTP à ChromeDriver.
  • Constat dans les journaux : le processus chromedriver.exe se termine inopinément (exception 0xc0000005), tandis qu’un même test exécuté seul réussit.
  • Environnement : Windows Server sur AWS, Chrome 123.0.6312.59 et ChromeDriver de même version.

Comprendre les erreurs WinError 10061 et le code d’exception 0xc0000005

Le client Selenium (votre script Python) dialogue avec chromedriver.exe via une socket locale (HTTP). L’erreur WinError 10061 (« connection refused ») signifie que personne n’écoute sur le port cible à l’instant T : soit ChromeDriver n’a pas encore écouté, soit il a crashé ou est mort entre-temps, soit le port a été réaffecté ou bloqué par le système. Le code d’exception 0xc0000005 désigne une Access Violation (accès mémoire invalide) qui peut résulter d’un binaire instable, d’injections de pilotes/antivirus, d’extensions, ou d’une pression mémoire.

Causes techniques probables

CatégorieExplication succincte
Incompatibilité ou bug Chrome / ChromeDriverVersion très récente, binaire corrompu ou régression provoquant des crashs sporadiques.
Conflit de ports locauxChaque instance ChromeDriver écoute sur un port aléatoire ; une forte parallélisation épuise les ports éphémères ou réutilise des sockets encore en TIME_WAIT.
Ressources serveur insuffisantesPics CPU/RAM provoquant la fermeture du processus ou l’échec de la liaison socket dans les délais.
Parallélisme excessifpytest-xdist/Jenkins ouvrent de nombreux navigateurs ; si driver.quit() n’est pas systématiquement appelé, des processus « zombies » persistent.
Interférences systèmeAntivirus, politiques de groupe, pare-feu ou outils EDR tuent/bloquent le processus ou ses connexions locales.

Stratégie de résolution (playbook complet)

Sécuriser le socle navigateur

  1. Vérifier la parité exacte Chrome / ChromeDriver. N’utilisez que des versions assorties. Deux options robustes :
    • Selenium Manager (intégré à Selenium 4+) récupère automatiquement le driver compatible.
    • WebDriverManager (lib Python) télécharge la version correspondante.
    # Option A : Selenium Manager (recommandée) from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options opts = Options() opts.add_argument("--headless=new") # si exécution headless service = Service(port=0) # laisse l'OS choisir un port libre driver = webdriver.Chrome(service=service, options=opts) driver.get("[https://example.com](https://example.com)") driver.quit() # Option B : WebDriverManager (si vous gérez vous-mêmes le cache) from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options from webdriver_manager.chrome import ChromeDriverManager opts = Options() opts.add_argument("--headless=new") service = Service(ChromeDriverManager().install(), port=0) driver = webdriver.Chrome(service=service, options=opts) driver.quit()
  2. Tester une version précédente/suivante de Chrome/ChromeDriver pour isoler un bug régressif. Conservez la version stable dans un cache d’artefacts (CI/CD) pour la reproductibilité.

Gérer les ports et le parallélisme

  1. Limiter ou séquencer la concurrence. Sur des hôtes Windows généraux, visez ~1 vCPU et 1.5–2 Go RAM par navigateur au minimum. Démarrez prudemment (ex. -n 4) puis montez. # Pytest (xdist) pytest -n 4 --dist loadfile # Jenkins Pipeline (déclaratif) : limiter le parallélisme du job options { disableConcurrentBuilds() // ou plugin Throttle Concurrent Builds si disponible }
  2. Fermer proprement chaque session. Utilisez une fixture yield assurant quit() même en cas d’échec. # conftest.py import tempfile, shutil, time import contextlib import itertools from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.chrome.options import Options def create_driver(): opts = Options() opts.add_argument("--headless=new") opts.add_argument("--disable-extensions") opts.add_argument("--disable-gpu") opts.add_argument("--no-sandbox") # surtout utile sur Linux ; inoffensif sur Windows opts.add_argument("--disable-dev-shm-usage") # idem # Profil isolé par test (supprime toute contention de profil) user_data_dir = tempfile.mkdtemp(prefix="chrome_profile_") opts.add_argument(f"--user-data-dir={user_data_dir}") service = Service(port=0) drv = webdriver.Chrome(service=service, options=opts) # Attache le chemin pour cleanup drv._user_data_dir = user_data_dir return drv def retry_create_driver(attempts=3, base_sleep=0.8): for i in range(1, attempts+1): try: return create_driver() except Exception as e: if i == attempts: raise time.sleep(base_sleep * i) # backoff linéaire (simple & efficace) import pytest @pytest.fixture def driver(): d = retry_create_driver() yield d with contextlib.suppress(Exception): d.quit() with contextlib.suppress(Exception): shutil.rmtree(getattr(d, "_user_data_dir", ""), ignore_errors=True)
  3. Étendre la plage de ports éphémères et réduire TIME_WAIT. Sur Windows Server récents, préférez netsh pour la plage dynamique, et ajustez les registres si besoin. Afficher la plage actuellenetsh int ipv4 show dynamicport tcp netsh int ipv4 show dynamicport udpÉtendre la plage (exemple)rem Démarre à 10000, 55535 ports (jusqu'à 65535) netsh int ipv4 set dynamicport tcp start=10000 num=55535 netsh int ipv4 set dynamicport udp start=10000 num=55535Accélérer la libération de ports (TIME_WAIT)reg add HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters ^ /v TcpTimedWaitDelay /t REG_DWORD /d 30 /f rem Optionnel (compat.) : augmenter le nombre de ports utilisables reg add HKLM\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters ^ /v MaxUserPort /t REG_DWORD /d 65534 /fUn redémarrage est recommandé après ces modifications.

Renforcer la robustesse ChromeDriver

  1. Démarrer chromedriver via Service en demandant un port libre. Port 0 confie le choix à l’OS, évitant la collision. from selenium.webdriver.chrome.service import Service service = Service(port=0)
  2. Passer des flags de stabilité. Selon l’OS : --disable-dev-shm-usage, --no-sandbox, --disable-extensions, --headless=new. Sous Windows GUI, --disable-gpu évite certains plantages graphiques.
  3. Isoler chaque profil Chrome. --user-data-dir unique par test empêche les verrous de profil (source fréquente de crashs silencieux).
  4. Activer des logs exploitables. Demarrez ChromeDriver en mode verbeux et archivez les logs en artefacts CI. rem Exemple (démarrage manuel pour debug ciblé) start "" chromedriver.exe --port=0 --verbose --log-path=%WORKSPACE%\logs\chromedriver_%RANDOM%.log

Réduire la charge système

  1. Surveiller CPU/RAM pendant l’exécution. Activez PerfMon/CloudWatch Agent pour remonter :
    • Processor(_Total)\% Processor Time, System\Processor Queue Length
    • Memory\Available MBytes, Process(chrome)\Private Bytes
    • TCPv4\Connections Established, TCPv4\Connections Passive
    En cas de saturation, augmentez la taille de l’instance (ex. familles à hautes fréquences CPU) et activez un page file automatique. # Activer la gestion automatique du fichier d'échange wmic computersystem where name="%computername%" set AutomaticManagedPagefile=True
  2. Exclure les binaires de l’antivirus/EDR. Déclarez en exclusion le répertoire des drivers et des profils temporaires. # Windows Defender : exemples d'exclusion Add-MpPreference -ExclusionPath "C:\Tools\selenium\drivers" Add-MpPreference -ExclusionPath "C:\Jenkins\workspace" Add-MpPreference -ExclusionProcess "chromedriver.exe" Add-MpPreference -ExclusionProcess "chrome.exe" Vérifiez également les politiques AppLocker/Software Restriction Policies qui pourraient bloquer chromedriver.exe.

Alternatives d’architecture

  1. Selenium Grid / RemoteWebDriver. Externalisez le navigateur vers un Grid Linux (EC2 auto-scalé), ce qui isole les ports et évite la pression locale.
  2. Dockeriser l’exécution. Utilisez des conteneurs éphémères, chacun avec son Chrome/ChromeDriver. Sur Windows, préférez des workers Linux pour la simplicité (lancement en agents Jenkins Linux) ou WSL 2 si vous restez sur hôte Windows.

Runbook opérationnel (check-list rapide)

ÉtapeAction concrèteObjectifTemps estimé
1 : Parité versionsBasculer sur Selenium Manager ou verrouiller Chrome/ChromeDriver assortisÉliminer les crashs dûs à un mismatch~15 min
2 : ParallélismeRéduire à -n 4, observer, augmenter progressivementLimiter contention CPU/RAM/ports~30 min
3 : PortsConfigurer netsh + TcpTimedWaitDelayEmpêcher l’épuisement des ports éphémères~20 min + reboot
4 : AntivirusCréer des exclusions Defender/EDRÉviter les kill/scan bloquants~15 min
5 : NettoyagePost-build : purge des process orphelins et des profilsSupprimer les verrous de port et fuites~10 min

Implémentations références (copier-coller)

Fixture Pytest robuste avec retries à la création du WebDriver

# conftest.py
import os, time, tempfile, shutil, contextlib
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options

MAX_CREATE_ATTEMPTS = 3

def *new_chrome():
opts = Options()
opts.add_argument("--headless=new")
opts.add_argument("--disable-extensions")
opts.add_argument("--disable-gpu")
opts.add_argument("--no-sandbox")
opts.add_argument("--disable-dev-shm-usage")
# profil isolé
profile_dir = tempfile.mkdtemp(prefix="chrome_profile*")
opts.add_argument(f"--user-data-dir={profile_dir}")

```
service = Service(port=0)
drv = webdriver.Chrome(service=service, options=opts)
drv._profile_dir = profile_dir
return drv
```

def make_driver_with_retry():
last_exc = None
for attempt in range(1, MAX_CREATE_ATTEMPTS+1):
try:
return _new_chrome()
except Exception as e:
last_exc = e
time.sleep(0.7 * attempt)
raise last_exc

@pytest.fixture
def driver():
drv = make_driver_with_retry()
yield drv
with contextlib.suppress(Exception):
drv.quit()
with contextlib.suppress(Exception):
shutil.rmtree(getattr(drv, "_profile_dir", ""), ignore_errors=True)

Nettoyage systématique entre deux builds (Windows Batch)

echo Nettoyage des processus Chrome/ChromeDriver...
taskkill /IM chrome.exe /F 2>nul
taskkill /IM chromedriver.exe /F 2>nul

echo Purge des profils temporaires créés par les tests...
for /d %%D in ("%WORKSPACE%\chrome_profile_*") do rd /s /q "%%~fD"

echo Etat des ports en TIME_WAIT (diagnostic) :
netstat -an | find "TIME_WAIT" | find ":127.0.0.1" | find ":http" /c

Jenkins Pipeline : throttling et artefacts

pipeline {
  agent { label 'windows' }
  options {
    disableConcurrentBuilds()       // empêche les builds parallèles du même job
    buildDiscarder(logRotator(numToKeepStr: '20'))
    timestamps()
  }
  stages {
    stage('Install') {
      steps {
        bat 'python -m pip install -U pip'
        bat 'pip install -r requirements.txt'
      }
    }
    stage('Test') {
      steps {
        bat 'pytest -n 4 -q --maxfail=1 --durations=10'
      }
      post {
        always {
          archiveArtifacts artifacts: 'logs/**, reports/**, **/chromedriver_*.log', fingerprint: true
          bat 'call cleanup.bat'
        }
      }
    }
  }
}

Détection d’épuisement de ports (PowerShell)

$tw = Get-NetTCPConnection -State TimeWait
$est = Get-NetTCPConnection -State Established
"TIME_WAIT: {0}  |  Established: {1}" -f ($tw.Count), ($est.Count)

Observabilité et preuves de rétablissement

  • Journaux ChromeDriver verbeux + captation des logs du pipeline (archiver en artefacts avec horodatage).
  • WER (Windows Error Reporting) LocalDumps pour chromedriver.exe afin d’obtenir un minidump quand le crash survient :
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\chromedriver.exe" /v DumpType /t REG_DWORD /d 2 /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\chromedriver.exe" /v DumpCount /t REG_DWORD /d 10 /f
reg add "HKLM\SOFTWARE\Microsoft\Windows\Windows Error Reporting\LocalDumps\chromedriver.exe" /v DumpFolder /t REG_EXPAND_SZ /d "C:\Dumps" /f
mkdir C:\Dumps

Corrélez la baisse des erreurs WinError 10061 avec le réglage des ports et la réduction du parallélisme pour prouver la stabilisation.

Bonnes pratiques complémentaires

  • Journaliser création/terminaison des sessions. Ajoutez un identifiant de test au nom du fichier log afin d’isoler rapidement l’origine des fuites.
  • Retries ciblés. Appliquez 2–3 tentatives sur la création du WebDriver uniquement (backoff progressif). Évitez les retries aveugles sur toutes les actions, qui masquent les vrais problèmes.
  • Mises à jour régulières. Gardez Selenium Python à jour et retestez sur une branche « canari » avant de promouvoir.
  • Chrome « headless » moderne. Privilégiez --headless=new, plus proche du rendu réel.
  • Politique de profil. Un profil propre par test évite les « profile already in use » qui dégénèrent en crashs.
  • Nettoyage post-build. Script systématique taskkill /IM chromedriver.exe /F et purge des profils temporaires.

Débogage ciblé quand ça coince encore

  1. Valider qu’un port écoute. Lors d’un crash, vérifiez si chromedriver.exe écoute bien sur 127.0.0.1:PORT (via netstat -ano), puis corrélez avec l’heure du test en échec.
  2. Comparer le comportement en séquentiel. Si 1 test à la fois passe 100% du temps, le problème vient quasi sûr du parallélisme (ressources/ports).
  3. Tester une exécution sans antivirus/EDR. Sur un hôte clone, désactivez temporairement la protection pour valider l’hypothèse d’interférence (puis rétablissez-la avec exclusions fines).
  4. Vérifier l’intégrité du binaire. Supprimez le cache des drivers et redémarrez l’agent : un exécutable corrompu peut provoquer 0xc0000005.
  5. Inspecter les minidumps. Si C:\Dumps contient des dumps ChromeDriver récents, signalez le module fautif le plus fréquent (ex. DLL d’EDR, pilote graphique) et mettez-le à jour.

Architecture alternative recommandée pour gros volumes

Au-delà de ~8–12 navigateurs par hôte Windows généraliste, migrez vers un Selenium Grid Linux scalable :

  • Des nœuds conteneurisés (Docker) offrant un isolement fort des ports et de la mémoire.
  • Un orchestrateur (EC2 Auto Scaling ou Kubernetes) pour élasticité selon la charge.
  • Collecte standardisée des logs et vidéos (optionnel) par conteneur.

FAQ rapide

Q : Pourquoi limiter le parallélisme si l’instance a beaucoup de CPU ?
R : Les ports éphémères et TIME_WAIT deviennent des goulots d’étranglement même sur des hôtes puissants. De plus, Chrome consomme des pics mémoires imprévisibles.

Q : Faut-il lancer Chrome en non-headless pour plus de stabilité ?
R : Non. Le mode --headless=new est stable et plus simple à exploiter en CI, surtout sans GPU.

Q : Les erreurs WinError 10061 ont disparu en diminuant -n, dois-je faire autre chose ?
R : Oui : étendez la plage de ports, réglez TcpTimedWaitDelay, isolez les profils et mettez en place le nettoyage. Vous pourrez ensuite remonter le parallélisme en sécurité.

Récapitulatif exécutif

  • Quick wins : parité Chrome/ChromeDriver, port=0, -n 4 puis ramp-up, profil unique par test, exclusions antivirus, nettoyage systématique.
  • Renfort système : plage de ports dynamique plus large, TcpTimedWaitDelay réduit, surveillance CPU/RAM/TCP, page file automatique.
  • Montée en charge : conteneurisation et/ou Selenium Grid sur Linux pour isoler les ressources quand le volume de tests croît.

En appliquant d’abord la mise à niveau Chrome/ChromeDriver et la réduction du parallélisme, la majorité des équipes voient disparaître les erreurs WinError 10061. Les optimisations réseau et système (plage de ports, mémoire, antivirus) complètent la stabilisation à mesure que la volumétrie augmente.


Annexes utiles

Matrix « Symptômes → Actions »

SymptômeDiagnostic rapideActions prioritaires
WinError 10061 dès la création du driverChromeDriver n’écoute pas ou port indisponibleService(port=0), étendre ports éphémères, réduire -n, retries à la création
Crash 0xc0000005 aléatoire à mi-testConflit extension/antivirus/ressourcesExclusions AV/EDR, --disable-extensions, surveillance RAM, maj driver
Forte variabilité de duréeCPU en surchauffe, IO élevésLimiter parallélisme, calibrer vCPU/Go/test, upgrade instance
Ports en TIME_WAIT qui explosentNetstat montre > des milliers de TIME_WAITRégler TcpTimedWaitDelay, étendre plage, cleanup post-build

Règles de pare-feu Windows (si verrouillages stricts)

Les connexions sont locales (127.0.0.1) et ne devraient pas être bloquées. Si votre politique est extrême, autorisez explicitement chromedriver.exe en inbound/outbound sur le profil « Domaine ».

New-NetFirewallRule -DisplayName "Allow ChromeDriver Loopback" `
  -Direction Inbound -Program "C:\Tools\selenium\drivers\chromedriver.exe" `
  -Action Allow -Profile Domain,Private,Public

Durcissement de la fiabilité Pytest

# pytest.ini

[pytest]

addopts = -q –maxfail=1 –durations=10 # Envisagez pytest-rerunfailures pour la création de driver UNIQUEMENT # exemple: @pytest.mark.flaky(reruns=2, reruns_delay=1)

Conseils de profilage

  • Activez Background Intelligent Transfer Service (BITS) uniquement si nécessaire ; il peut injecter des IO inattendus.
  • Désactivez les extensions et « policies » d’entreprise dans Chrome pendant les tests.
  • Vérifiez que l’agent Jenkins n’exécute pas d’autres outils gourmands (antivirus scans planifiés, sauvegardes).

Conclusion : la combinaison « versions assorties » + « ports bien configurés » + « parallélisme maîtrisé » + « cleanup strict » suffit à faire disparaître la quasi-totalité des WinError 10061 et des crashs 0xc0000005 sur Windows Server (AWS). Au-delà, l’industrialisation via Grid/Docker scelle définitivement la stabilité à grande échelle.

Sommaire