From 05c53f62f879ec9440315e95600bf79fd1e12efc Mon Sep 17 00:00:00 2001 From: Aymeric Mortemousque Date: Fri, 15 Sep 2023 16:13:17 +0200 Subject: [PATCH 01/10] Cleanly stop INP tests --- .../view/viewMetrics/trackInteractionToNextPaint.spec.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.spec.ts index 719e3abc63..4dc9d24008 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackInteractionToNextPaint.spec.ts @@ -37,11 +37,13 @@ describe('trackInteractionToNextPaint', () => { interactionCountStub = subInteractionCount() setupBuilder = setup().beforeBuild(({ lifeCycle, configuration }) => { - ;({ getInteractionToNextPaint } = trackInteractionToNextPaint( + const interactionToNextPaintTracking = trackInteractionToNextPaint( configuration, ViewLoadingType.INITIAL_LOAD, lifeCycle - )) + ) + getInteractionToNextPaint = interactionToNextPaintTracking.getInteractionToNextPaint + return interactionToNextPaintTracking }) }) From eccf7e9d4cb462366b2893e31d3ef262f76636cc Mon Sep 17 00:00:00 2001 From: Aymeric Mortemousque Date: Fri, 15 Sep 2023 16:14:55 +0200 Subject: [PATCH 02/10] Move CLS support check in trackCommonViewMetrics module --- .../viewMetrics/trackCommonViewMetrics.ts | 27 +++++++------------ .../viewMetrics/trackCumulativeLayoutShift.ts | 12 +++++++++ 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackCommonViewMetrics.ts b/packages/rum-core/src/domain/view/viewMetrics/trackCommonViewMetrics.ts index f2c370de70..85050e10d8 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackCommonViewMetrics.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackCommonViewMetrics.ts @@ -1,5 +1,4 @@ import type { ClocksState, Duration, Observable } from '@datadog/browser-core' -import { noop } from '@datadog/browser-core' import type { ViewLoadingType } from '../../../rawRumEvent.types' import type { RumConfiguration } from '../../configuration' import type { LifeCycle } from '../../lifeCycle' @@ -8,7 +7,7 @@ import type { ScrollMetrics } from './trackScrollMetrics' import { computeScrollValues, trackScrollMetrics } from './trackScrollMetrics' import { trackLoadingTime } from './trackLoadingTime' import type { CumulativeLayoutShift } from './trackCumulativeLayoutShift' -import { isLayoutShiftSupported, trackCumulativeLayoutShift } from './trackCumulativeLayoutShift' +import { trackCumulativeLayoutShift } from './trackCumulativeLayoutShift' import type { InteractionToNextPaint } from './trackInteractionToNextPaint' import { trackInteractionToNextPaint } from './trackInteractionToNextPaint' @@ -62,21 +61,15 @@ export function trackCommonViewMetrics( computeScrollValues ) - let stopCLSTracking: () => void - if (isLayoutShiftSupported()) { - commonViewMetrics.cumulativeLayoutShift = { value: 0 } - ;({ stop: stopCLSTracking } = trackCumulativeLayoutShift( - configuration, - lifeCycle, - webVitalTelemetryDebug, - (cumulativeLayoutShift) => { - commonViewMetrics.cumulativeLayoutShift = cumulativeLayoutShift - scheduleViewUpdate() - } - )) - } else { - stopCLSTracking = noop - } + const { stop: stopCLSTracking } = trackCumulativeLayoutShift( + configuration, + lifeCycle, + webVitalTelemetryDebug, + (cumulativeLayoutShift) => { + commonViewMetrics.cumulativeLayoutShift = cumulativeLayoutShift + scheduleViewUpdate() + } + ) const { stop: stopINPTracking, getInteractionToNextPaint } = trackInteractionToNextPaint( configuration, diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.ts b/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.ts index 232e2b888c..dbcf840ba6 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.ts @@ -5,6 +5,7 @@ import { ONE_SECOND, isExperimentalFeatureEnabled, ExperimentalFeature, + noop, } from '@datadog/browser-core' import { isElementNode } from '../../../browser/htmlDomUtils' import type { LifeCycle } from '../../lifeCycle' @@ -43,8 +44,19 @@ export function trackCumulativeLayoutShift( webVitalTelemetryDebug: WebVitalTelemetryDebug, callback: (cumulativeLayoutShift: CumulativeLayoutShift) => void ) { + if (!isLayoutShiftSupported()) { + return { + stop: noop, + } + } + let maxClsValue = 0 + // if no layout shift happen the value should be reported as 0 + callback({ + value: 0, + }) + const window = slidingSessionWindow() let clsAttributionCollected = false From 2a26082bc9b3b22d71f0d874a43bec5257932986 Mon Sep 17 00:00:00 2001 From: Aymeric Mortemousque Date: Fri, 15 Sep 2023 16:15:25 +0200 Subject: [PATCH 03/10] Scope trackCumulativeLayoutShift tests --- .../trackCumulativeLayoutShift.spec.ts | 83 ++++++++----------- 1 file changed, 35 insertions(+), 48 deletions(-) diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.spec.ts index b93d0d3fbf..7a6922108d 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.spec.ts @@ -1,29 +1,27 @@ -import { ExperimentalFeature, addExperimentalFeatures, resetExperimentalFeatures } from '@datadog/browser-core' +import { ExperimentalFeature, addExperimentalFeatures, noop, resetExperimentalFeatures } from '@datadog/browser-core' import type { TestSetupBuilder } from '../../../../test' import { appendElement, appendTextNode, createPerformanceEntry, setup } from '../../../../test' import { LifeCycleEventType } from '../../lifeCycle' -import { THROTTLE_VIEW_UPDATE_PERIOD } from '../trackViews' -import type { ViewTest } from '../setupViewTest.specHelper' -import { setupViewTest } from '../setupViewTest.specHelper' import { RumPerformanceEntryType } from '../../../browser/performanceCollection' +import type { CumulativeLayoutShift } from './trackCumulativeLayoutShift' +import { trackCumulativeLayoutShift } from './trackCumulativeLayoutShift' describe('trackCumulativeLayoutShift', () => { let setupBuilder: TestSetupBuilder - let viewTest: ViewTest let isLayoutShiftSupported: boolean let originalSupportedEntryTypes: PropertyDescriptor | undefined + let clsCallback: jasmine.Spy<(csl: CumulativeLayoutShift) => void> beforeEach(() => { if (!('PerformanceObserver' in window) || !('supportedEntryTypes' in PerformanceObserver)) { pending('No PerformanceObserver support') } - setupBuilder = setup() - .withFakeLocation('/foo') - .beforeBuild((buildContext) => { - viewTest = setupViewTest(buildContext) - return viewTest - }) + clsCallback = jasmine.createSpy() + setupBuilder = setup().beforeBuild(({ lifeCycle, configuration }) => + trackCumulativeLayoutShift(configuration, lifeCycle, { addWebVitalTelemetryDebug: noop }, clsCallback) + ) + originalSupportedEntryTypes = Object.getOwnPropertyDescriptor(PerformanceObserver, 'supportedEntryTypes') isLayoutShiftSupported = true Object.defineProperty(PerformanceObserver, 'supportedEntryTypes', { @@ -40,24 +38,21 @@ describe('trackCumulativeLayoutShift', () => { it('should be initialized to 0', () => { setupBuilder.build() - const { getViewUpdate, getViewUpdateCount } = viewTest - expect(getViewUpdateCount()).toEqual(1) - expect(getViewUpdate(0).commonViewMetrics.cumulativeLayoutShift?.value).toBe(0) + expect(clsCallback).toHaveBeenCalledTimes(1) + expect(clsCallback).toHaveBeenCalledWith({ value: 0 }) }) it('should be initialized to undefined if layout-shift is not supported', () => { isLayoutShiftSupported = false setupBuilder.build() - const { getViewUpdate, getViewUpdateCount } = viewTest - expect(getViewUpdateCount()).toEqual(1) - expect(getViewUpdate(0).commonViewMetrics.cumulativeLayoutShift?.value).toBe(undefined) + expect(clsCallback).not.toHaveBeenCalled() }) it('should accumulate layout shift values for the first session window', () => { const { lifeCycle, clock } = setupBuilder.withFakeClock().build() - const { getViewUpdate, getViewUpdateCount } = viewTest + lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.1 }), ]) @@ -65,15 +60,14 @@ describe('trackCumulativeLayoutShift', () => { lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.2 }), ]) - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - expect(getViewUpdateCount()).toEqual(2) - expect(getViewUpdate(1).commonViewMetrics.cumulativeLayoutShift?.value).toBe(0.3) + expect(clsCallback).toHaveBeenCalledTimes(3) + expect(clsCallback.calls.mostRecent().args[0].value).toEqual(0.3) }) it('should round the cumulative layout shift value to 4 decimals', () => { const { lifeCycle, clock } = setupBuilder.withFakeClock().build() - const { getViewUpdate, getViewUpdateCount } = viewTest + lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 1.23456789 }), ]) @@ -81,28 +75,24 @@ describe('trackCumulativeLayoutShift', () => { lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 1.11111111111 }), ]) - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - expect(getViewUpdateCount()).toEqual(2) - expect(getViewUpdate(1).commonViewMetrics.cumulativeLayoutShift?.value).toBe(2.3457) + expect(clsCallback).toHaveBeenCalledTimes(3) + expect(clsCallback.calls.mostRecent().args[0].value).toEqual(2.3457) }) it('should ignore entries with recent input', () => { - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() - const { getViewUpdate, getViewUpdateCount } = viewTest + const { lifeCycle } = setupBuilder.withFakeClock().build() lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.1, hadRecentInput: true }), ]) - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - expect(getViewUpdateCount()).toEqual(1) - expect(getViewUpdate(0).commonViewMetrics.cumulativeLayoutShift?.value).toBe(0) + expect(clsCallback).toHaveBeenCalledTimes(1) + expect(clsCallback.calls.mostRecent().args[0].value).toEqual(0) }) it('should create a new session window if the gap is more than 1 second', () => { const { lifeCycle, clock } = setupBuilder.withFakeClock().build() - const { getViewUpdate, getViewUpdateCount } = viewTest // first session window lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.1 }), @@ -116,31 +106,31 @@ describe('trackCumulativeLayoutShift', () => { createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.1 }), ]) - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - expect(getViewUpdateCount()).toEqual(2) - expect(getViewUpdate(1).commonViewMetrics.cumulativeLayoutShift?.value).toBe(0.3) + expect(clsCallback).toHaveBeenCalledTimes(3) + expect(clsCallback.calls.mostRecent().args[0].value).toEqual(0.3) }) it('should create a new session window if the current session window is more than 5 second', () => { const { lifeCycle, clock } = setupBuilder.withFakeClock().build() - const { getViewUpdate, getViewUpdateCount } = viewTest + lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0 }), ]) + for (let i = 0; i < 6; i += 1) { clock.tick(999) lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.1 }), ]) } // window 1: 0.5 | window 2: 0.1 - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - expect(getViewUpdateCount()).toEqual(3) - expect(getViewUpdate(2).commonViewMetrics.cumulativeLayoutShift?.value).toBe(0.5) + + expect(clsCallback).toHaveBeenCalledTimes(6) + expect(clsCallback.calls.mostRecent().args[0].value).toEqual(0.5) }) it('should get the max value sessions', () => { const { lifeCycle, clock } = setupBuilder.withFakeClock().build() - const { getViewUpdate, getViewUpdateCount } = viewTest + // first session window lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.1 }), @@ -168,9 +158,8 @@ describe('trackCumulativeLayoutShift', () => { createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT, { value: 0.2 }), ]) - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - expect(getViewUpdateCount()).toEqual(3) - expect(getViewUpdate(2).commonViewMetrics.cumulativeLayoutShift?.value).toBe(0.5) + expect(clsCallback).toHaveBeenCalledTimes(4) + expect(clsCallback.calls.mostRecent().args[0]).toEqual({ value: 0.5, targetSelector: undefined }) }) describe('cls target element', () => { @@ -181,7 +170,6 @@ describe('trackCumulativeLayoutShift', () => { it('should return the first target element selector amongst all the shifted nodes when FF enabled', () => { addExperimentalFeatures([ExperimentalFeature.WEB_VITALS_ATTRIBUTION]) const { lifeCycle } = setupBuilder.build() - const { getViewUpdate, getViewUpdateCount } = viewTest const textNode = appendTextNode('') const divElement = appendElement('div', { id: 'div-element' }) @@ -192,13 +180,12 @@ describe('trackCumulativeLayoutShift', () => { }), ]) - expect(getViewUpdateCount()).toEqual(1) - expect(getViewUpdate(0).commonViewMetrics.cumulativeLayoutShift?.targetSelector).toBe('#div-element') + expect(clsCallback).toHaveBeenCalledTimes(2) + expect(clsCallback.calls.mostRecent().args[0].targetSelector).toEqual('#div-element') }) it('should not return the target element selector when FF disabled', () => { const { lifeCycle } = setupBuilder.build() - const { getViewUpdate, getViewUpdateCount } = viewTest const divElement = appendElement('div', { id: 'div-element' }) @@ -208,8 +195,8 @@ describe('trackCumulativeLayoutShift', () => { }), ]) - expect(getViewUpdateCount()).toEqual(1) - expect(getViewUpdate(0).commonViewMetrics.cumulativeLayoutShift?.targetSelector).toBe(undefined) + expect(clsCallback).toHaveBeenCalledTimes(2) + expect(clsCallback.calls.mostRecent().args[0].targetSelector).toEqual(undefined) }) }) }) From 3acd34dfb2ff1c30f38f8624f310d33b0c7203c6 Mon Sep 17 00:00:00 2001 From: Aymeric Mortemousque Date: Fri, 15 Sep 2023 17:07:31 +0200 Subject: [PATCH 04/10] Scope trackLoadingTime tests --- .../view/viewMetrics/trackLoadingTime.spec.ts | 118 ++++++++---------- 1 file changed, 50 insertions(+), 68 deletions(-) diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts index 33511e1bfc..ef1950e467 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts @@ -1,13 +1,13 @@ import type { RelativeTime, Duration } from '@datadog/browser-core' -import { addDuration } from '@datadog/browser-core' +import { addDuration, clocksNow } from '@datadog/browser-core' +import { ViewLoadingType } from '../../../rawRumEvent.types' import type { TestSetupBuilder } from '../../../../test' import { createPerformanceEntry, setup } from '../../../../test' -import { RumPerformanceEntryType } from '../../../browser/performanceCollection' -import { LifeCycleEventType } from '../../lifeCycle' import { PAGE_ACTIVITY_END_DELAY, PAGE_ACTIVITY_VALIDATION_DELAY } from '../../waitPageActivityEnd' import { THROTTLE_VIEW_UPDATE_PERIOD } from '../trackViews' -import type { ViewTest } from '../setupViewTest.specHelper' -import { setupViewTest } from '../setupViewTest.specHelper' +import { RumPerformanceEntryType } from '../../../browser/performanceCollection' +import { LifeCycleEventType } from '../../lifeCycle' +import { trackLoadingTime } from './trackLoadingTime' const BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY = (PAGE_ACTIVITY_VALIDATION_DELAY * 0.8) as Duration @@ -19,14 +19,25 @@ const LOAD_EVENT_AFTER_ACTIVITY_TIMING = (BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY describe('trackLoadingTime', () => { let setupBuilder: TestSetupBuilder - let viewTest: ViewTest + let loadingTimeCallback: jasmine.Spy<(loadingTime: Duration) => void> + let loadType: ViewLoadingType + let setLoadEvent: (loadEvent: Duration) => void beforeEach(() => { + loadType = ViewLoadingType.ROUTE_CHANGE + loadingTimeCallback = jasmine.createSpy('loadingTimeCallback') setupBuilder = setup() - .withFakeLocation('/foo') - .beforeBuild((buildContext) => { - viewTest = setupViewTest(buildContext) - return viewTest + .beforeBuild(({ lifeCycle, domMutationObservable, configuration }) => { + const loadingTimeTracking = trackLoadingTime( + lifeCycle, + domMutationObservable, + configuration, + loadType, + clocksNow(), + loadingTimeCallback + ) + setLoadEvent = loadingTimeTracking.setLoadEvent + return loadingTimeTracking }) .withFakeClock() }) @@ -36,114 +47,85 @@ describe('trackLoadingTime', () => { }) it('should have an undefined loading time if there is no activity on a route change', () => { - const { clock } = setupBuilder.build() - const { getViewUpdate, getViewUpdateCount, startView } = viewTest - - startView() - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) + setupBuilder.build() - expect(getViewUpdateCount()).toEqual(3) - expect(getViewUpdate(2).commonViewMetrics.loadingTime).toBeUndefined() + expect(loadingTimeCallback).not.toHaveBeenCalled() }) it('should have a loading time equal to the activity time if there is a unique activity on a route change', () => { const { domMutationObservable, clock } = setupBuilder.build() - const { getViewUpdate, startView } = viewTest - startView() clock.tick(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) domMutationObservable.notify() clock.tick(AFTER_PAGE_ACTIVITY_END_DELAY) - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - expect(getViewUpdate(3).commonViewMetrics.loadingTime).toEqual(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) + expect(loadingTimeCallback).toHaveBeenCalledTimes(1) + expect(loadingTimeCallback).toHaveBeenCalledWith(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) }) it('should use loadEventEnd for initial view when having no activity', () => { + loadType = ViewLoadingType.INITIAL_LOAD const { lifeCycle, clock } = setupBuilder.build() - const { getViewUpdate, getViewUpdateCount } = viewTest - expect(getViewUpdateCount()).toEqual(1) const entry = createPerformanceEntry(RumPerformanceEntryType.NAVIGATION) lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [entry]) - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - expect(getViewUpdateCount()).toEqual(2) - expect(getViewUpdate(1).commonViewMetrics.loadingTime).toEqual(entry.loadEventEnd) + setLoadEvent(entry.loadEventEnd) + clock.tick(PAGE_ACTIVITY_END_DELAY) + + expect(loadingTimeCallback).toHaveBeenCalledTimes(1) + expect(loadingTimeCallback).toHaveBeenCalledWith(entry.loadEventEnd) }) it('should use loadEventEnd for initial view when load event is bigger than computed loading time', () => { - const { lifeCycle, domMutationObservable, clock } = setupBuilder.build() - const { getViewUpdate, getViewUpdateCount } = viewTest - - expect(getViewUpdateCount()).toEqual(1) + loadType = ViewLoadingType.INITIAL_LOAD + const { domMutationObservable, clock } = setupBuilder.build() clock.tick(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) - lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ - createPerformanceEntry(RumPerformanceEntryType.NAVIGATION, { - loadEventEnd: LOAD_EVENT_AFTER_ACTIVITY_TIMING, - }), - ]) - + setLoadEvent(LOAD_EVENT_AFTER_ACTIVITY_TIMING) domMutationObservable.notify() clock.tick(AFTER_PAGE_ACTIVITY_END_DELAY) - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - - expect(getViewUpdateCount()).toEqual(2) - expect(getViewUpdate(1).commonViewMetrics.loadingTime).toEqual(LOAD_EVENT_AFTER_ACTIVITY_TIMING) + expect(loadingTimeCallback).toHaveBeenCalledTimes(1) + expect(loadingTimeCallback).toHaveBeenCalledWith(LOAD_EVENT_AFTER_ACTIVITY_TIMING) }) it('should use computed loading time for initial view when load event is smaller than computed loading time', () => { - const { lifeCycle, domMutationObservable, clock } = setupBuilder.build() - const { getViewUpdate, getViewUpdateCount } = viewTest - - expect(getViewUpdateCount()).toEqual(1) + loadType = ViewLoadingType.INITIAL_LOAD + const { domMutationObservable, clock } = setupBuilder.build() clock.tick(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) - lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ - createPerformanceEntry(RumPerformanceEntryType.NAVIGATION, { - loadEventEnd: LOAD_EVENT_BEFORE_ACTIVITY_TIMING, - }), - ]) + + setLoadEvent(LOAD_EVENT_BEFORE_ACTIVITY_TIMING) + domMutationObservable.notify() clock.tick(AFTER_PAGE_ACTIVITY_END_DELAY) - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - expect(getViewUpdateCount()).toEqual(2) - expect(getViewUpdate(1).commonViewMetrics.loadingTime).toEqual(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) + expect(loadingTimeCallback).toHaveBeenCalledTimes(1) + expect(loadingTimeCallback).toHaveBeenCalledWith(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) }) it('should use computed loading time from time origin for initial view', () => { + loadType = ViewLoadingType.INITIAL_LOAD + const { domMutationObservable, clock } = setupBuilder.build() + // introduce a gap between time origin and tracking start // ensure that `load event > activity delay` and `load event < activity delay + clock gap` // to make the test fail if the clock gap is not correctly taken into account const CLOCK_GAP = (LOAD_EVENT_AFTER_ACTIVITY_TIMING - BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY + 1) as Duration - setupBuilder.clock!.tick(CLOCK_GAP) - - const { lifeCycle, domMutationObservable, clock } = setupBuilder.build() - const { getViewUpdate, getViewUpdateCount } = viewTest - - expect(getViewUpdateCount()).toEqual(1) + clock.tick(CLOCK_GAP) clock.tick(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) - lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ - createPerformanceEntry(RumPerformanceEntryType.NAVIGATION, { - loadEventEnd: LOAD_EVENT_BEFORE_ACTIVITY_TIMING, - }), - ]) + setLoadEvent(LOAD_EVENT_AFTER_ACTIVITY_TIMING) domMutationObservable.notify() clock.tick(AFTER_PAGE_ACTIVITY_END_DELAY) - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - expect(getViewUpdateCount()).toEqual(2) - expect(getViewUpdate(1).commonViewMetrics.loadingTime).toEqual( - addDuration(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY, CLOCK_GAP) - ) + expect(loadingTimeCallback).toHaveBeenCalledTimes(1) + expect(loadingTimeCallback).toHaveBeenCalledWith(addDuration(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY, CLOCK_GAP)) }) }) From bdbab3a8ceaa7f69d70ad49f565697ac05ce92e5 Mon Sep 17 00:00:00 2001 From: Aymeric Mortemousque Date: Fri, 15 Sep 2023 17:10:26 +0200 Subject: [PATCH 05/10] Scope trackViewEventCounts tests Most of these tests where in fact testing trackView behavior or where already covered by trackEventCounts tests. --- .../domain/view/trackViewEventCounts.spec.ts | 187 +----------------- 1 file changed, 10 insertions(+), 177 deletions(-) diff --git a/packages/rum-core/src/domain/view/trackViewEventCounts.spec.ts b/packages/rum-core/src/domain/view/trackViewEventCounts.spec.ts index 4c08efdb51..54bdab4c96 100644 --- a/packages/rum-core/src/domain/view/trackViewEventCounts.spec.ts +++ b/packages/rum-core/src/domain/view/trackViewEventCounts.spec.ts @@ -3,209 +3,42 @@ import type { RumEvent } from '../../rumEvent.types' import { LifeCycleEventType } from '../lifeCycle' import type { TestSetupBuilder } from '../../../test' import { setup } from '../../../test' -import { FrustrationType, RumEventType } from '../../rawRumEvent.types' -import { THROTTLE_VIEW_UPDATE_PERIOD } from './trackViews' -import type { ViewTest } from './setupViewTest.specHelper' -import { setupViewTest } from './setupViewTest.specHelper' +import { RumEventType } from '../../rawRumEvent.types' +import { trackViewEventCounts } from './trackViewEventCounts' describe('trackViewEventCounts', () => { let setupBuilder: TestSetupBuilder - let viewTest: ViewTest + let onChange: () => void beforeEach(() => { - setupBuilder = setup() - .withFakeLocation('/foo') - .beforeBuild((buildContext) => { - viewTest = setupViewTest(buildContext) - return viewTest - }) + onChange = jasmine.createSpy('onChange') + + setupBuilder = setup().beforeBuild(({ lifeCycle }) => trackViewEventCounts(lifeCycle, 'view-id', onChange)) }) afterEach(() => { setupBuilder.cleanup() }) - it('should track error count', () => { + it('should track events count', () => { const { lifeCycle } = setupBuilder.build() - const { getViewUpdate, getViewUpdateCount, startView, getLatestViewContext } = viewTest - - expect(getViewUpdateCount()).toEqual(1) - expect(getViewUpdate(0).eventCounts.errorCount).toEqual(0) lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { type: RumEventType.ERROR, - view: getLatestViewContext(), - } as RumEvent & Context) - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { - type: RumEventType.ERROR, - view: getLatestViewContext(), - } as RumEvent & Context) - startView() - - expect(getViewUpdateCount()).toEqual(3) - expect(getViewUpdate(1).eventCounts.errorCount).toEqual(2) - expect(getViewUpdate(2).eventCounts.errorCount).toEqual(0) - }) - - it('should track long task count', () => { - const { lifeCycle } = setupBuilder.build() - const { getViewUpdate, getViewUpdateCount, startView, getLatestViewContext } = viewTest - - expect(getViewUpdateCount()).toEqual(1) - expect(getViewUpdate(0).eventCounts.longTaskCount).toEqual(0) - - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { - type: RumEventType.LONG_TASK, - view: getLatestViewContext(), - } as RumEvent & Context) - startView() - - expect(getViewUpdateCount()).toEqual(3) - expect(getViewUpdate(1).eventCounts.longTaskCount).toEqual(1) - expect(getViewUpdate(2).eventCounts.longTaskCount).toEqual(0) - }) - - it('should track resource count', () => { - const { lifeCycle } = setupBuilder.build() - const { getViewUpdate, getViewUpdateCount, startView, getLatestViewContext } = viewTest - - expect(getViewUpdateCount()).toEqual(1) - expect(getViewUpdate(0).eventCounts.resourceCount).toEqual(0) - - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { - type: RumEventType.RESOURCE, - view: getLatestViewContext(), - } as RumEvent & Context) - startView() - - expect(getViewUpdateCount()).toEqual(3) - expect(getViewUpdate(1).eventCounts.resourceCount).toEqual(1) - expect(getViewUpdate(2).eventCounts.resourceCount).toEqual(0) - }) - - it('should track action count', () => { - const { lifeCycle } = setupBuilder.build() - const { getViewUpdate, getViewUpdateCount, startView, getLatestViewContext } = viewTest - - expect(getViewUpdateCount()).toEqual(1) - expect(getViewUpdate(0).eventCounts.actionCount).toEqual(0) - - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { - type: RumEventType.ACTION, - action: { type: 'custom' }, - view: getLatestViewContext(), - } as RumEvent & Context) - startView() - - expect(getViewUpdateCount()).toEqual(3) - expect(getViewUpdate(1).eventCounts.actionCount).toEqual(1) - expect(getViewUpdate(2).eventCounts.actionCount).toEqual(0) - }) - - it('should track frustration count', () => { - const { lifeCycle } = setupBuilder.build() - const { getViewUpdate, getViewUpdateCount, startView, getLatestViewContext } = viewTest - - expect(getViewUpdateCount()).toEqual(1) - expect(getViewUpdate(0).eventCounts.frustrationCount).toEqual(0) - - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { - type: RumEventType.ACTION, - action: { - type: 'click', - id: '123', - frustration: { - type: [FrustrationType.DEAD_CLICK, FrustrationType.ERROR_CLICK], - }, - }, - view: getLatestViewContext(), + view: { id: 'view-id' }, } as RumEvent & Context) - startView() - expect(getViewUpdateCount()).toEqual(3) - expect(getViewUpdate(1).eventCounts.frustrationCount).toEqual(2) - expect(getViewUpdate(2).eventCounts.frustrationCount).toEqual(0) + expect(onChange).toHaveBeenCalledTimes(1) }) it('should not count child events unrelated to the view', () => { const { lifeCycle } = setupBuilder.build() - const { getViewUpdate, getViewUpdateCount, startView } = viewTest - - expect(getViewUpdateCount()).toEqual(1) - expect(getViewUpdate(0).eventCounts.errorCount).toEqual(0) lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { type: RumEventType.ERROR, view: { id: 'unrelated-view-id' }, } as RumEvent & Context) - startView() - - expect(getViewUpdateCount()).toEqual(3) - expect(getViewUpdate(1).eventCounts.errorCount).toEqual(0) - expect(getViewUpdate(2).eventCounts.errorCount).toEqual(0) - }) - - it('should reset event count when the view changes', () => { - const { lifeCycle, changeLocation } = setupBuilder.build() - const { getViewUpdate, getViewUpdateCount, startView, getLatestViewContext } = viewTest - - expect(getViewUpdateCount()).toEqual(1) - expect(getViewUpdate(0).eventCounts.resourceCount).toEqual(0) - - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { - type: RumEventType.RESOURCE, - view: getLatestViewContext(), - } as RumEvent & Context) - startView() - - expect(getViewUpdateCount()).toEqual(3) - expect(getViewUpdate(1).eventCounts.resourceCount).toEqual(1) - expect(getViewUpdate(2).eventCounts.resourceCount).toEqual(0) - - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { - type: RumEventType.RESOURCE, - view: getLatestViewContext(), - } as RumEvent & Context) - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { - type: RumEventType.RESOURCE, - view: getLatestViewContext(), - } as RumEvent & Context) - changeLocation('/baz') - - expect(getViewUpdateCount()).toEqual(5) - expect(getViewUpdate(3).eventCounts.resourceCount).toEqual(2) - expect(getViewUpdate(4).eventCounts.resourceCount).toEqual(0) - }) - - it('should update eventCounts when a resource event is collected (throttled)', () => { - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() - const { getViewUpdate, getViewUpdateCount, getLatestViewContext } = viewTest - - expect(getViewUpdateCount()).toEqual(1) - expect(getViewUpdate(0).eventCounts).toEqual({ - errorCount: 0, - longTaskCount: 0, - resourceCount: 0, - actionCount: 0, - frustrationCount: 0, - }) - - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { - type: RumEventType.RESOURCE, - view: getLatestViewContext(), - } as RumEvent & Context) - - expect(getViewUpdateCount()).toEqual(1) - - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - expect(getViewUpdateCount()).toEqual(2) - expect(getViewUpdate(1).eventCounts).toEqual({ - errorCount: 0, - longTaskCount: 0, - resourceCount: 1, - actionCount: 0, - frustrationCount: 0, - }) + expect(onChange).not.toHaveBeenCalled() }) }) From 65c8747db07f253f47e94c0027e7d6d5a3b205ae Mon Sep 17 00:00:00 2001 From: Aymeric Mortemousque Date: Fri, 15 Sep 2023 17:16:45 +0200 Subject: [PATCH 06/10] Reorganize trackViews tests --- .../src/domain/view/trackViews.spec.ts | 487 +++++++++--------- 1 file changed, 249 insertions(+), 238 deletions(-) diff --git a/packages/rum-core/src/domain/view/trackViews.spec.ts b/packages/rum-core/src/domain/view/trackViews.spec.ts index dcff5e2bb1..51ede086b3 100644 --- a/packages/rum-core/src/domain/view/trackViews.spec.ts +++ b/packages/rum-core/src/domain/view/trackViews.spec.ts @@ -26,7 +26,7 @@ describe('track views automatically', () => { setupBuilder = setup() .withFakeLocation('/foo') .beforeBuild((buildContext) => { - viewTest = setupViewTest(buildContext) + viewTest = setupViewTest(buildContext, { name: 'initial view name' }) return viewTest }) }) @@ -35,6 +35,16 @@ describe('track views automatically', () => { setupBuilder.cleanup() }) + describe('initial view', () => { + it('should be created on start', () => { + setupBuilder.build() + const { getViewCreate, getViewCreateCount } = viewTest + + expect(getViewCreateCount()).toBe(1) + expect(getViewCreate(0).name).toBe('initial view name') + }) + }) + describe('location changes', () => { it('should create new view on path change', () => { const { changeLocation } = setupBuilder.build() @@ -90,241 +100,6 @@ describe('track views automatically', () => { }) }) -describe('initial view', () => { - let setupBuilder: TestSetupBuilder - let viewTest: ViewTest - - beforeEach(() => { - setupBuilder = setup().beforeBuild((buildContext) => { - viewTest = setupViewTest(buildContext, { name: 'initial view name' }) - return viewTest - }) - }) - - afterEach(() => { - setupBuilder.cleanup() - }) - - it('should be created on start', () => { - setupBuilder.build() - const { getViewCreate, getViewCreateCount } = viewTest - - expect(getViewCreateCount()).toBe(1) - expect(getViewCreate(0).name).toBe('initial view name') - }) - - describe('metrics', () => { - it('should update initial view metrics when notified with a PERFORMANCE_ENTRY_COLLECTED event (throttled)', () => { - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() - const { getViewUpdateCount, getViewUpdate } = viewTest - const performanceEntry = createPerformanceEntry(RumPerformanceEntryType.NAVIGATION) - - expect(getViewUpdateCount()).toEqual(1) - expect(getViewUpdate(0).initialViewMetrics).toEqual({}) - - clock.tick(performanceEntry.responseStart) // ensure now > responseStart - lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [performanceEntry]) - - expect(getViewUpdateCount()).toEqual(1) - - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - - expect(getViewUpdateCount()).toEqual(2) - expect(getViewUpdate(1).initialViewMetrics.navigationTimings).toEqual({ - firstByte: 123 as Duration, - domComplete: 456 as Duration, - domContentLoaded: 345 as Duration, - domInteractive: 234 as Duration, - loadEvent: 567 as Duration, - }) - }) - - it('should update initial view metrics when ending a view', () => { - const { lifeCycle } = setupBuilder.build() - const { getViewUpdateCount, getViewUpdate, startView } = viewTest - - expect(getViewUpdateCount()).toEqual(1) - expect(getViewUpdate(0).initialViewMetrics).toEqual({}) - - lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ - createPerformanceEntry(RumPerformanceEntryType.PAINT), - createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT), - createPerformanceEntry(RumPerformanceEntryType.NAVIGATION), - ]) - expect(getViewUpdateCount()).toEqual(1) - - startView() - - expect(getViewUpdateCount()).toEqual(3) - expect(getViewUpdate(1).initialViewMetrics).toEqual( - jasmine.objectContaining({ - firstContentfulPaint: 123 as Duration, - navigationTimings: { - firstByte: 123 as Duration, - domComplete: 456 as Duration, - domContentLoaded: 345 as Duration, - domInteractive: 234 as Duration, - loadEvent: 567 as Duration, - }, - largestContentfulPaint: { value: 789 as Duration, targetSelector: undefined }, - }) - ) - expect(getViewUpdate(2).initialViewMetrics).toEqual({}) - }) - - describe('load event happening after initial view end', () => { - let initialView: { init: ViewEvent; end: ViewEvent; last: ViewEvent } - let secondView: { init: ViewEvent; last: ViewEvent } - const VIEW_DURATION = 100 as Duration - - beforeEach(() => { - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() - const { getViewUpdateCount, getViewUpdate, startView } = viewTest - - expect(getViewUpdateCount()).toEqual(1) - - clock.tick(VIEW_DURATION) - - startView() - - clock.tick(VIEW_DURATION) - - expect(getViewUpdateCount()).toEqual(3) - - lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ - createPerformanceEntry(RumPerformanceEntryType.PAINT), - createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT), - createPerformanceEntry(RumPerformanceEntryType.NAVIGATION), - ]) - - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - - expect(getViewUpdateCount()).toEqual(4) - - initialView = { - end: getViewUpdate(1), - init: getViewUpdate(0), - last: getViewUpdate(3), - } - secondView = { - init: getViewUpdate(2), - last: getViewUpdate(2), - } - }) - - it('should not set metrics to the second view', () => { - expect(secondView.last.initialViewMetrics).toEqual({}) - }) - - it('should set initial view metrics only on the initial view', () => { - expect(initialView.last.initialViewMetrics).toEqual( - jasmine.objectContaining({ - firstContentfulPaint: 123 as Duration, - navigationTimings: { - firstByte: 123 as Duration, - domComplete: 456 as Duration, - domContentLoaded: 345 as Duration, - domInteractive: 234 as Duration, - loadEvent: 567 as Duration, - }, - largestContentfulPaint: { value: 789 as Duration, targetSelector: undefined }, - }) - ) - }) - - it('should not update the initial view duration when updating it with new timings', () => { - expect(initialView.end.duration).toBe(VIEW_DURATION) - expect(initialView.last.duration).toBe(VIEW_DURATION) - }) - - it('should update the initial view loadingTime following the loadEventEnd value', () => { - expect(initialView.last.commonViewMetrics.loadingTime).toBe(567 as RelativeTime) - }) - }) - - it('should keep updating the initial view metrics for 5 min after view end', () => { - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() - const { getViewCreateCount, getViewUpdate, getViewUpdateCount, startView } = viewTest - const lcpEntry = createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT) - startView() - expect(getViewCreateCount()).toEqual(2) - - clock.tick(KEEP_TRACKING_AFTER_VIEW_DELAY - 1) - lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ - createPerformanceEntry(RumPerformanceEntryType.PAINT), - lcpEntry, - createPerformanceEntry(RumPerformanceEntryType.NAVIGATION), - ]) - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - - const latestUpdate = getViewUpdate(getViewUpdateCount() - 1) - const firstView = getViewUpdate(0) - expect(latestUpdate.id).toBe(firstView.id) - expect(latestUpdate.initialViewMetrics.largestContentfulPaint?.value).toEqual(lcpEntry.startTime) - }) - - it('should not update the initial view metrics 5 min after view end', () => { - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() - const { getViewCreateCount, getViewUpdate, getViewUpdateCount, startView } = viewTest - startView() - expect(getViewCreateCount()).toEqual(2) - - clock.tick(KEEP_TRACKING_AFTER_VIEW_DELAY) - lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ - createPerformanceEntry(RumPerformanceEntryType.PAINT), - createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT), - createPerformanceEntry(RumPerformanceEntryType.NAVIGATION), - ]) - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - - const latestUpdate = getViewUpdate(getViewUpdateCount() - 1) - const firstView = getViewUpdate(0) - expect(latestUpdate.id).not.toBe(firstView.id) - }) - - it('should keep updating the view event counters for 5 min after view end', () => { - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() - const { getViewUpdate, getViewUpdateCount, getViewCreateCount, startView } = viewTest - startView() - expect(getViewCreateCount()).toEqual(2) - const firstView = getViewUpdate(0) - - clock.tick(KEEP_TRACKING_AFTER_VIEW_DELAY - 1) - - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { - type: RumEventType.RESOURCE, - view: { id: firstView.id }, - } as RumEvent & Context) - - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - - const latestUpdate = getViewUpdate(getViewUpdateCount() - 1) - expect(latestUpdate.id).toEqual(firstView.id) - expect(latestUpdate.eventCounts.resourceCount).toEqual(1) - }) - - it('should not update the view event counters 5 min after view end', () => { - const { lifeCycle, clock } = setupBuilder.withFakeClock().build() - const { getViewUpdate, getViewUpdateCount, getViewCreateCount, startView } = viewTest - startView() - expect(getViewCreateCount()).toEqual(2) - const firstView = getViewUpdate(0) - - clock.tick(KEEP_TRACKING_AFTER_VIEW_DELAY) - - lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { - type: RumEventType.RESOURCE, - view: { id: firstView.id }, - } as RumEvent & Context) - - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - - const latestUpdate = getViewUpdate(getViewUpdateCount() - 1) - expect(latestUpdate.id).not.toEqual(firstView.id) - }) - }) -}) - describe('view lifecycle', () => { let setupBuilder: TestSetupBuilder let viewTest: ViewTest @@ -586,6 +361,201 @@ describe('view loading type', () => { }) }) +describe('view metrics', () => { + let setupBuilder: TestSetupBuilder + let viewTest: ViewTest + + beforeEach(() => { + setupBuilder = setup() + .withFakeLocation('/foo') + .beforeBuild((buildContext) => { + viewTest = setupViewTest(buildContext) + return viewTest + }) + }) + + afterEach(() => { + setupBuilder.cleanup() + }) + + describe('common view metrics', () => { + it('should be updated when notified with a PERFORMANCE_ENTRY_COLLECTED event (throttled)', () => { + const { lifeCycle, clock } = setupBuilder.withFakeClock().build() + const { getViewUpdateCount, getViewUpdate } = viewTest + + expect(getViewUpdateCount()).toEqual(1) + expect(getViewUpdate(0).initialViewMetrics).toEqual({}) + + lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ + createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT), + ]) + + expect(getViewUpdateCount()).toEqual(1) + + clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) + + expect(getViewUpdateCount()).toEqual(2) + expect(getViewUpdate(1).commonViewMetrics.cumulativeLayoutShift).toEqual({ + value: 0.1, + targetSelector: undefined, + }) + }) + + it('should not be updated after view end', () => { + const { lifeCycle, clock } = setupBuilder.withFakeClock().build() + const { getViewUpdate, getViewUpdateCount, getViewCreateCount, startView } = viewTest + startView() + expect(getViewCreateCount()).toEqual(2) + + lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ + createPerformanceEntry(RumPerformanceEntryType.LAYOUT_SHIFT), + ]) + + clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) + + const latestUpdate = getViewUpdate(getViewUpdateCount() - 1) + const firstView = getViewUpdate(0) + expect(latestUpdate.id).not.toBe(firstView.id) + }) + }) + + describe('initial view metrics', () => { + it('should be updated when notified with a PERFORMANCE_ENTRY_COLLECTED event (throttled)', () => { + const { lifeCycle, clock } = setupBuilder.withFakeClock().build() + const { getViewUpdateCount, getViewUpdate } = viewTest + expect(getViewUpdateCount()).toEqual(1) + expect(getViewUpdate(0).initialViewMetrics).toEqual({}) + + const navigationEntry = createPerformanceEntry(RumPerformanceEntryType.NAVIGATION) + clock.tick(navigationEntry.responseStart) // ensure now > responseStart + lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [navigationEntry]) + + expect(getViewUpdateCount()).toEqual(1) + + clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) + + expect(getViewUpdateCount()).toEqual(2) + expect(getViewUpdate(1).initialViewMetrics.navigationTimings).toEqual({ + firstByte: 123 as Duration, + domComplete: 456 as Duration, + domContentLoaded: 345 as Duration, + domInteractive: 234 as Duration, + loadEvent: 567 as Duration, + }) + }) + + it('should be updated for 5 min after view end', () => { + const { lifeCycle, clock } = setupBuilder.withFakeClock().build() + const { getViewCreateCount, getViewUpdate, getViewUpdateCount, startView } = viewTest + startView() + expect(getViewCreateCount()).toEqual(2) + + const lcpEntry = createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT) + clock.tick(KEEP_TRACKING_AFTER_VIEW_DELAY - 1) + lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ + createPerformanceEntry(RumPerformanceEntryType.PAINT), + lcpEntry, + createPerformanceEntry(RumPerformanceEntryType.NAVIGATION), + ]) + clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) + + const latestUpdate = getViewUpdate(getViewUpdateCount() - 1) + const firstView = getViewUpdate(0) + expect(latestUpdate.id).toBe(firstView.id) + expect(latestUpdate.initialViewMetrics.largestContentfulPaint?.value).toEqual(lcpEntry.startTime) + }) + + it('should not be updated 5 min after view end', () => { + const { lifeCycle, clock } = setupBuilder.withFakeClock().build() + const { getViewCreateCount, getViewUpdate, getViewUpdateCount, startView } = viewTest + startView() + expect(getViewCreateCount()).toEqual(2) + + clock.tick(KEEP_TRACKING_AFTER_VIEW_DELAY) + lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ + createPerformanceEntry(RumPerformanceEntryType.PAINT), + createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT), + createPerformanceEntry(RumPerformanceEntryType.NAVIGATION), + ]) + clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) + + const latestUpdate = getViewUpdate(getViewUpdateCount() - 1) + const firstView = getViewUpdate(0) + expect(latestUpdate.id).not.toBe(firstView.id) + }) + + describe('when load event happening after initial view end', () => { + let initialView: { init: ViewEvent; end: ViewEvent; last: ViewEvent } + let secondView: { init: ViewEvent; last: ViewEvent } + const VIEW_DURATION = 100 as Duration + + beforeEach(() => { + const { lifeCycle, clock } = setupBuilder.withFakeClock().build() + const { getViewUpdateCount, getViewUpdate, startView } = viewTest + + expect(getViewUpdateCount()).toEqual(1) + + clock.tick(VIEW_DURATION) + + startView() + + clock.tick(VIEW_DURATION) + + expect(getViewUpdateCount()).toEqual(3) + + lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [ + createPerformanceEntry(RumPerformanceEntryType.PAINT), + createPerformanceEntry(RumPerformanceEntryType.LARGEST_CONTENTFUL_PAINT), + createPerformanceEntry(RumPerformanceEntryType.NAVIGATION), + ]) + + clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) + + expect(getViewUpdateCount()).toEqual(4) + + initialView = { + end: getViewUpdate(1), + init: getViewUpdate(0), + last: getViewUpdate(3), + } + secondView = { + init: getViewUpdate(2), + last: getViewUpdate(2), + } + }) + + it('should not be added on second view', () => { + expect(secondView.last.initialViewMetrics).toEqual({}) + }) + + it('should be added only on the initial view', () => { + expect(initialView.last.initialViewMetrics).toEqual( + jasmine.objectContaining({ + firstContentfulPaint: 123 as Duration, + navigationTimings: { + firstByte: 123 as Duration, + domComplete: 456 as Duration, + domContentLoaded: 345 as Duration, + domInteractive: 234 as Duration, + loadEvent: 567 as Duration, + }, + largestContentfulPaint: { value: 789 as Duration, targetSelector: undefined }, + }) + ) + }) + + it('should not update the initial view duration when updating it with new timings', () => { + expect(initialView.end.duration).toBe(VIEW_DURATION) + expect(initialView.last.duration).toBe(VIEW_DURATION) + }) + + it('should update the initial view loadingTime following the loadEventEnd value', () => { + expect(initialView.last.commonViewMetrics.loadingTime).toBe(567 as RelativeTime) + }) + }) + }) +}) + describe('view is active', () => { let setupBuilder: TestSetupBuilder let viewTest: ViewTest @@ -870,7 +840,7 @@ describe('view event count', () => { setupBuilder.cleanup() }) - it('includes event counts', () => { + it('should be updated when notified with a RUM_EVENT_COLLECTED event', () => { const { lifeCycle, clock } = setupBuilder.withFakeClock().build() const { getViewUpdate, getViewUpdateCount } = viewTest @@ -881,7 +851,7 @@ describe('view event count', () => { expect(getViewUpdate(getViewUpdateCount() - 1).eventCounts.actionCount).toBe(1) }) - it('takes child events occurring on view end into account', () => { + it('should take child events occurring on view end into account', () => { const { lifeCycle } = setupBuilder.build() const { getViewUpdate, getViewUpdateCount } = viewTest @@ -894,6 +864,47 @@ describe('view event count', () => { expect(getViewUpdate(getViewUpdateCount() - 1).eventCounts.actionCount).toBe(1) }) + it('should be updated for 5 min after view end', () => { + const { lifeCycle, clock } = setupBuilder.withFakeClock().build() + const { getViewUpdate, getViewUpdateCount, getViewCreateCount, startView } = viewTest + startView() + expect(getViewCreateCount()).toEqual(2) + const firstView = getViewUpdate(0) + + clock.tick(KEEP_TRACKING_AFTER_VIEW_DELAY - 1) + + lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { + type: RumEventType.RESOURCE, + view: { id: firstView.id }, + } as RumEvent & Context) + + clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) + + const latestUpdate = getViewUpdate(getViewUpdateCount() - 1) + expect(latestUpdate.id).toEqual(firstView.id) + expect(latestUpdate.eventCounts.resourceCount).toEqual(1) + }) + + it('should not be updated 5 min after view end', () => { + const { lifeCycle, clock } = setupBuilder.withFakeClock().build() + const { getViewUpdate, getViewUpdateCount, getViewCreateCount, startView } = viewTest + startView() + expect(getViewCreateCount()).toEqual(2) + const firstView = getViewUpdate(0) + + clock.tick(KEEP_TRACKING_AFTER_VIEW_DELAY) + + lifeCycle.notify(LifeCycleEventType.RUM_EVENT_COLLECTED, { + type: RumEventType.RESOURCE, + view: { id: firstView.id }, + } as RumEvent & Context) + + clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) + + const latestUpdate = getViewUpdate(getViewUpdateCount() - 1) + expect(latestUpdate.id).not.toEqual(firstView.id) + }) + function createFakeActionEvent() { return { type: RumEventType.ACTION, From 1254fc002762c4a48bb31f08e587818b04f35af9 Mon Sep 17 00:00:00 2001 From: Aymeric Mortemousque Date: Wed, 20 Sep 2023 14:29:22 +0200 Subject: [PATCH 07/10] Use toHaveBeenCalledOnceWith --- .../trackCumulativeLayoutShift.spec.ts | 3 +-- .../view/viewMetrics/trackFirstInput.spec.ts | 10 +++------- .../view/viewMetrics/trackLoadingTime.spec.ts | 19 ++++++------------- .../trackNavigationTimings.spec.ts | 3 +-- 4 files changed, 11 insertions(+), 24 deletions(-) diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.spec.ts index 7a6922108d..1b7be8f422 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackCumulativeLayoutShift.spec.ts @@ -39,8 +39,7 @@ describe('trackCumulativeLayoutShift', () => { it('should be initialized to 0', () => { setupBuilder.build() - expect(clsCallback).toHaveBeenCalledTimes(1) - expect(clsCallback).toHaveBeenCalledWith({ value: 0 }) + expect(clsCallback).toHaveBeenCalledOnceWith({ value: 0 }) }) it('should be initialized to undefined if layout-shift is not supported', () => { diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackFirstInput.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackFirstInput.spec.ts index 9044c742e9..b70d442d91 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackFirstInput.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackFirstInput.spec.ts @@ -57,8 +57,7 @@ describe('firstInputTimings', () => { createPerformanceEntry(RumPerformanceEntryType.FIRST_INPUT), ]) - expect(fitCallback).toHaveBeenCalledTimes(1) - expect(fitCallback).toHaveBeenCalledWith({ + expect(fitCallback).toHaveBeenCalledOnceWith({ delay: 100 as Duration, time: 1000 as RelativeTime, targetSelector: undefined, @@ -75,8 +74,7 @@ describe('firstInputTimings', () => { }), ]) - expect(fitCallback).toHaveBeenCalledTimes(1) - expect(fitCallback).toHaveBeenCalledWith( + expect(fitCallback).toHaveBeenCalledOnceWith( jasmine.objectContaining({ targetSelector: '#fid-target-element', }) @@ -93,7 +91,6 @@ describe('firstInputTimings', () => { }), ]) - expect(fitCallback).toHaveBeenCalledTimes(1) expect(fitCallback).toHaveBeenCalledWith( jasmine.objectContaining({ targetSelector: undefined, @@ -123,8 +120,7 @@ describe('firstInputTimings', () => { }), ]) - expect(fitCallback).toHaveBeenCalledTimes(1) - expect(fitCallback).toHaveBeenCalledWith( + expect(fitCallback).toHaveBeenCalledOnceWith( jasmine.objectContaining({ delay: 0, time: 1000, diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts index ef1950e467..00c71a552e 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts @@ -6,7 +6,6 @@ import { createPerformanceEntry, setup } from '../../../../test' import { PAGE_ACTIVITY_END_DELAY, PAGE_ACTIVITY_VALIDATION_DELAY } from '../../waitPageActivityEnd' import { THROTTLE_VIEW_UPDATE_PERIOD } from '../trackViews' import { RumPerformanceEntryType } from '../../../browser/performanceCollection' -import { LifeCycleEventType } from '../../lifeCycle' import { trackLoadingTime } from './trackLoadingTime' const BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY = (PAGE_ACTIVITY_VALIDATION_DELAY * 0.8) as Duration @@ -59,22 +58,19 @@ describe('trackLoadingTime', () => { domMutationObservable.notify() clock.tick(AFTER_PAGE_ACTIVITY_END_DELAY) - expect(loadingTimeCallback).toHaveBeenCalledTimes(1) - expect(loadingTimeCallback).toHaveBeenCalledWith(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) + expect(loadingTimeCallback).toHaveBeenCalledOnceWith(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) }) it('should use loadEventEnd for initial view when having no activity', () => { loadType = ViewLoadingType.INITIAL_LOAD - const { lifeCycle, clock } = setupBuilder.build() + const { clock } = setupBuilder.build() const entry = createPerformanceEntry(RumPerformanceEntryType.NAVIGATION) - lifeCycle.notify(LifeCycleEventType.PERFORMANCE_ENTRIES_COLLECTED, [entry]) setLoadEvent(entry.loadEventEnd) clock.tick(PAGE_ACTIVITY_END_DELAY) - expect(loadingTimeCallback).toHaveBeenCalledTimes(1) - expect(loadingTimeCallback).toHaveBeenCalledWith(entry.loadEventEnd) + expect(loadingTimeCallback).toHaveBeenCalledOnceWith(entry.loadEventEnd) }) it('should use loadEventEnd for initial view when load event is bigger than computed loading time', () => { @@ -87,8 +83,7 @@ describe('trackLoadingTime', () => { domMutationObservable.notify() clock.tick(AFTER_PAGE_ACTIVITY_END_DELAY) - expect(loadingTimeCallback).toHaveBeenCalledTimes(1) - expect(loadingTimeCallback).toHaveBeenCalledWith(LOAD_EVENT_AFTER_ACTIVITY_TIMING) + expect(loadingTimeCallback).toHaveBeenCalledOnceWith(LOAD_EVENT_AFTER_ACTIVITY_TIMING) }) it('should use computed loading time for initial view when load event is smaller than computed loading time', () => { @@ -102,8 +97,7 @@ describe('trackLoadingTime', () => { domMutationObservable.notify() clock.tick(AFTER_PAGE_ACTIVITY_END_DELAY) - expect(loadingTimeCallback).toHaveBeenCalledTimes(1) - expect(loadingTimeCallback).toHaveBeenCalledWith(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) + expect(loadingTimeCallback).toHaveBeenCalledOnceWith(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) }) it('should use computed loading time from time origin for initial view', () => { @@ -125,7 +119,6 @@ describe('trackLoadingTime', () => { clock.tick(AFTER_PAGE_ACTIVITY_END_DELAY) clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) - expect(loadingTimeCallback).toHaveBeenCalledTimes(1) - expect(loadingTimeCallback).toHaveBeenCalledWith(addDuration(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY, CLOCK_GAP)) + expect(loadingTimeCallback).toHaveBeenCalledOnceWith(addDuration(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY, CLOCK_GAP)) }) }) diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackNavigationTimings.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackNavigationTimings.spec.ts index 0b173df1d3..69e0aa7a00 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackNavigationTimings.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackNavigationTimings.spec.ts @@ -27,8 +27,7 @@ describe('trackNavigationTimings', () => { createPerformanceEntry(RumPerformanceEntryType.NAVIGATION), ]) - expect(navigationTimingsCallback).toHaveBeenCalledTimes(1) - expect(navigationTimingsCallback).toHaveBeenCalledWith({ + expect(navigationTimingsCallback).toHaveBeenCalledOnceWith({ firstByte: 123 as Duration, domComplete: 456 as Duration, domContentLoaded: 345 as Duration, From 8d719a51acf5c374854d399ad376792186eabfd5 Mon Sep 17 00:00:00 2001 From: Aymeric Mortemousque Date: Wed, 20 Sep 2023 15:42:06 +0200 Subject: [PATCH 08/10] Fix test --- .../domain/view/viewMetrics/trackLoadingTime.spec.ts | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts index 00c71a552e..5fc0a89ad7 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts @@ -1,5 +1,5 @@ import type { RelativeTime, Duration } from '@datadog/browser-core' -import { addDuration, clocksNow } from '@datadog/browser-core' +import { addDuration, clocksOrigin } from '@datadog/browser-core' import { ViewLoadingType } from '../../../rawRumEvent.types' import type { TestSetupBuilder } from '../../../../test' import { createPerformanceEntry, setup } from '../../../../test' @@ -32,7 +32,7 @@ describe('trackLoadingTime', () => { domMutationObservable, configuration, loadType, - clocksNow(), + clocksOrigin(), loadingTimeCallback ) setLoadEvent = loadingTimeTracking.setLoadEvent @@ -102,18 +102,19 @@ describe('trackLoadingTime', () => { it('should use computed loading time from time origin for initial view', () => { loadType = ViewLoadingType.INITIAL_LOAD - const { domMutationObservable, clock } = setupBuilder.build() // introduce a gap between time origin and tracking start // ensure that `load event > activity delay` and `load event < activity delay + clock gap` // to make the test fail if the clock gap is not correctly taken into account const CLOCK_GAP = (LOAD_EVENT_AFTER_ACTIVITY_TIMING - BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY + 1) as Duration - clock.tick(CLOCK_GAP) + setupBuilder.clock!.tick(CLOCK_GAP) + + const { domMutationObservable, clock } = setupBuilder.build() clock.tick(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY) - setLoadEvent(LOAD_EVENT_AFTER_ACTIVITY_TIMING) + setLoadEvent(LOAD_EVENT_BEFORE_ACTIVITY_TIMING) domMutationObservable.notify() clock.tick(AFTER_PAGE_ACTIVITY_END_DELAY) From 5170675dfc9aed10328894fc5fe3ebaae3e23d55 Mon Sep 17 00:00:00 2001 From: Aymeric Mortemousque Date: Wed, 20 Sep 2023 16:39:28 +0200 Subject: [PATCH 09/10] Scope trackScrollMetrics tests --- .../view/viewMetrics/trackLoadingTime.spec.ts | 2 - .../viewMetrics/trackScrollMetrics.spec.ts | 56 ++++++++----------- 2 files changed, 24 insertions(+), 34 deletions(-) diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts index 5fc0a89ad7..b3de9be376 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackLoadingTime.spec.ts @@ -4,7 +4,6 @@ import { ViewLoadingType } from '../../../rawRumEvent.types' import type { TestSetupBuilder } from '../../../../test' import { createPerformanceEntry, setup } from '../../../../test' import { PAGE_ACTIVITY_END_DELAY, PAGE_ACTIVITY_VALIDATION_DELAY } from '../../waitPageActivityEnd' -import { THROTTLE_VIEW_UPDATE_PERIOD } from '../trackViews' import { RumPerformanceEntryType } from '../../../browser/performanceCollection' import { trackLoadingTime } from './trackLoadingTime' @@ -118,7 +117,6 @@ describe('trackLoadingTime', () => { domMutationObservable.notify() clock.tick(AFTER_PAGE_ACTIVITY_END_DELAY) - clock.tick(THROTTLE_VIEW_UPDATE_PERIOD) expect(loadingTimeCallback).toHaveBeenCalledOnceWith(addDuration(BEFORE_PAGE_ACTIVITY_VALIDATION_DELAY, CLOCK_GAP)) }) diff --git a/packages/rum-core/src/domain/view/viewMetrics/trackScrollMetrics.spec.ts b/packages/rum-core/src/domain/view/viewMetrics/trackScrollMetrics.spec.ts index 5d0518589f..75f1d8d66d 100644 --- a/packages/rum-core/src/domain/view/viewMetrics/trackScrollMetrics.spec.ts +++ b/packages/rum-core/src/domain/view/viewMetrics/trackScrollMetrics.spec.ts @@ -1,13 +1,10 @@ -import type { RelativeTime, Subscription, TimeStamp } from '@datadog/browser-core' +import type { Duration, RelativeTime, Subscription, TimeStamp } from '@datadog/browser-core' import { DOM_EVENT, Observable, isIE } from '@datadog/browser-core' -import type { Clock } from '@datadog/browser-core/test' -import { createNewEvent, mockClock } from '@datadog/browser-core/test' +import { createNewEvent } from '@datadog/browser-core/test' import type { TestSetupBuilder } from '../../../../test' import { setup } from '../../../../test' import type { RumConfiguration } from '../../configuration' -import type { ViewTest } from '../setupViewTest.specHelper' -import { setupViewTest } from '../setupViewTest.specHelper' -import type { ScrollValues } from './trackScrollMetrics' +import type { ScrollMetrics, ScrollValues } from './trackScrollMetrics' import { createScrollValuesObservable, trackScrollMetrics } from './trackScrollMetrics' describe('createScrollValuesObserver', () => { @@ -52,69 +49,64 @@ describe('createScrollValuesObserver', () => { describe('trackScrollMetrics', () => { let setupBuilder: TestSetupBuilder - let viewTest: ViewTest - let stopTrackScrollMetrics: () => void - let callbackSpy: jasmine.Spy - let clock: Clock + let scrollMetricsCallback: jasmine.Spy<(metrics: ScrollMetrics) => void> const scrollObservable = new Observable() beforeEach(() => { + scrollMetricsCallback = jasmine.createSpy() setupBuilder = setup() - .withFakeLocation('/foo') - .beforeBuild((buildContext) => { - viewTest = setupViewTest(buildContext) - return viewTest - }) - callbackSpy = jasmine.createSpy('callback') - stopTrackScrollMetrics = trackScrollMetrics( - {} as RumConfiguration, - { relative: 0 as RelativeTime, timeStamp: 0 as TimeStamp }, - callbackSpy, - scrollObservable - ).stop - clock = mockClock() + .withFakeClock() + .beforeBuild(({ configuration }) => + trackScrollMetrics( + configuration, + { relative: 0 as RelativeTime, timeStamp: 0 as TimeStamp }, + scrollMetricsCallback, + scrollObservable + ) + ) }) afterEach(() => { - stopTrackScrollMetrics() document.body.innerHTML = '' setupBuilder.cleanup() - clock.cleanup() }) const updateScrollValues = (scrollValues: ScrollValues) => { - clock.tick(100) + setupBuilder.clock!.tick(100) scrollObservable.notify(scrollValues) } it('should update scroll height and scroll depth', () => { + setupBuilder.build() updateScrollValues({ scrollDepth: 700, scrollHeight: 2000, scrollTop: 100 }) - expect(callbackSpy).toHaveBeenCalledOnceWith({ + expect(scrollMetricsCallback).toHaveBeenCalledOnceWith({ maxDepth: 700, maxDepthScrollHeight: 2000, - maxDepthTime: 100, + maxDepthTime: 100 as Duration, maxDepthScrollTop: 100, }) }) it('should update time and scroll height only if it has increased', () => { + setupBuilder.build() updateScrollValues({ scrollDepth: 700, scrollHeight: 2000, scrollTop: 100 }) updateScrollValues({ scrollDepth: 700, scrollHeight: 1900, scrollTop: 100 }) - expect(callbackSpy).toHaveBeenCalledOnceWith({ + expect(scrollMetricsCallback).toHaveBeenCalledOnceWith({ maxDepth: 700, maxDepthScrollHeight: 2000, - maxDepthTime: 100, + maxDepthTime: 100 as Duration, maxDepthScrollTop: 100, }) }) it('should update max depth only if it has increased', () => { + setupBuilder.build() updateScrollValues({ scrollDepth: 700, scrollHeight: 2000, scrollTop: 100 }) updateScrollValues({ scrollDepth: 600, scrollHeight: 2000, scrollTop: 0 }) - expect(callbackSpy).toHaveBeenCalledOnceWith({ + expect(scrollMetricsCallback).toHaveBeenCalledOnceWith({ maxDepth: 700, maxDepthScrollHeight: 2000, - maxDepthTime: 100, + maxDepthTime: 100 as Duration, maxDepthScrollTop: 100, }) }) From 271831d85d42628fe2e996abddea5257d97e736d Mon Sep 17 00:00:00 2001 From: Aymeric Mortemousque Date: Wed, 20 Sep 2023 17:26:13 +0200 Subject: [PATCH 10/10] Fix tests on IE --- packages/rum-core/src/domain/view/trackViews.spec.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/packages/rum-core/src/domain/view/trackViews.spec.ts b/packages/rum-core/src/domain/view/trackViews.spec.ts index 51ede086b3..36632eaa45 100644 --- a/packages/rum-core/src/domain/view/trackViews.spec.ts +++ b/packages/rum-core/src/domain/view/trackViews.spec.ts @@ -17,6 +17,7 @@ import type { ViewEvent } from './trackViews' import { SESSION_KEEP_ALIVE_INTERVAL, THROTTLE_VIEW_UPDATE_PERIOD, KEEP_TRACKING_AFTER_VIEW_DELAY } from './trackViews' import type { ViewTest } from './setupViewTest.specHelper' import { setupViewTest } from './setupViewTest.specHelper' +import { isLayoutShiftSupported } from './viewMetrics/trackCumulativeLayoutShift' describe('track views automatically', () => { let setupBuilder: TestSetupBuilder @@ -380,6 +381,9 @@ describe('view metrics', () => { describe('common view metrics', () => { it('should be updated when notified with a PERFORMANCE_ENTRY_COLLECTED event (throttled)', () => { + if (!isLayoutShiftSupported()) { + pending('CLS web vital not supported') + } const { lifeCycle, clock } = setupBuilder.withFakeClock().build() const { getViewUpdateCount, getViewUpdate } = viewTest @@ -402,6 +406,9 @@ describe('view metrics', () => { }) it('should not be updated after view end', () => { + if (!isLayoutShiftSupported()) { + pending('CLS web vital not supported') + } const { lifeCycle, clock } = setupBuilder.withFakeClock().build() const { getViewUpdate, getViewUpdateCount, getViewCreateCount, startView } = viewTest startView()