diff --git a/packages/rum-core/src/index.ts b/packages/rum-core/src/index.ts
index 497d5c2ab0..1292120b31 100644
--- a/packages/rum-core/src/index.ts
+++ b/packages/rum-core/src/index.ts
@@ -22,7 +22,7 @@ export {
} from './domainContext.types'
export { ReplayStats, ActionType, RumEventType, FrustrationType, RawRumActionEvent } from './rawRumEvent.types'
export { startRum } from './boot/startRum'
-export { LifeCycle, LifeCycleEventType } from './domain/lifeCycle'
+export { LifeCycle, LifeCycleEventType, RawRumEventCollectedData } from './domain/lifeCycle'
export { ViewCreatedEvent } from './domain/view/trackViews'
export { ViewContexts, ViewContext, startViewContexts } from './domain/contexts/viewContexts'
export { RumSessionManager, RumSession } from './domain/rumSessionManager'
diff --git a/packages/rum/src/domain/record/record.spec.ts b/packages/rum/src/domain/record/record.spec.ts
index 486a0b4346..93e00bcad7 100644
--- a/packages/rum/src/domain/record/record.spec.ts
+++ b/packages/rum/src/domain/record/record.spec.ts
@@ -3,14 +3,19 @@ import type { RumConfiguration, ViewCreatedEvent } from '@datadog/browser-rum-co
import { LifeCycle, LifeCycleEventType } from '@datadog/browser-rum-core'
import type { Clock } from '@datadog/browser-core/test'
import { createNewEvent, collectAsyncCalls, registerCleanupTask } from '@datadog/browser-core/test'
-import { findElement, findFullSnapshot, findNode, recordsPerFullSnapshot } from '../../../test'
+import {
+ findElement,
+ findFullSnapshot,
+ findNode,
+ recordsPerFullSnapshot,
+ createRumFrustrationEvent,
+} from '../../../test'
import type {
BrowserIncrementalSnapshotRecord,
BrowserMutationData,
BrowserRecord,
DocumentFragmentNode,
ElementNode,
- FocusRecord,
ScrollData,
} from '../../types'
import { NodeType, RecordType, IncrementalSource } from '../../types'
@@ -146,64 +151,6 @@ describe('record', () => {
})
})
- describe('Focus records', () => {
- let hasFocus: boolean
-
- beforeEach(() => {
- hasFocus = true
- spyOn(Document.prototype, 'hasFocus').and.callFake(() => hasFocus)
- })
-
- it('adds an initial Focus record when starting to record', () => {
- startRecording()
- expect(getEmittedRecords()[1]).toEqual({
- type: RecordType.Focus,
- timestamp: jasmine.any(Number),
- data: {
- has_focus: true,
- },
- })
- })
-
- it('adds a Focus record on focus', () => {
- startRecording()
- emitSpy.calls.reset()
-
- window.dispatchEvent(createNewEvent('focus'))
- expect(getEmittedRecords()[0].type).toBe(RecordType.Focus)
- })
-
- it('adds a Focus record on blur', () => {
- startRecording()
- emitSpy.calls.reset()
-
- window.dispatchEvent(createNewEvent('blur'))
- expect(getEmittedRecords()[0].type).toBe(RecordType.Focus)
- })
-
- it('adds a Focus record on when taking a full snapshot', () => {
- startRecording()
- emitSpy.calls.reset()
-
- // trigger full snapshot by starting a new view
- newView()
-
- expect(getEmittedRecords()[1].type).toBe(RecordType.Focus)
- })
-
- it('set has_focus to true if the document has the focus', () => {
- hasFocus = true
- startRecording()
- expect((getEmittedRecords()[1] as FocusRecord).data.has_focus).toBe(true)
- })
-
- it("set has_focus to false if the document doesn't have the focus", () => {
- hasFocus = false
- startRecording()
- expect((getEmittedRecords()[1] as FocusRecord).data.has_focus).toBe(false)
- })
- })
-
describe('Shadow dom', () => {
it('should record a simple mutation inside a shadow root', () => {
const element = appendElement('
', createShadow())
@@ -422,6 +369,95 @@ describe('record', () => {
})
})
+ describe('should collect records', () => {
+ let div: HTMLDivElement
+ let input: HTMLInputElement
+ let audio: HTMLAudioElement
+ beforeEach(() => {
+ div = appendElement('') as HTMLDivElement
+ input = appendElement('') as HTMLInputElement
+ audio = appendElement('') as HTMLAudioElement
+ startRecording()
+ emitSpy.calls.reset()
+ })
+
+ it('move', () => {
+ document.body.dispatchEvent(createNewEvent('mousemove', { clientX: 1, clientY: 2 }))
+ expect(getEmittedRecords()[0].type).toBe(RecordType.IncrementalSnapshot)
+ expect((getEmittedRecords()[0] as BrowserIncrementalSnapshotRecord).data.source).toBe(IncrementalSource.MouseMove)
+ })
+
+ it('interaction', () => {
+ document.body.dispatchEvent(createNewEvent('click', { clientX: 1, clientY: 2 }))
+ expect((getEmittedRecords()[0] as BrowserIncrementalSnapshotRecord).data.source).toBe(
+ IncrementalSource.MouseInteraction
+ )
+ })
+
+ it('scroll', () => {
+ div.dispatchEvent(createNewEvent('scroll', { target: div }))
+
+ expect(getEmittedRecords()[0].type).toBe(RecordType.IncrementalSnapshot)
+ expect((getEmittedRecords()[0] as BrowserIncrementalSnapshotRecord).data.source).toBe(IncrementalSource.Scroll)
+ })
+
+ it('viewport resize', () => {
+ window.dispatchEvent(createNewEvent('resize'))
+
+ expect(getEmittedRecords()[0].type).toBe(RecordType.IncrementalSnapshot)
+ expect((getEmittedRecords()[0] as BrowserIncrementalSnapshotRecord).data.source).toBe(
+ IncrementalSource.ViewportResize
+ )
+ })
+
+ it('input', () => {
+ input.value = 'newValue'
+ input.dispatchEvent(createNewEvent('input', { target: input }))
+
+ expect(getEmittedRecords()[0].type).toBe(RecordType.IncrementalSnapshot)
+ expect((getEmittedRecords()[0] as BrowserIncrementalSnapshotRecord).data.source).toBe(IncrementalSource.Input)
+ })
+
+ it('media interaction', () => {
+ audio.dispatchEvent(createNewEvent('play', { target: audio }))
+
+ expect(getEmittedRecords()[0].type).toBe(RecordType.IncrementalSnapshot)
+ expect((getEmittedRecords()[0] as BrowserIncrementalSnapshotRecord).data.source).toBe(
+ IncrementalSource.MediaInteraction
+ )
+ })
+
+ it('focus', () => {
+ window.dispatchEvent(createNewEvent('blur'))
+
+ expect(getEmittedRecords()[0].type).toBe(RecordType.Focus)
+ })
+
+ it('visual viewport resize', () => {
+ if (!window.visualViewport) {
+ pending('visualViewport not supported')
+ }
+
+ visualViewport!.dispatchEvent(createNewEvent('resize'))
+ expect(getEmittedRecords()[0].type).toBe(RecordType.VisualViewport)
+ })
+
+ it('frustration', () => {
+ lifeCycle.notify(
+ LifeCycleEventType.RAW_RUM_EVENT_COLLECTED,
+ createRumFrustrationEvent(new MouseEvent('pointerup'))
+ )
+
+ expect(getEmittedRecords()[0].type).toBe(RecordType.FrustrationRecord)
+ })
+
+ it('view end event', () => {
+ lifeCycle.notify(LifeCycleEventType.VIEW_ENDED, {} as any)
+
+ expect(getEmittedRecords()[0].type).toBe(RecordType.ViewEnd)
+ })
+ })
+
function startRecording() {
lifeCycle = new LifeCycle()
recordApi = record({
diff --git a/packages/rum/src/domain/record/record.ts b/packages/rum/src/domain/record/record.ts
index f5b027d1ea..3dbbf9b737 100644
--- a/packages/rum/src/domain/record/record.ts
+++ b/packages/rum/src/domain/record/record.ts
@@ -15,6 +15,7 @@ import {
trackStyleSheet,
trackViewEnd,
trackViewportResize,
+ trackVisualViewportResize,
} from './trackers'
import { createElementsScrollPositions } from './elementsScrollPositions'
import type { ShadowRootsController } from './shadowRootsController'
@@ -79,7 +80,7 @@ export function record(options: RecordOptions): RecordAPI {
trackMediaInteraction(configuration, emitAndComputeStats),
trackStyleSheet(emitAndComputeStats),
trackFocus(configuration, emitAndComputeStats),
- trackViewportResize(configuration, emitAndComputeStats),
+ trackVisualViewportResize(configuration, emitAndComputeStats),
trackFrustration(lifeCycle, emitAndComputeStats, recordIds),
trackViewEnd(lifeCycle, (viewEndRecord) => {
flushMutations()
diff --git a/packages/rum/src/domain/record/startFullSnapshots.spec.ts b/packages/rum/src/domain/record/startFullSnapshots.spec.ts
index a8026ed705..264f2a2ddc 100644
--- a/packages/rum/src/domain/record/startFullSnapshots.spec.ts
+++ b/packages/rum/src/domain/record/startFullSnapshots.spec.ts
@@ -2,7 +2,7 @@ import type { RumConfiguration, ViewCreatedEvent } from '@datadog/browser-rum-co
import { LifeCycle, LifeCycleEventType } from '@datadog/browser-rum-core'
import type { TimeStamp } from '@datadog/browser-core'
import { isIE, noop } from '@datadog/browser-core'
-import type { BrowserRecord } from '../../types'
+import { RecordType, type BrowserRecord } from '../../types'
import { startFullSnapshots } from './startFullSnapshots'
import { createElementsScrollPositions } from './elementsScrollPositions'
import type { ShadowRootsController } from './shadowRootsController'
@@ -51,4 +51,57 @@ describe('startFullSnapshots', () => {
expect(records[1].timestamp).toEqual(1)
expect(records[2].timestamp).toEqual(1)
})
+
+ it('full snapshot records should contain Meta, Focus, FullSnapshot', () => {
+ const records = fullSnapshotCallback.calls.mostRecent().args[0]
+
+ expect(records).toEqual(
+ jasmine.arrayContaining([
+ {
+ data: {
+ height: jasmine.any(Number),
+ href: window.location.href,
+ width: jasmine.any(Number),
+ },
+ type: RecordType.Meta,
+ timestamp: jasmine.any(Number),
+ },
+ {
+ data: {
+ has_focus: document.hasFocus(),
+ },
+ type: RecordType.Focus,
+ timestamp: jasmine.any(Number),
+ },
+ {
+ data: {
+ node: jasmine.any(Object),
+ initialOffset: {
+ left: jasmine.any(Number),
+ top: jasmine.any(Number),
+ },
+ },
+ type: RecordType.FullSnapshot,
+ timestamp: jasmine.any(Number),
+ },
+ ])
+ )
+ })
+
+ it('full snapshot records should contain visualViewport when supported', () => {
+ if (!window.visualViewport) {
+ pending('visualViewport not supported')
+ }
+ const records = fullSnapshotCallback.calls.mostRecent().args[0]
+
+ expect(records).toEqual(
+ jasmine.arrayContaining([
+ {
+ data: jasmine.any(Object),
+ type: RecordType.VisualViewport,
+ timestamp: jasmine.any(Number),
+ },
+ ])
+ )
+ })
})
diff --git a/packages/rum/src/domain/record/trackers/index.ts b/packages/rum/src/domain/record/trackers/index.ts
index 907c8f1236..8bb61d1b9d 100644
--- a/packages/rum/src/domain/record/trackers/index.ts
+++ b/packages/rum/src/domain/record/trackers/index.ts
@@ -1,7 +1,7 @@
export { trackMove } from './trackMove'
export { trackMouseInteraction } from './trackMouseInteraction'
export { trackScroll } from './trackScroll'
-export { trackViewportResize, tackVisualViewportResize } from './trackViewportResize'
+export { trackViewportResize, trackVisualViewportResize } from './trackViewportResize'
export { trackMediaInteraction } from './trackMediaInteraction'
export { trackStyleSheet } from './trackStyleSheet'
export { trackFocus } from './trackFocus'
diff --git a/packages/rum/src/domain/record/trackers/trackFrustration.spec.ts b/packages/rum/src/domain/record/trackers/trackFrustration.spec.ts
index c1762fac1e..b59275ac56 100644
--- a/packages/rum/src/domain/record/trackers/trackFrustration.spec.ts
+++ b/packages/rum/src/domain/record/trackers/trackFrustration.spec.ts
@@ -1,8 +1,9 @@
-import { isIE, relativeNow, timeStampNow } from '@datadog/browser-core'
+import { isIE } from '@datadog/browser-core'
import type { RawRumActionEvent } from '@datadog/browser-rum-core'
-import { ActionType, LifeCycle, LifeCycleEventType, RumEventType, FrustrationType } from '@datadog/browser-rum-core'
+import { ActionType, LifeCycle, LifeCycleEventType } from '@datadog/browser-rum-core'
import type { RawRumEventCollectedData } from 'packages/rum-core/src/domain/lifeCycle'
import { registerCleanupTask } from '@datadog/browser-core/test'
+import { createRumFrustrationEvent } from '../../../../test'
import { RecordType } from '../../../types'
import type { RecordIds } from '../recordIds'
import { initRecordIds } from '../recordIds'
@@ -26,24 +27,7 @@ describe('trackFrustration', () => {
frustrationsCallbackSpy = jasmine.createSpy()
recordIds = initRecordIds()
- rumData = {
- startTime: relativeNow(),
- rawRumEvent: {
- date: timeStampNow(),
- type: RumEventType.ACTION,
- action: {
- id: '123e4567-e89b-12d3-a456-426614174000',
- type: ActionType.CLICK,
- frustration: {
- type: [FrustrationType.DEAD_CLICK],
- },
- target: {
- name: '123e4567-e89b-12d3-a456-426614174000',
- },
- },
- },
- domainContext: { events: [mouseEvent] },
- }
+ rumData = createRumFrustrationEvent(mouseEvent)
registerCleanupTask(() => {
frustrationTracker.stop()
diff --git a/packages/rum/src/domain/record/trackers/trackViewportResize.spec.ts b/packages/rum/src/domain/record/trackers/trackViewportResize.spec.ts
index c08838fb55..f4c8a5132a 100644
--- a/packages/rum/src/domain/record/trackers/trackViewportResize.spec.ts
+++ b/packages/rum/src/domain/record/trackers/trackViewportResize.spec.ts
@@ -7,7 +7,7 @@ import { createElementsScrollPositions } from '../elementsScrollPositions'
import { RecordType } from '../../../types'
import { DEFAULT_CONFIGURATION, DEFAULT_SHADOW_ROOT_CONTROLLER } from './trackers.specHelper'
import type { VisualViewportResizeCallback } from './trackViewportResize'
-import { tackVisualViewportResize } from './trackViewportResize'
+import { trackVisualViewportResize } from './trackViewportResize'
import type { Tracker } from './tracker.types'
describe('trackViewportResize', () => {
@@ -30,7 +30,7 @@ describe('trackViewportResize', () => {
elementsScrollPositions,
})
- viewportResizeTracker = tackVisualViewportResize(configuration, visualViewportResizeCallback)
+ viewportResizeTracker = trackVisualViewportResize(configuration, visualViewportResizeCallback)
registerCleanupTask(() => {
viewportResizeTracker.stop()
diff --git a/packages/rum/src/domain/record/trackers/trackViewportResize.ts b/packages/rum/src/domain/record/trackers/trackViewportResize.ts
index 8e931a3aaa..5fae722f93 100644
--- a/packages/rum/src/domain/record/trackers/trackViewportResize.ts
+++ b/packages/rum/src/domain/record/trackers/trackViewportResize.ts
@@ -28,7 +28,7 @@ export function trackViewportResize(
}
}
-export function tackVisualViewportResize(
+export function trackVisualViewportResize(
configuration: RumConfiguration,
visualViewportResizeCb: VisualViewportResizeCallback
): Tracker {
diff --git a/packages/rum/test/index.ts b/packages/rum/test/index.ts
index 486c21a7d3..227e480f65 100644
--- a/packages/rum/test/index.ts
+++ b/packages/rum/test/index.ts
@@ -4,3 +4,4 @@ export * from './mutationPayloadValidator'
export * from './nodes'
export * from './segments'
export * from './readReplayPayload'
+export * from './rumFrustrationEvent'
diff --git a/packages/rum/test/rumFrustrationEvent.ts b/packages/rum/test/rumFrustrationEvent.ts
new file mode 100644
index 0000000000..ce16199672
--- /dev/null
+++ b/packages/rum/test/rumFrustrationEvent.ts
@@ -0,0 +1,24 @@
+import { relativeNow, timeStampNow } from '@datadog/browser-core'
+import type { RawRumActionEvent, RawRumEventCollectedData } from '@datadog/browser-rum-core'
+import { ActionType, FrustrationType, RumEventType } from '@datadog/browser-rum-core'
+
+export function createRumFrustrationEvent(mouseEvent: MouseEvent): RawRumEventCollectedData {
+ return {
+ startTime: relativeNow(),
+ rawRumEvent: {
+ date: timeStampNow(),
+ type: RumEventType.ACTION,
+ action: {
+ id: '123e4567-e89b-12d3-a456-426614174000',
+ type: ActionType.CLICK,
+ frustration: {
+ type: [FrustrationType.DEAD_CLICK],
+ },
+ target: {
+ name: '123e4567-e89b-12d3-a456-426614174000',
+ },
+ },
+ },
+ domainContext: { events: [mouseEvent] },
+ }
+}