Esecuzione batch Selenium su AWS Windows: come risolvere WinError 10061 e crash di ChromeDriver

Batch di test Selenium su Windows Server in AWS che falliscono con “MaxRetryError… [WinError 10061]” e crash di chromedriver.exe? Qui trovi una guida completa per riprodurre, diagnosticare e risolvere in modo stabile, con esempi di configurazione per pytest/Jenkins, ottimizzazioni di rete/OS e strategie di contenimento.

Indice

Panoramica e contesto

Singolarmente i test passano, ma quando partono in batch (20–30+ esecuzioni via pytest o pipeline Jenkins) compaiono errori intermittenti come:

MaxRetryError: HTTPConnectionPool(host='localhost', port=#####)...
NewConnectionError...
[WinError 10061] No connection could be made because the target machine actively refused it

In parallelo, il registro eventi di Windows segnala crash di chromedriver.exe con eccezione 0xc0000005 (access violation). L’ambiente tipico è Windows Server su AWS con Google Chrome e ChromeDriver (es. 123.0.6312.59), orchestrati da pytest e/o Jenkins con pytest-xdist per il parallelismo.

Perché succede davvero

WinError 10061 significa che qualcosa sulla macchina di test sta rifiutando una connessione TCP in loopback verso il servizio HTTP esposto da ChromeDriver. Selenium crea una sessione WebDriver contattando http://localhost:<porta-driver>; se il processo del driver non è in ascolto, è occupato, è crashato o la porta è già in uso, la connessione fallisce e la libreria urllib3 (usata da Selenium) lancia quel messaggio. In batch, i fattori più comuni sono:

  • Mismatch di versione o bug regressivi tra Chrome e ChromeDriver.
  • Esaurimento di risorse locali (porte effimere, CPU, RAM, handle, GDI, I/O su disco) quando troppi driver partono insieme.
  • Gestione imperfetta del ciclo di vita delle sessioni (driver non chiusi ⇒ processi zombie che occupano porte e memoria).
  • Limiti dell’ambiente CI (parallelismo eccessivo, antivirus/firewall/EDR che ispezionano processi e socket e introducono latenza o blocchi).

Come si manifesta

SegnaleChe cosa indicaPerché è rilevante
WinError 10061 su localhostIl server ChromeDriver sulla porta attesa non risponde o rifiutaPorta occupata, driver crashato, istanza non ancora pronta
Crash chromedriver.exe 0xc0000005Access violation nel processo del driverSpesso dovuto a mismatch binario, hook antivirus o memoria sotto pressione
Test singoli ok, batch instabileCondizione legata a concorrenza e risorseParallelismo, esaurimento porte effimere o TIME_WAIT

Verifiche rapide prima di intervenire

  • Controlla versioni: Chrome e ChromeDriver sono esattamente allineati? Se usi Selenium 4.6+, valuta Selenium Manager per il provisioning automatico del driver.
  • Controlla zombie: dopo un batch fallito, restano processi chrome.exe o chromedriver.exe in esecuzione?
  • Controlla porte: quante connessioni in TIME_WAIT e quante porte effimere disponibili?
  • Controlla risorse: CPU/RAM/IO saturi in CloudWatch o PerfMon durante i picchi?
  • Controlla sicurezza: antivirus/EDR/Defender scansionano la cartella del driver o iniettano hook nel processo?

Cause probabili e come provarle

CategoriaDettaglioSegnali di confermaComandi utili
Incompatibilità/bugChromeDriver non corrisponde alla versione di Chrome o build difettosaCrash immediati all’avvio, log driver con errori internichromedriver --version, chrome --version
Esaurimento porteMolti avvii paralleli esauriscono il pool TCP effimeroMolte connessioni in TIME_WAIT; errori 10061 sporadicinetsh int ipv4 show dynamicportrange tcp, Get-NetTCPConnection
CPU/RAM insufficientiPressione memoria o contesa CPU durante picchiSpike CPU 90%+, paging, crash non deterministiciPerfMon/CloudWatch su CPU, Memory, Disk
Sessioni non chiuseDriver rimasti attivi occupano porte e handleProcessi residui dopo i testtasklist /fi "imagename eq chromedriver.exe"
Antivirus/FirewallIspezione aggressiva di processi e socket localiCrash o latenze quando si avvia ChromeEsclusioni Defender, regole firewall locali

Strategia di risoluzione consigliata

Applica gli step nell’ordine seguente. Ognuno mitiga una classe specifica di cause e, nel complesso, stabilizza l’intero sistema.

Allineare e rendere riproducibili le versioni

  • Aggiorna Chrome e ChromeDriver alla stessa major/minor/patch. Se noti una regressione, prova la coppia immediatamente precedente nota come stabile.
  • Preferisci un meccanismo di provisioning automatico:
    • Selenium Manager (Selenium ≥ 4.6): nessuna dipendenza esterna, risolve la versione corretta al volo.
    • WebDriverManager: gestione esplicita via codice con caching locale.
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service

Selenium Manager: niente executable_path, Service(port=0) per porta libera

options = Options()
options.add_argument("--headless=new")
service = Service(port=0, log_output="chromedriver.log")  # log per diagnostica

driver = webdriver.Chrome(service=service, options=options)
try:
driver.get("[https://example.org](https://example.org)")
finally:
driver.quit() 
# Variante con WebDriverManager (se preferisci controllo esplicito)
from webdriver_manager.chrome import ChromeDriverManager

service = Service(ChromeDriverManager().install(), port=0)
driver = webdriver.Chrome(service=service, options=options)

Ridurre e gestire il parallelismo

Troppi worker simultanei amplificano contesa su CPU, RAM e porte locali. Parti conservativo e scala in modo empirico.

  • pytest-xdist: limita i worker ai core fisici (o meno). Evita -n auto su istanze piccole.
  • Batch più piccoli: spezza la suite in gruppi di test omogenei (per durata/funzionalità).
  • Jenkins: usa throttle e stage sequenziali per fasi critiche (bootstrap browser).
# pytest.ini

[pytest]

addopts = -q –maxfail=1 –dist=loadscope Usa xdist a riga di comando: pytest -n 2

// Jenkinsfile (estratto)
pipeline {
  agent any
  options { disableConcurrentBuilds() }
  stages {
    stage('Tests batch 1') { steps { sh 'pytest -n 2 -m "not heavy"' } }
    stage('Tests batch 2') { steps { sh 'pytest -n 2 -m heavy' } }
  }
}

Chiudere correttamente le sessioni e ripulire i processi

Ogni test deve garantire quit() anche in caso di eccezione. Proteggi il teardown e aggiungi una “scopa” finale.

import pytest
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options

@pytest.fixture
def driver():
opts = Options()
opts.add_argument("--headless=new")
service = Service(port=0, log_output="chromedriver.log")
d = webdriver.Chrome(service=service, options=opts)
try:
yield d
finally:
try:
d.quit()
except Exception:
pass

@pytest.fixture(autouse=True, scope="session")
def kill_orphans():
Pulizia sessione: chiude processi residui al termine dell'intera suite
import subprocess
yield
for name in ("chromedriver.exe", "chrome.exe"):
subprocess.run(["taskkill", "/F", "/IM", name], shell=True) 

Aumentare il pool di porte e mitigare TIME_WAIT

ChromeDriver ascolta su una porta locale. Quando molti processi vengono creati in rapida sequenza, il sistema può esaurire le porte effimere o restare a lungo in TIME_WAIT. Due mosse chiave:

  1. Lascia scegliere la porta: Service(port=0) fa selezionare una porta libera al sistema, riducendo collisioni.
  2. Verifica/espandi la gamma effimera e monitora lo stato TCP.
# Mostra la gamma corrente
netsh int ipv4 show dynamicportrange tcp

Imposta gamma standard ampia (49152–65535 = 16384 porte)

netsh int ipv4 set dynamicportrange tcp start=49152 num=16384 
# Quante connessioni in TIME_WAIT?
Get-NetTCPConnection -State TimeWait | Measure-Object

Porte locali in uso da chromedriver
Get-NetTCPConnection | Where-Object { $_.OwningProcess -in (Get-Process chromedriver).Id } |
  Select-Object LocalPort, RemotePort, State

Opzionale e con cautela: riduci il tempo di TIME_WAIT su ambienti di test isolati.

# NON in produzione senza change approvato
New-ItemProperty -Path "HKLM:\SYSTEM\CurrentControlSet\Services\Tcpip\Parameters" `
  -Name "TcpTimedWaitDelay" -PropertyType DWord -Value 30 -Force

Ottimizzare Chrome in headless

Headless moderno (--headless=new) è più stabile in CI e consuma meno risorse. Usa anche flag che riducono l’impatto su GPU e IPC.

opts = Options()
opts.add_argument("--headless=new")
opts.add_argument("--disable-gpu")
opts.add_argument("--no-sandbox")
opts.add_argument("--disable-dev-shm-usage")
opts.add_argument("--disable-background-networking")
opts.add_argument("--disable-extensions")
opts.add_argument("--window-size=1920,1080")
driver = webdriver.Chrome(options=opts, service=Service(port=0))

Dimensionare correttamente l’istanza AWS e osservare i colli di bottiglia

  • CPU: puntare a < 70% medio durante il batch; se superi 85–90% stabile, riduci -n o passa a più vCPU.
  • RAM: considera ~300–600 MB per istanza headless, ma prevedi picchi; se inizi a fare paging, i crash aumentano.
  • Disco/I/O: log intensivi su volumi lenti causano latenza; ruota e comprimi i log.
# PerfMon rapido: alcuni contatori utili
logman create counter "SeleniumBatch" -c `
  "\Processor(_Total)\% Processor Time" `
  "\Memory\Available MBytes" `
  "\TCPv4\Connections Established" `
  "\Process(chromedriver)\Handle Count" `
  -si 00:00:05 -o C:\PerfLogs\SeleniumBatch.blg -f bin -v mmddhhmm -max 200
logman start SeleniumBatch

Controlli di sicurezza e impatto su stabilità

Antivirus/EDR possono iniettare hook o bloccare aperture di socket locali. Per l’agent CI dedicato:

  • Aggiungi esclusioni per la cartella dei driver e del workspace.
  • Disabilita temporaneamente il firewall locale per un test A/B (su ambienti non esposti), poi ripristina una regola granulare.
# Esclusioni Windows Defender (solo su VM di test isolata)
Add-MpPreference -ExclusionPath "C:\SeleniumDrivers"
Add-MpPreference -ExclusionPath "C:\Jenkins\workspace"

Esempio regola firewall per loopback ChromeDriver

netsh advfirewall firewall add rule name="Allow ChromeDriver Loopback" `
dir=in action=allow protocol=TCP localport=49152-65535 localip=127.0.0.1 

Container o Selenium Grid per massimizzare l’isolamento

Se la stabilità è un requisito forte o se devi scalare oltre le risorse di una singola VM, sposta l’esecuzione in container dedicati o in un Grid con nodi Chrome isolati.

# docker-compose.yml (esempio minimo)
services:
  chrome:
    image: selenium/standalone-chrome:latest
    shm_size: 2g
    ports: ["4444:4444"]
# Esecuzione remota su Grid/standalone
from selenium.webdriver import Remote
from selenium.webdriver.chrome.options import Options

opts = Options()
opts.add_argument("--headless=new")
driver = Remote(command_executor="[http://localhost:4444/wd/hub](http://localhost:4444/wd/hub)", options=opts) 

Playbook di diagnostica passo–passo

Abilita log dettagliati di ChromeDriver

service = Service(port=0, log_output="chromedriver.log")
oppure avvia chromedriver con flag espliciti:
chromedriver --verbose --log-path=chromedriver.log
  • Cosa cercare: “Started ChromeDriver” con porta assegnata, tempi di handshake, errori interni, eccezioni.

Cattura dump del crash per 0xc0000005

Abilita i LocalDumps per chromedriver.exe solo in ambienti di test, per raccogliere evidenze.

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 DumpFolder /t REGEXPANDSZ /d "C:\Dumps" /f

In alternativa, usa procdump per cattura mirata:

procdump -e -ma -x C:\Dumps -w chromedriver.exe

Monitora il consumo di porte e lo stato TCP

# Conteggio porte in uso nella gamma effimera
$range = netsh int ipv4 show dynamicportrange tcp
...parsa $range se vuoi automatizzare un alert

Top processi per connessioni

Get-NetTCPConnection | Group-Object OwningProcess | `
Sort-Object Count -Descending | Select-Object -First 10 

Rileva e chiudi zombie

Get-Process chromedriver, chrome -ErrorAction SilentlyContinue |
  Where-Object { $.CPU -eq $null -or $.Responding -eq $false } |
  Stop-Process -Force

Fixture e configurazioni “a prova di CI”

Fixture completa con timeouts e stabilizzatori

import pytest, os
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service

@pytest.fixture(scope="function")
def driver(tmp_path):
opts = Options()
opts.add_argument("--headless=new")
opts.add_argument("--disable-gpu")
opts.add_argument("--no-sandbox")
opts.add_argument("--disable-dev-shm-usage")
opts.add_argument("--disable-extensions")
opts.add_argument("--window-size=1920,1080")
Stabilizza il caricamento
opts.pageloadstrategy = "eager"
```
log_file = os.path.join(tmp_path, "chromedriver.log")
service = Service(port=0, log_output=log_file)
d = webdriver.Chrome(service=service, options=opts)

# Timeouts sensati in CI
d.set_page_load_timeout(60)
d.implicitly_wait(3)
d.set_script_timeout(30)
try:
    yield d
finally:
    try:
        d.quit()
    finally:
        # Rotazione log su workspace
        pass
```

Partizionamento della suite per ridurre la pressione

# pytest.ini (markers e limiti)

[pytest]

markers = smoke: test rapidi di disponibilità heavy: test lunghi o ad alto consumo addopts = -q –dist=loadscope –maxfail=1

# Esempi di invocazione
pytest -m "smoke and not heavy" -n 2
pytest -m "heavy" -n 1

Checklist operativa

AttivitàObiettivoComando/Nota
Allinea Chrome/ChromeDriverElimina crash da mismatchSelenium Manager o WebDriverManager
Service(port=0)Evita collisioni di portaInizializzazione WebDriver
Limita parallelismoRiduce contesa e TIME_WAITpytest -n 1..N calibrato su vCPU
Headless + flagMinori risorse e più stabilità--headless=new --disable-gpu
Espandi gamma effimeraPiù porte disponibilinetsh int ipv4 set dynamicportrange
Esclusioni antivirusMeno hook e blocchiAdd-MpPreference -ExclusionPath ...
Log driver + dump WERRoot cause dei crash--verbose --log-path, LocalDumps
Cleanup zombiePorte/memoria liberatetaskkill su chrome*
OsservabilitàEvita diagnosi alla ciecaPerfMon/CloudWatch

Domande frequenti

Perché in locale funziona e in Jenkins no?

In locale l’avvio è seriale e con più risorse per processo. In CI partono N driver contemporaneamente, accentuando contesa su porte e CPU; inoltre agent e policy di sicurezza differiscono.

Headless è obbligatorio?

No, ma in CI è fortemente consigliato per ridurre consumo di risorse ed eliminare classi di crash legati all’accelerazione grafica. Se devi eseguire con UI, mantieni -n basso.

Posso aumentare i timeout per “nascondere” il problema?

Timeout più generosi aiutano con latenze transitorie ma non risolvono crash. Concentrati su versione, parallelismo, porte e risorse.

Cos’è l’eccezione 0xc0000005?

Access violation: il processo ha tentato di leggere/scrivere memoria non valida. Spesso correlata a fattori esterni (hook antivirus/EDR), mismatch binari o condizioni di OOM/contesa.

Approccio consigliato “prima il valore”

  1. Stabilizza la piattaforma: allinea versioni, usa Service(port=0), headless e limiti ragionevoli di -n.
  2. Misura: attiva PerfMon e log di ChromeDriver; verifica gamma porte e TIME_WAIT.
  3. Rimuovi interferenze: esclusioni antivirus e regole firewall locali per il loopback.
  4. Scala: se serve, distribuisci i test su Grid o container con isolamento forte.

Appendice: esempio completo di progetto di test

Struttura minima orientata alla stabilità.

.
├─ tests/
│  ├─ conftest.py
│  ├─ test_smoke.py
│  └─ test_heavy.py
├─ pytest.ini
├─ requirements.txt
└─ Jenkinsfile
# tests/conftest.py
import os, pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service

@pytest.fixture(scope="session", autouse=True)
def envsetup(tmppath_factory):
os.environ.setdefault("PYTHONUNBUFFERED", "1")
logs = tmppathfactory.mktemp("logs")
os.environ["RUNLOGDIR"] = str(logs)
yield

@pytest.fixture
def driver():
opts = Options()
opts.add_argument("--headless=new")
opts.add_argument("--disable-gpu")
opts.add_argument("--no-sandbox")
opts.add_argument("--disable-dev-shm-usage")
opts.pageloadstrategy = "eager"
service = Service(port=0, logoutput=os.path.join(os.environ["RUNLOG_DIR"], "chromedriver.log"))
d = webdriver.Chrome(service=service, options=opts)
d.setpageload_timeout(60)
d.implicitly_wait(3)
try:
yield d
finally:
try: d.quit()
except: pass 
# pytest.ini

[pytest]

addopts = -q –dist=loadscope –maxfail=1 markers = smoke: test rapidi heavy: test pesanti

// Jenkinsfile
pipeline {
  agent { label 'windows' }
  options { buildDiscarder(logRotator(numToKeepStr: '10')); disableConcurrentBuilds() }
  stages {
    stage('Install') {
      steps {
        bat 'python -m pip install -U pip'
        bat 'pip install -r requirements.txt'
      }
    }
    stage('Tests - smoke') {
      steps { bat 'pytest -m smoke -n 2' }
    }
    stage('Tests - heavy') {
      steps { bat 'pytest -m heavy -n 1' }
    }
  }
  post {
    always { archiveArtifacts artifacts: '/logs/*.log', allowEmptyArchive: true }
  }
}

Conclusioni

WinError 10061 è un effetto: la porta attesa non accetta connessioni perché il servizio ChromeDriver non è pronto, è crashato o non può aprire una nuova porta. Agendo su versioni, parallelismo, chiusura corretta delle sessioni, gamma porte/timeout, headless e policy di sicurezza, la stragrande maggioranza dei casi analoghi viene risolta in modo definitivo. Se dopo questi interventi il problema persiste, attiva verbose logging e dump del processo, raccogli i log e apri una segnalazione sul tracker di ChromeDriver allegando evidenze (chromedriver.log, minidump e informazioni di versione).


Nota operativa finale: in ambienti Windows Server su AWS è spesso più efficace perseguire due direzioni in parallelo: riduzione del parallelismo immediata per tornare verdi (time-to-green), seguita da ottimizzazioni strutturali (porta 0, osservabilità, esclusioni AV, Grid/containers) per sostenere la crescita futura della suite.


Il messaggio “[WinError 10061]” è un sintomo, non la causa. Risolvi la radice: versioni compatibili e risorse ben gestite.

Riepilogo azionabile

  • Usa Service(port=0) e --headless=new.
  • Allinea Chrome/ChromeDriver e, se necessario, fai rollback di una coppia.
  • Limita -n a 1–2 per vCPU; spezza la suite.
  • Espandi/verifica la gamma di porte effimere; monitora TIME_WAIT.
  • Chiudi driver sempre; rimuovi zombie al termine.
  • Aggiungi esclusioni antivirus e regole firewall per loopback.
  • Se la scalabilità è prioritaria: Grid o container dedicati.

Se adotti gli step sopra nell’ordine proposto, vedrai la riduzione degli errori intermittenti e l’eliminazione dei crash di chromedriver.exe nella quasi totalità dei contesti CI su Windows Server in AWS.

Contenuti di riferimento pratico (copincolla rapido)

# Gamma porte effimere
netsh int ipv4 show dynamicportrange tcp
netsh int ipv4 set dynamicportrange tcp start=49152 num=16384

Monitor TIME_WAIT

Get-NetTCPConnection -State TimeWait | Measure-Object

Esclusioni Defender

Add-MpPreference -ExclusionPath "C:\SeleniumDrivers"
Add-MpPreference -ExclusionPath "C:\Jenkins\workspace"

Firewall loopback (se necessario)

netsh advfirewall firewall add rule name="Allow ChromeDriver Loopback" dir=in action=allow protocol=TCP localport=49152-65535 localip=127.0.0.1

Kill orfani

taskkill /F /IM chromedriver.exe
taskkill /F /IM chrome.exe 

In sintesi: stabilità = versioni allineate + parallelismo calibrato + gestione porte/timeouts + osservabilità + riduzione interferenze di sicurezza. Il resto (Grid, container, tuning avanzato TCP) è moltiplicatore di affidabilità quando la suite cresce.

Indice