@@ -350,3 +350,77 @@ def test_step_defaults(self):
350350 assert step .data .name == ''
351351 assert step .data .line == 0
352352 assert step .step_type == StepType .GHERKIN
353+
354+
355+ class TestBehavexAbsoluteTimestamps :
356+ """When BehaveX records ``start`` / ``stop`` (unix-ms), the parsed
357+ Result/Step must use those timestamps (shifted by ``time_offset``)
358+ rather than synthesising a window relative to ``now()``."""
359+
360+ def test_scenario_uses_real_start_stop_with_offset (self ):
361+ # BehaveX timestamps from an old run (start=10s, stop=10.5s in unix-ms).
362+ scenario_dict = {
363+ 'name' : 'old run' , 'status' : 'passed' , 'duration' : 0.5 ,
364+ 'tags' : [], 'start' : 10_000 , 'stop' : 10_500 ,
365+ }
366+ offset = 1_000_000.0 # shift the whole timeline by 1e6 seconds
367+
368+ result = parse_scenario_from_json (
369+ scenario_dict , 'features/x.feature' , time_offset = offset
370+ )
371+
372+ assert result .execution .start_time == 10.0 + offset
373+ assert result .execution .end_time == 10.5 + offset
374+ assert result .execution .duration == 500
375+
376+ def test_scenario_without_start_stop_falls_back_to_now (self ):
377+ scenario_dict = {
378+ 'name' : 'no times' , 'status' : 'passed' , 'duration' : 0.3 , 'tags' : [],
379+ }
380+ result = parse_scenario_from_json (
381+ scenario_dict , 'features/x.feature' , time_offset = 999.0
382+ )
383+
384+ # No start/stop → behave like the legacy path; offset must NOT be applied.
385+ assert result .execution .duration == 300
386+ assert result .execution .end_time > 1_000_000 # current unix time, not 999
387+ assert abs (result .execution .end_time - result .execution .start_time - 0.3 ) < 0.1
388+
389+ def test_step_uses_real_start_stop_with_offset (self ):
390+ step_dict = {
391+ 'step_type' : 'when' , 'name' : 'press button' , 'line' : 1 ,
392+ 'status' : 'passed' , 'duration' : 0.12 ,
393+ 'start' : 2_000 , 'stop' : 2_120 ,
394+ }
395+ offset = 5_000_000.0
396+ step = parse_step_from_json (step_dict , time_offset = offset )
397+
398+ assert step .execution .start_time == 2.0 + offset
399+ assert step .execution .end_time == 2.12 + offset
400+ assert step .execution .duration == 120
401+
402+ def test_scenarios_preserve_real_ordering_after_offset (self ):
403+ """Three BehaveX scenarios spaced 200 ms apart must remain spaced
404+ 200 ms apart after offsetting (regression for the bug that
405+ collapsed all scenarios onto the same current_time)."""
406+ offset = 1_000_000.0
407+ scenarios = [
408+ {'name' : 'A' , 'status' : 'passed' , 'duration' : 0.1 , 'tags' : [],
409+ 'start' : 0 , 'stop' : 100 },
410+ {'name' : 'B' , 'status' : 'passed' , 'duration' : 0.2 , 'tags' : [],
411+ 'start' : 200 , 'stop' : 400 },
412+ {'name' : 'C' , 'status' : 'passed' , 'duration' : 0.3 , 'tags' : [],
413+ 'start' : 600 , 'stop' : 900 },
414+ ]
415+
416+ results = [
417+ parse_scenario_from_json (sc , 'features/x.feature' , time_offset = offset )
418+ for sc in scenarios
419+ ]
420+
421+ starts = [r .execution .start_time for r in results ]
422+ ends = [r .execution .end_time for r in results ]
423+
424+ assert ends [0 ] < starts [1 ] < ends [1 ] < starts [2 ] < ends [2 ]
425+ assert pytest .approx (starts [1 ] - starts [0 ], abs = 1e-6 ) == 0.2
426+ assert pytest .approx (starts [2 ] - starts [1 ], abs = 1e-6 ) == 0.4
0 commit comments