Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

🐛 Fix replay visual viewport resize support #2891

Merged
merged 3 commits into from
Jul 25, 2024
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
156 changes: 96 additions & 60 deletions packages/rum/src/domain/record/record.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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)
})
Comment on lines -157 to -182
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Basically covered here:

it('full snapshot records should contain Meta, Focus, FullSnapshot', () => {


it('adds a Focus record on when taking a full snapshot', () => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Now covered here:

it('full snapshot records should contain Meta, Focus, FullSnapshot', () => {

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', () => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already covered here:

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", () => {
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Already covered here:

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('<hr class="toto" />', createShadow())
Expand Down Expand Up @@ -422,6 +369,95 @@ describe('record', () => {
})
})

describe('should collect records', () => {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👏 praise: ‏looks great

let div: HTMLDivElement
let input: HTMLInputElement
let audio: HTMLAudioElement
beforeEach(() => {
div = appendElement('<div target></div>') as HTMLDivElement
input = appendElement('<input target />') as HTMLInputElement
audio = appendElement('<audio controls autoplay target></audio>') 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({
Expand Down
3 changes: 2 additions & 1 deletion packages/rum/src/domain/record/record.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
trackStyleSheet,
trackViewEnd,
trackViewportResize,
trackVisualViewportResize,
} from './trackers'
import { createElementsScrollPositions } from './elementsScrollPositions'
import type { ShadowRootsController } from './shadowRootsController'
Expand Down Expand Up @@ -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()
Expand Down
55 changes: 54 additions & 1 deletion packages/rum/src/domain/record/startFullSnapshots.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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),
},
])
)
})
})
2 changes: 1 addition & 1 deletion packages/rum/src/domain/record/trackers/index.ts
Original file line number Diff line number Diff line change
@@ -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'
Expand Down
Original file line number Diff line number Diff line change
@@ -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'
Expand All @@ -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()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand All @@ -30,7 +30,7 @@ describe('trackViewportResize', () => {
elementsScrollPositions,
})

viewportResizeTracker = tackVisualViewportResize(configuration, visualViewportResizeCallback)
viewportResizeTracker = trackVisualViewportResize(configuration, visualViewportResizeCallback)

registerCleanupTask(() => {
viewportResizeTracker.stop()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export function trackViewportResize(
}
}

export function tackVisualViewportResize(
export function trackVisualViewportResize(
configuration: RumConfiguration,
visualViewportResizeCb: VisualViewportResizeCallback
): Tracker {
Expand Down
1 change: 1 addition & 0 deletions packages/rum/test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ export * from './mutationPayloadValidator'
export * from './nodes'
export * from './segments'
export * from './readReplayPayload'
export * from './rumFrustrationEvent'
25 changes: 25 additions & 0 deletions packages/rum/test/rumFrustrationEvent.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { relativeNow, timeStampNow } from '@datadog/browser-core'
import type { RawRumActionEvent } from '@datadog/browser-rum-core'
import { ActionType, FrustrationType, RumEventType } from '@datadog/browser-rum-core'
import type { RawRumEventCollectedData } from 'packages/rum-core/src/domain/lifeCycle'

export function createRumFrustrationEvent(mouseEvent: MouseEvent): RawRumEventCollectedData<RawRumActionEvent> {
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] },
}
}