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.
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
| Segnale | Che cosa indica | Perché è rilevante |
|---|---|---|
WinError 10061 su localhost | Il server ChromeDriver sulla porta attesa non risponde o rifiuta | Porta occupata, driver crashato, istanza non ancora pronta |
Crash chromedriver.exe 0xc0000005 | Access violation nel processo del driver | Spesso dovuto a mismatch binario, hook antivirus o memoria sotto pressione |
| Test singoli ok, batch instabile | Condizione legata a concorrenza e risorse | Parallelismo, 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.exeochromedriver.exein esecuzione? - Controlla porte: quante connessioni in
TIME_WAITe 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
| Categoria | Dettaglio | Segnali di conferma | Comandi utili |
|---|---|---|---|
| Incompatibilità/bug | ChromeDriver non corrisponde alla versione di Chrome o build difettosa | Crash immediati all’avvio, log driver con errori interni | chromedriver --version, chrome --version |
| Esaurimento porte | Molti avvii paralleli esauriscono il pool TCP effimero | Molte connessioni in TIME_WAIT; errori 10061 sporadici | netsh int ipv4 show dynamicportrange tcp, Get-NetTCPConnection |
| CPU/RAM insufficienti | Pressione memoria o contesa CPU durante picchi | Spike CPU 90%+, paging, crash non deterministici | PerfMon/CloudWatch su CPU, Memory, Disk |
| Sessioni non chiuse | Driver rimasti attivi occupano porte e handle | Processi residui dopo i test | tasklist /fi "imagename eq chromedriver.exe" |
| Antivirus/Firewall | Ispezione aggressiva di processi e socket locali | Crash o latenze quando si avvia Chrome | Esclusioni 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 autosu 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:
- Lascia scegliere la porta:
Service(port=0)fa selezionare una porta libera al sistema, riducendo collisioni. - 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
-no 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à | Obiettivo | Comando/Nota |
|---|---|---|
| Allinea Chrome/ChromeDriver | Elimina crash da mismatch | Selenium Manager o WebDriverManager |
| Service(port=0) | Evita collisioni di porta | Inizializzazione WebDriver |
| Limita parallelismo | Riduce contesa e TIME_WAIT | pytest -n 1..N calibrato su vCPU |
| Headless + flag | Minori risorse e più stabilità | --headless=new --disable-gpu |
| Espandi gamma effimera | Più porte disponibili | netsh int ipv4 set dynamicportrange |
| Esclusioni antivirus | Meno hook e blocchi | Add-MpPreference -ExclusionPath ... |
| Log driver + dump WER | Root cause dei crash | --verbose --log-path, LocalDumps |
| Cleanup zombie | Porte/memoria liberate | taskkill su chrome* |
| Osservabilità | Evita diagnosi alla cieca | PerfMon/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”
- Stabilizza la piattaforma: allinea versioni, usa
Service(port=0), headless e limiti ragionevoli di-n. - Misura: attiva PerfMon e log di ChromeDriver; verifica gamma porte e
TIME_WAIT. - Rimuovi interferenze: esclusioni antivirus e regole firewall locali per il loopback.
- 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
-na 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.
