Skip to content

Commit

Permalink
feat: report reason for recording start (#1452)
Browse files Browse the repository at this point in the history
Co-authored-by: Manoel Aranda Neto <[email protected]>
  • Loading branch information
pauldambra and marandaneto authored Oct 15, 2024
1 parent f6d62d8 commit 5e20147
Show file tree
Hide file tree
Showing 4 changed files with 162 additions and 70 deletions.
122 changes: 84 additions & 38 deletions cypress/e2e/session-recording.cy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,23 +60,25 @@ function ensureActivitySendsSnapshots(initial = true) {
.wait('@session-recording')
.then(() => {
cy.phCaptures({ full: true }).then((captures) => {
expect(captures.map((c) => c.event)).to.deep.equal(['$snapshot'])
expect(captures[0]['properties']['$snapshot_data']).to.have.length.above(14).and.below(40)
const capturedSnapshot = captures.find((e) => e.event === '$snapshot')
expect(capturedSnapshot).not.to.be.undefined

expect(capturedSnapshot['properties']['$snapshot_data']).to.have.length.above(14).and.below(40)
// a meta and then a full snapshot
expect(captures[0]['properties']['$snapshot_data'][0].type).to.equal(4) // meta
expect(captures[0]['properties']['$snapshot_data'][1].type).to.equal(2) // full_snapshot
expect(capturedSnapshot['properties']['$snapshot_data'][0].type).to.equal(4) // meta
expect(capturedSnapshot['properties']['$snapshot_data'][1].type).to.equal(2) // full_snapshot

if (initial) {
expectSessionOptionsCustomEvent(captures[0]['properties']['$snapshot_data'][2])
expectPostHogConfigCustomEvent(captures[0]['properties']['$snapshot_data'][3])
expectSessionOptionsCustomEvent(capturedSnapshot['properties']['$snapshot_data'][2])
expectPostHogConfigCustomEvent(capturedSnapshot['properties']['$snapshot_data'][3])
} else {
expectSessionOptionsCustomEvent(captures[0]['properties']['$snapshot_data'][2])
expectPostHogConfigCustomEvent(captures[0]['properties']['$snapshot_data'][3])
expectSessionIdChangedCustomEvent(captures[0]['properties']['$snapshot_data'][4])
expectSessionOptionsCustomEvent(capturedSnapshot['properties']['$snapshot_data'][2])
expectPostHogConfigCustomEvent(capturedSnapshot['properties']['$snapshot_data'][3])
expectSessionIdChangedCustomEvent(capturedSnapshot['properties']['$snapshot_data'][4])
}

// Making a set from the rest should all be 3 - incremental snapshots
const remainder = captures[0]['properties']['$snapshot_data'].slice(initial ? 4 : 5)
const remainder = capturedSnapshot['properties']['$snapshot_data'].slice(initial ? 4 : 5)
expect(Array.from(new Set(remainder.map((s) => s.type)))).to.deep.equal([3])
})
})
Expand Down Expand Up @@ -112,6 +114,9 @@ describe('Session recording', () => {
describe('array.full.js', () => {
it('captures session events', () => {
start({
options: {
session_recording: {},
},
decideResponseOverrides: {
isAuthenticated: false,
sessionRecording: {
Expand All @@ -128,9 +133,13 @@ describe('Session recording', () => {
.type('hello posthog!')
.wait('@session-recording')
.then(() => {
cy.posthog().invoke('capture', 'test_registered_property')
cy.phCaptures({ full: true }).then((captures) => {
// should be a pageview and a $snapshot
expect(captures.map((c) => c.event)).to.deep.equal(['$pageview', '$snapshot'])
expect(captures.map((c) => c.event)).to.deep.equal([
'$pageview',
'$snapshot',
'test_registered_property',
])

expect(captures[1]['properties']['$snapshot_data']).to.have.length.above(33).and.below(40)
// a meta and then a full snapshot
Expand All @@ -141,6 +150,10 @@ describe('Session recording', () => {
// Making a set from the rest should all be 3 - incremental snapshots
const incrementalSnapshots = captures[1]['properties']['$snapshot_data'].slice(4)
expect(Array.from(new Set(incrementalSnapshots.map((s) => s.type)))).to.deep.eq([3])

expect(captures[2]['properties']['$session_recording_start_reason']).to.equal(
'recording_initialized'
)
})
})
})
Expand Down Expand Up @@ -180,6 +193,8 @@ describe('Session recording', () => {
loaded: (ph) => {
ph.sessionRecording._forceAllowLocalhostNetworkCapture = true
},

session_recording: {},
},
})

Expand Down Expand Up @@ -271,6 +286,9 @@ describe('Session recording', () => {
describe('array.js', () => {
beforeEach(() => {
start({
options: {
session_recording: {},
},
decideResponseOverrides: {
isAuthenticated: false,
sessionRecording: {
Expand Down Expand Up @@ -386,10 +404,11 @@ describe('Session recording', () => {
it('continues capturing to the same session when the page reloads', () => {
let sessionId: string | null = null

// cypress time handling can confuse when to run full snapshot, let's force that to happen...
cy.get('[data-cy-input]').type('hello world! ')
cy.wait('@session-recording').then(() => {
cy.phCaptures({ full: true }).then((captures) => {
expect(captures.map((c) => c.event)).to.deep.equal(['$pageview', '$snapshot'])

captures.forEach((c) => {
if (isNull(sessionId)) {
sessionId = c.properties['$session_id']
Expand All @@ -404,7 +423,9 @@ describe('Session recording', () => {
cy.resetPhCaptures()
// and refresh the page
cy.reload()
cy.posthogInit({})
cy.posthogInit({
session_recording: {},
})
cy.wait('@decide')
cy.wait('@recorder-script')

Expand All @@ -418,10 +439,13 @@ describe('Session recording', () => {
cy.phCaptures({ full: true }).then((captures) => {
// should be a $snapshot for the current session
expect(captures.map((c) => c.event)).to.deep.equal(['$pageview', '$snapshot'])

expect(captures[0].properties['$session_id']).to.equal(sessionId)
expect(captures[1].properties['$session_id']).to.equal(sessionId)

expect(captures[1]['properties']['$snapshot_data']).to.have.length.above(0)
const capturedSnapshot = captures[1]
expect(capturedSnapshot.properties['$session_id']).to.equal(sessionId)

expect(capturedSnapshot['properties']['$snapshot_data']).to.have.length.above(0)

/**
* the snapshots will look a little like:
Expand All @@ -433,28 +457,28 @@ describe('Session recording', () => {

// page reloaded so we will start with a full snapshot
// a meta and then a full snapshot
expect(captures[1]['properties']['$snapshot_data'][0].type).to.equal(4) // meta
expect(captures[1]['properties']['$snapshot_data'][1].type).to.equal(2) // full_snapshot
expect(capturedSnapshot['properties']['$snapshot_data'][0].type).to.equal(4) // meta
expect(capturedSnapshot['properties']['$snapshot_data'][1].type).to.equal(2) // full_snapshot

// these custom events should always be in the same order, but computers
// we don't care if they are present and in a changing order
const customEvents = sortByTag([
captures[1]['properties']['$snapshot_data'][2],
captures[1]['properties']['$snapshot_data'][3],
captures[1]['properties']['$snapshot_data'][4],
capturedSnapshot['properties']['$snapshot_data'][2],
capturedSnapshot['properties']['$snapshot_data'][3],
capturedSnapshot['properties']['$snapshot_data'][4],
])
expectPageViewCustomEvent(customEvents[0])
expectPostHogConfigCustomEvent(customEvents[1])
expectSessionOptionsCustomEvent(customEvents[2])

const xPositions = []
for (let i = 5; i < captures[1]['properties']['$snapshot_data'].length; i++) {
expect(captures[1]['properties']['$snapshot_data'][i].type).to.equal(3)
expect(captures[1]['properties']['$snapshot_data'][i].data.source).to.equal(
for (let i = 5; i < capturedSnapshot['properties']['$snapshot_data'].length; i++) {
expect(capturedSnapshot['properties']['$snapshot_data'][i].type).to.equal(3)
expect(capturedSnapshot['properties']['$snapshot_data'][i].data.source).to.equal(
6,
JSON.stringify(captures[1]['properties']['$snapshot_data'][i])
JSON.stringify(capturedSnapshot['properties']['$snapshot_data'][i])
)
xPositions.push(captures[1]['properties']['$snapshot_data'][i].data.positions[0].x)
xPositions.push(capturedSnapshot['properties']['$snapshot_data'][i].data.positions[0].x)
}

// even though we trigger 4 events, only 2 snapshots should be captured
Expand All @@ -478,11 +502,20 @@ describe('Session recording', () => {
.type('hello posthog!')
.wait('@session-recording')
.then(() => {
cy.posthog().invoke('capture', 'test_registered_property')
cy.phCaptures({ full: true }).then((captures) => {
// should be a pageview and a $snapshot
expect(captures.map((c) => c.event)).to.deep.equal(['$pageview', '$snapshot'])
expect(captures.map((c) => c.event)).to.deep.equal([
'$pageview',
'$snapshot',
'test_registered_property',
])

expect(captures[1]['properties']['$session_id']).to.be.a('string')
firstSessionId = captures[1]['properties']['$session_id']

expect(captures[2]['properties']['$session_recording_start_reason']).to.equal(
'recording_initialized'
)
})
})

Expand All @@ -505,24 +538,29 @@ describe('Session recording', () => {
.type('hello posthog!')
.wait('@session-recording', { timeout: 10000 })
.then(() => {
cy.posthog().invoke('capture', 'test_registered_property')
cy.phCaptures({ full: true }).then((captures) => {
// should be a pageview and a $snapshot
expect(captures[0].event).to.equal('$snapshot')
const capturedSnapshot = captures[0]
expect(capturedSnapshot.event).to.equal('$snapshot')

expect(captures[0]['properties']['$session_id']).to.be.a('string')
expect(captures[0]['properties']['$session_id']).not.to.eq(firstSessionId)
expect(capturedSnapshot['properties']['$session_id']).to.be.a('string')
expect(capturedSnapshot['properties']['$session_id']).not.to.eq(firstSessionId)

expect(captures[0]['properties']['$snapshot_data']).to.have.length.above(0)
expect(captures[0]['properties']['$snapshot_data'][0].type).to.equal(4) // meta
expect(captures[0]['properties']['$snapshot_data'][1].type).to.equal(2) // full_snapshot
expect(capturedSnapshot['properties']['$snapshot_data']).to.have.length.above(0)
expect(capturedSnapshot['properties']['$snapshot_data'][0].type).to.equal(4) // meta
expect(capturedSnapshot['properties']['$snapshot_data'][1].type).to.equal(2) // full_snapshot

expect(captures[1].event).to.equal('test_registered_property')
expect(captures[1]['properties']['$session_recording_start_reason']).to.equal(
'session_id_changed'
)
})
})
})

it('starts a new recording after calling reset', () => {
cy.phCaptures({ full: true }).then((captures) => {
// should be a pageview at the beginning
expect(captures.map((c) => c.event)).to.deep.equal(['$pageview'])
expect(captures[0].event).to.eq('$pageview')
})
cy.resetPhCaptures()

Expand Down Expand Up @@ -553,6 +591,9 @@ describe('Session recording', () => {
describe('with sampling', () => {
beforeEach(() => {
start({
options: {
session_recording: {},
},
decideResponseOverrides: {
isAuthenticated: false,
sessionRecording: {
Expand All @@ -575,7 +616,6 @@ describe('Session recording', () => {
.wait(200) // can't wait on call to session recording, it's not going to happen
.then(() => {
cy.phCaptures({ full: true }).then((captures) => {
// should be a pageview and a $snapshot
expect(captures.map((c) => c.event)).to.deep.equal(['$pageview'])
})
})
Expand Down Expand Up @@ -611,6 +651,12 @@ describe('Session recording', () => {
// no call to session-recording yet
})

cy.posthog().invoke('capture', 'test_registered_property')
cy.phCaptures({ full: true }).then((captures) => {
expect((captures || []).map((c) => c.event)).to.deep.equal(['$pageview', 'test_registered_property'])
expect(captures[1]['properties']['$session_recording_start_reason']).to.equal('sampling_override')
})

cy.resetPhCaptures()

cy.get('[data-cy-input]').type('hello posthog!')
Expand Down
15 changes: 12 additions & 3 deletions src/__tests__/extensions/replay/sessionrecording.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@ import {
pluginEvent,
} from '@rrweb/types'
import Mock = jest.Mock
import { ConsentManager } from '../../../consent'

// Type and source defined here designate a non-user-generated recording event

Expand Down Expand Up @@ -243,14 +244,22 @@ describe('SessionRecording', () => {
config: config,
capture: jest.fn(),
persistence: postHogPersistence,
onFeatureFlags: (cb: (flags: string[]) => void) => {
onFeatureFlags: (
cb: (flags: string[], variants: Record<string, string | boolean>) => void
): (() => void) => {
onFeatureFlagsCallback = cb
return () => {}
},
sessionManager: sessionManager,
requestRouter: new RequestRouter({ config } as any),
_addCaptureHook: addCaptureHookMock,
consent: { isOptedOut: () => false },
} as unknown as PostHog
consent: {
isOptedOut(): boolean {
return false
},
} as unknown as ConsentManager,
register_for_session() {},
} as Partial<PostHog> as PostHog

loadScriptMock.mockImplementation((_ph, _path, callback) => {
addRRwebToWindow()
Expand Down
Loading

0 comments on commit 5e20147

Please sign in to comment.