@@ -121,6 +121,8 @@ def managed_webdriver_session(chrome_options: Options, debug_logger: DebugLogger
121121 debug_logger .log ("WebDriver quit: START" )
122122 try :
123123 driver .quit ()
124+ except Exception as e :
125+ debug_logger .log (f"Ignoring error during driver.quit(): { e } " )
124126 finally :
125127 debug_logger .log ("WebDriver quit: END" )
126128 if service and getattr (service , "process" , None ):
@@ -167,6 +169,10 @@ class SpeedResults(TypedDict, total=False):
167169 local_downstream_speed : float
168170 local_upstream_speed : float
169171 local_speedtest_jitter : float
172+ # Bufferbloat / under-load metrics from local Ookla CLI JSON
173+ local_latency_down_load_ms : float
174+ local_latency_up_load_ms : float
175+ local_packet_loss_pct : float
170176
171177
172178class WifiDiagnostics (TypedDict , total = False ):
@@ -266,6 +272,13 @@ def log_results(all_data: Mapping[str, str | float | int | None]) -> None:
266272 "Local_Downstream_Mbps" : all_data .get ("local_downstream_speed" ),
267273 "Local_Upstream_Mbps" : all_data .get ("local_upstream_speed" ),
268274 "Local_Speedtest_Jitter_ms" : all_data .get ("local_speedtest_jitter" ),
275+ # Calculated bufferbloat deltas
276+ "Download_Bufferbloat_ms" : all_data .get ("download_bufferbloat_ms" ),
277+ "Upload_Bufferbloat_ms" : all_data .get ("upload_bufferbloat_ms" ),
278+ # New bufferbloat metrics
279+ "Local_Load_Down_ms" : all_data .get ("local_latency_down_load_ms" ),
280+ "Local_Load_Up_ms" : all_data .get ("local_latency_up_load_ms" ),
281+ "Local_Pkt_Loss_Pct" : all_data .get ("local_packet_loss_pct" ),
269282 "WiFi_BSSID" : all_data .get ("wifi_bssid" , "N/A" ),
270283 "WiFi_Channel" : all_data .get ("wifi_channel" , "N/A" ),
271284 "WiFi_RSSI" : all_data .get ("wifi_rssi" , "N/A" ),
@@ -275,7 +288,7 @@ def log_results(all_data: Mapping[str, str | float | int | None]) -> None:
275288
276289 # --- CSV Logging ---
277290 csv_values = [
278- f"{ v :.3f} " if isinstance (v , float ) else ( "N/A" if v is None else v )
291+ f"{ v :.3f} " if isinstance (v , float ) else "N/A" if v is None else str ( v )
279292 for v in data_points .values ()
280293 ]
281294 header = "Timestamp," + "," .join (data_points .keys ()) + "\n "
@@ -402,6 +415,39 @@ def format_value(
402415 )
403416 print (f" Speedtest Jitter: { speed_jitter } " )
404417
418+ # Bufferbloat deltas (idle -> under-load)
419+ down_bloat = format_value (
420+ data_points ["Download_Bufferbloat_ms" ],
421+ "ms" ,
422+ config .BUFFERBLOAT_DELTA_THRESHOLD ,
423+ precision = 2 ,
424+ )
425+ print (f" Download Bufferbloat: { down_bloat } " )
426+
427+ up_bloat = format_value (
428+ data_points ["Upload_Bufferbloat_ms" ],
429+ "ms" ,
430+ config .BUFFERBLOAT_DELTA_THRESHOLD ,
431+ precision = 2 ,
432+ )
433+ print (f" Upload Bufferbloat: { up_bloat } " )
434+
435+ # New bufferbloat metrics
436+ down_load_latency = format_value (
437+ data_points ["Local_Load_Down_ms" ], "ms" , config .LATENCY_UNDER_LOAD_THRESHOLD
438+ )
439+ print (f" Latency (Download Load): { down_load_latency } " )
440+
441+ up_load_latency = format_value (
442+ data_points ["Local_Load_Up_ms" ], "ms" , config .LATENCY_UNDER_LOAD_THRESHOLD
443+ )
444+ print (f" Latency (Upload Load): { up_load_latency } " )
445+
446+ packet_loss_val = format_value (
447+ data_points ["Local_Pkt_Loss_Pct" ], "%" , config .SPEEDTEST_PACKET_LOSS_THRESHOLD
448+ )
449+ print (f" Speedtest Packet Loss: { packet_loss_val } " )
450+
405451 print ("\n --- Wi-Fi Diagnostics ---" )
406452 print (f" Connected AP (BSSID): { data_points ['WiFi_BSSID' ]} " )
407453 print (f" Signal Strength (RSSI): { data_points ['WiFi_RSSI' ]} " )
@@ -426,7 +472,12 @@ def run_ping_test_task(driver: WebDriver) -> Optional[GatewayPingResults]:
426472 driver .execute_script ("arguments[0].click();" , ping_button )
427473 print (f"Gateway ping test started for { config .PING_TARGET } ." )
428474 print ("Waiting for gateway ping results..." )
429- time .sleep (15 )
475+ wait = WebDriverWait (driver , 30 )
476+ wait .until (
477+ lambda d : "ping statistics" in d .find_element (By .ID , "progress" ).get_attribute ("value" )
478+ )
479+
480+ # Re-find the element after the wait to avoid stale references
430481 results_element = driver .find_element (By .ID , "progress" )
431482 results_text = (results_element .get_attribute ("value" ) or "" ).strip ()
432483 if results_text :
@@ -466,13 +517,14 @@ def run_speed_test_task(driver: WebDriver, access_code: str) -> Optional[SpeedRe
466517 run_button = WebDriverWait (driver , 15 ).until (EC .element_to_be_clickable ((By .NAME , "run" )))
467518 run_button .click ()
468519 print ("Gateway speed test initiated. This will take up to 90 seconds..." )
469- WebDriverWait (driver , 90 ).until (EC .element_to_be_clickable ((By .NAME , "run" )))
520+ # Wait directly for the results table to appear
521+ # instead of relying on the run button's state
522+ print ("Waiting for gateway results table..." )
523+ table_selector = (By .CSS_SELECTOR , "table.grid.table100" )
524+ table = WebDriverWait (driver , 90 ).until (EC .visibility_of_element_located (table_selector ))
470525 print ("Gateway speed test complete. Parsing results..." )
471526
472527 results : SpeedResults = {}
473- table = WebDriverWait (driver , 10 ).until (
474- EC .visibility_of_element_located ((By .CSS_SELECTOR , "table.grid.table100" ))
475- )
476528 rows = table .find_elements (By .TAG_NAME , "tr" )
477529 for row in rows :
478530 cols = row .find_elements (By .TAG_NAME , "td" )
@@ -570,11 +622,19 @@ def run_local_speed_test_task() -> Optional[SpeedResults]:
570622 upload_speed = (results .get ("upload" , {}).get ("bandwidth" , 0 ) * 8 ) / 1_000_000
571623 jitter = results .get ("ping" , {}).get ("jitter" , 0.0 )
572624
625+ # New parsing logic for bufferbloat
626+ latency_down = results .get ("download" , {}).get ("latency" , {}).get ("iqm" , 0.0 )
627+ latency_up = results .get ("upload" , {}).get ("latency" , {}).get ("iqm" , 0.0 )
628+ packet_loss = results .get ("packetLoss" , 0.0 )
629+
573630 print ("Local speed test complete." )
574631 return {
575632 "local_downstream_speed" : download_speed ,
576633 "local_upstream_speed" : upload_speed ,
577634 "local_speedtest_jitter" : jitter ,
635+ "local_latency_down_load_ms" : latency_down ,
636+ "local_latency_up_load_ms" : latency_up ,
637+ "local_packet_loss_pct" : packet_loss ,
578638 }
579639
580640 except subprocess .CalledProcessError as e :
@@ -765,6 +825,22 @@ def perform_checks() -> None:
765825 # This 'else' block will run if the context manager yields None
766826 print ("Skipping gateway tests because WebDriver session failed to start." )
767827
828+ # --- Bufferbloat calculation (download/upload deltas relative to idle WAN RTT) ---
829+ idle_latency = master_results .get ("local_wan_rtt_avg_ms" )
830+ down_latency = master_results .get ("local_latency_down_load_ms" )
831+ up_latency = master_results .get ("local_latency_up_load_ms" )
832+
833+ master_results ["download_bufferbloat_ms" ] = (
834+ (down_latency - idle_latency )
835+ if (idle_latency is not None and down_latency is not None )
836+ else None
837+ )
838+ master_results ["upload_bufferbloat_ms" ] = (
839+ (up_latency - idle_latency )
840+ if (idle_latency is not None and up_latency is not None )
841+ else None
842+ )
843+
768844 debug_log .log ("perform_checks: END" )
769845 log_results (master_results )
770846 print ("\n " + "=" * 60 + "\n " )
0 commit comments