@@ -860,6 +860,58 @@ describe('profiler', () => {
860860 expect ( profilingContextManager . get ( ) ?. status ) . toBe ( 'stopped' )
861861 } )
862862
863+ it ( 'should not include long tasks outside the profiling window when clocks drift' , async ( ) => {
864+ const clock = mockClock ( )
865+ const timeOrigin = performance . timing . navigationStart
866+ const { profiler, addLongTask } = setupProfiler ( )
867+
868+ profiler . start ( )
869+ expect ( profiler . isRunning ( ) ) . toBe ( true )
870+
871+ // Add a long task at T=100ms (inside the profile window)
872+ clock . tick ( 100 )
873+ addLongTask ( {
874+ id : 'long-task-inside' ,
875+ startClocks : clocksNow ( ) ,
876+ duration : 50 as Duration ,
877+ entryType : RumPerformanceEntryType . LONG_ANIMATION_FRAME ,
878+ } )
879+
880+ // Add a long task at T=1000ms (outside the actual profile relative window)
881+ clock . tick ( 900 )
882+ addLongTask ( {
883+ id : 'long-task-outside' ,
884+ startClocks : clocksNow ( ) ,
885+ duration : 50 as Duration ,
886+ entryType : RumPerformanceEntryType . LONG_ANIMATION_FRAME ,
887+ } )
888+
889+ // Advance to T=1100ms
890+ clock . tick ( 100 )
891+
892+ // Simulate clock drift: Date.now() drifted 1000ms ahead of performance.now()
893+ // This mimics NTP sync or system clock adjustments in production
894+ ; ( performance . now as jasmine . Spy ) . and . callFake ( ( ) => Date . now ( ) - timeOrigin - 1000 )
895+
896+ // Stop profiler — state changes synchronously, data collection is async via Promise
897+ profiler . stop ( )
898+ expect ( profiler . isStopped ( ) ) . toBe ( true )
899+
900+ // Flush microtasks for profiler.stop() Promise and transport.send()
901+ await waitNextMicrotask ( )
902+ await waitNextMicrotask ( )
903+
904+ expect ( interceptor . requests . length ) . toBe ( 1 )
905+ const request = await readFormDataRequest < ProfileEventPayload > ( interceptor . requests [ 0 ] )
906+ const trace = request [ 'wall-time.json' ]
907+
908+ // Should only include the long task that occurred during the actual profiling window.
909+ // Without the fix (using timeStamp for duration), both long tasks would be included
910+ // because the inflated timeStamp-based duration extends the query window.
911+ expect ( trace . longTasks . length ) . toBe ( 1 )
912+ expect ( trace . longTasks [ 0 ] . id ) . toBe ( 'long-task-inside' )
913+ } )
914+
863915 it ( 'should restart profiling when session expires while paused and then renews' , async ( ) => {
864916 const { profiler, profilingContextManager } = setupProfiler ( )
865917
0 commit comments