@@ -366,6 +366,7 @@ def before_feature(context: Context, feature: Feature) -> None:
366366 ``_E2E_FLAKY_MAX_ATTEMPTS`` and can be overridden with the
367367 ``E2E_FLAKY_MAX_ATTEMPTS`` environment variable.
368368 """
369+ setattr (feature , _E2E_FEATURE_PERF_START_ATTR , time .perf_counter ())
369370 reset_active_lightspeed_stack_config_basename ()
370371 context .active_lightspeed_stack_config_basename = None
371372 # One real Llama disruption per feature (module-level flag; survives context resets)
@@ -423,3 +424,19 @@ def after_feature(context: Context, feature: Feature) -> None:
423424
424425 _stop_proxy (context , "tunnel_proxy" , "proxy_loop" )
425426 _stop_proxy (context , "interception_proxy" , "interception_proxy_loop" )
427+
428+ start = getattr (feature , _E2E_FEATURE_PERF_START_ATTR , None )
429+ if start is not None :
430+ elapsed_s = time .perf_counter () - start
431+ try :
432+ delattr (feature , _E2E_FEATURE_PERF_START_ATTR )
433+ except AttributeError :
434+ pass
435+ feat_path = getattr (feature , "filename" , "" ) or ""
436+ label = os .path .basename (feat_path ) if feat_path else feature .name
437+ print (f"[e2e feature timing] { elapsed_s :.2f} s { label } " , flush = True )
438+
439+
440+ # Behave captures hook stdout by default; output is only shown in some failure paths.
441+ # Disable capture so feature timing lines always appear on the real console/CI log.
442+ after_feature .capture = False # type: ignore[attr-defined]
0 commit comments