diff --git a/playwright/session-recording-array-full.spec.ts b/playwright/session-recording-array-full.spec.ts new file mode 100644 index 000000000..18a00ea93 --- /dev/null +++ b/playwright/session-recording-array-full.spec.ts @@ -0,0 +1,49 @@ +import { expect, test, WindowWithPostHog } from './utils/posthog-playwright-test-base' +import { start } from './utils/setup' + +const startOptions = { + options: { + session_recording: {}, + }, + decideResponseOverrides: { + sessionRecording: { + endpoint: '/ses/', + }, + capturePerformance: true, + autocapture_opt_out: true, + }, + url: './playground/cypress-full/index.html', +} + +test.describe('session recording in array.full.js', () => { + test('captures session events', async ({ page, context }) => { + await start(startOptions, page, context) + + const responsePromise = page.waitForResponse('**/ses/*') + await page.locator('[data-cy-input]').fill('hello posthog!') + await responsePromise + + await page.evaluate(() => { + const ph = (window as WindowWithPostHog).posthog + ph?.capture('test_registered_property') + }) + + const capturedEvents = await page.capturedEvents() + expect(capturedEvents.map((x) => x.event)).toEqual(['$pageview', '$snapshot', 'test_registered_property']) + + // don't care about network payloads here + const snapshotData = capturedEvents[1]['properties']['$snapshot_data'].filter((s: any) => s.type !== 6) + + // a meta and then a full snapshot + expect(snapshotData[0].type).toEqual(4) // meta + expect(snapshotData[1].type).toEqual(2) // full_snapshot + expect(snapshotData[2].type).toEqual(5) // custom event with remote config + expect(snapshotData[3].type).toEqual(5) // custom event with options + expect(snapshotData[4].type).toEqual(5) // custom event with posthog config + // Making a set from the rest should all be 3 - incremental snapshots + const incrementalSnapshots = snapshotData.slice(5) + expect(Array.from(new Set(incrementalSnapshots.map((s: any) => s.type)))).toStrictEqual([3]) + + expect(capturedEvents[2]['properties']['$session_recording_start_reason']).toEqual('recording_initialized') + }) +}) diff --git a/playwright/session-recording-network-recorder.spec.ts b/playwright/session-recording-network-recorder.spec.ts new file mode 100644 index 000000000..b231ef146 --- /dev/null +++ b/playwright/session-recording-network-recorder.spec.ts @@ -0,0 +1,5 @@ +import { test } from './utils/posthog-playwright-test-base' + +test.describe('Session recording - array.js', () => { + test.fixme('network capture', () => {}) +}) diff --git a/playwright/session-recording-sampling.spec.ts b/playwright/session-recording-sampling.spec.ts new file mode 100644 index 000000000..3c8acce95 --- /dev/null +++ b/playwright/session-recording-sampling.spec.ts @@ -0,0 +1,78 @@ +import { expect, test, WindowWithPostHog } from './utils/posthog-playwright-test-base' +import { start } from './utils/setup' + +const startOptions = { + options: { + session_recording: {}, + }, + decideResponseOverrides: { + sessionRecording: { + endpoint: '/ses/', + }, + capturePerformance: true, + autocapture_opt_out: true, + }, + url: './playground/cypress/index.html', +} + +test.describe('Session recording - sampling', () => { + const sampleZeroStartOptions = { + ...startOptions, + decideResponseOverrides: { + ...startOptions.decideResponseOverrides, + sessionRecording: { + ...startOptions.decideResponseOverrides.sessionRecording, + sampleRate: '0', + }, + }, + } + test.beforeEach(async ({ page, context }) => { + await start(startOptions, page, context) + + await page.waitForResponse('**/recorder.js*') + const capturedEvents = await page.evaluate(() => (window as WindowWithPostHog).capturedEvents || []) + expect(capturedEvents.map((x) => x.event)).toEqual(['$pageview']) + await page.resetCapturedEvents() + }) + + test('does not capture events when sampling is set to 0', async ({ page }) => { + await page.locator('[data-cy-input]').fill('hello posthog!') + // because it doesn't make sense to wait for a snapshot event that won't happen + await page.waitForTimeout(250) + + const capturedEvents = await page.capturedEvents() + expect(capturedEvents).toEqual([]) + }) + + test('can override sampling when starting session recording', async ({ page, context }) => { + await page.evaluate(() => { + const ph = (window as WindowWithPostHog).posthog + ph?.startSessionRecording({ sampling: true }) + ph?.capture('test_registered_property') + }) + const capturedEvents = await page.capturedEvents() + expect(capturedEvents.map((x) => x.event)).toEqual(['test_registered_property']) + expect(capturedEvents[0]['properties']['$session_recording_start_reason']).toEqual('sampling_overridden') + + // sampling override survives a page refresh + await page.resetCapturedEvents() + await page.reload() + + await start( + { + ...sampleZeroStartOptions, + type: 'reload', + }, + page, + context + ) + await page.waitForResponse('**/recorder.js*') + const responsePromise = page.waitForResponse('**/ses/*') + await page.locator('[data-cy-input]').fill('hello posthog!') + await responsePromise + + const afterReloadCapturedEvents = await page.capturedEvents() + const lastCaptured = afterReloadCapturedEvents[afterReloadCapturedEvents.length - 1] + expect(lastCaptured['event']).toEqual('$snapshot') + }) +}) diff --git a/playwright/session-recording.spec.ts b/playwright/session-recording.spec.ts index 6beae76ec..a96e30738 100644 --- a/playwright/session-recording.spec.ts +++ b/playwright/session-recording.spec.ts @@ -64,313 +64,203 @@ const startOptions = { url: './playground/cypress/index.html', } -test.describe('Session recording', () => { - const arrayFullStartOptions = { - ...startOptions, - url: './playground/cypress-full/index.html', - } +test.describe('Session recording - array.js', () => { + test.beforeEach(async ({ page, context }) => { + await start(startOptions, page, context) + await page.waitForResponse('**/recorder.js*') + const capturedEvents = await page.evaluate(() => (window as WindowWithPostHog).capturedEvents || []) + expect(capturedEvents.map((x) => x.event)).toEqual(['$pageview']) + await page.resetCapturedEvents() + }) - test.describe('array.full.js', () => { - test('captures session events', async ({ page, context }) => { - await start(arrayFullStartOptions, page, context) - - await page.locator('[data-cy-input]').fill('hello world! ') - await page.waitForTimeout(500) - const responsePromise = page.waitForResponse('**/ses/*') - await page.locator('[data-cy-input]').fill('hello posthog!') - await responsePromise - - await page.evaluate(() => { - const ph = (window as WindowWithPostHog).posthog - ph?.capture('test_registered_property') - }) - - const capturedEvents = await page.capturedEvents() - expect(capturedEvents.map((x) => x.event)).toEqual(['$pageview', '$snapshot', 'test_registered_property']) - - // don't care about network payloads here - const snapshotData = capturedEvents[1]['properties']['$snapshot_data'].filter((s: any) => s.type !== 6) - - // a meta and then a full snapshot - expect(snapshotData[0].type).toEqual(4) // meta - expect(snapshotData[1].type).toEqual(2) // full_snapshot - expect(snapshotData[2].type).toEqual(5) // custom event with remote config - expect(snapshotData[3].type).toEqual(5) // custom event with options - expect(snapshotData[4].type).toEqual(5) // custom event with posthog config - // Making a set from the rest should all be 3 - incremental snapshots - const incrementalSnapshots = snapshotData.slice(5) - expect(Array.from(new Set(incrementalSnapshots.map((s: any) => s.type)))).toStrictEqual([3]) - - expect(capturedEvents[2]['properties']['$session_recording_start_reason']).toEqual('recording_initialized') + test('captures session events', async ({ page }) => { + const startingSessionId = await page.evaluate(() => { + const ph = (window as WindowWithPostHog).posthog + return ph?.get_session_id() }) - }) + await ensureActivitySendsSnapshots(page, ['$remote_config_received', '$session_options', '$posthog_config']) - test.fixme('network capture', () => {}) + await page.evaluate(() => { + const ph = (window as WindowWithPostHog).posthog + ph?.stopSessionRecording() + }) + + await ensureRecordingIsStopped(page) - test.describe('array.js', () => { - test.beforeEach(async ({ page, context }) => { - await start(startOptions, page, context) - await page.waitForResponse('**/recorder.js*') - const capturedEvents = await page.evaluate(() => (window as WindowWithPostHog).capturedEvents || []) - expect(capturedEvents.map((x) => x.event)).toEqual(['$pageview']) - await page.resetCapturedEvents() + await page.evaluate(() => { + const ph = (window as WindowWithPostHog).posthog + ph?.startSessionRecording() }) - test('captures session events', async ({ page }) => { - const startingSessionId = await page.evaluate(() => { - const ph = (window as WindowWithPostHog).posthog - return ph?.get_session_id() - }) - await ensureActivitySendsSnapshots(page, ['$remote_config_received', '$session_options', '$posthog_config']) - - await page.evaluate(() => { - const ph = (window as WindowWithPostHog).posthog - ph?.stopSessionRecording() - }) - - await ensureRecordingIsStopped(page) - - await page.evaluate(() => { - const ph = (window as WindowWithPostHog).posthog - ph?.startSessionRecording() - }) - - await ensureActivitySendsSnapshots(page, ['$session_options', '$posthog_config']) - - // the session id is not rotated by stopping and starting the recording - const finishingSessionId = await page.evaluate(() => { - const ph = (window as WindowWithPostHog).posthog - return ph?.get_session_id() - }) - expect(startingSessionId).toEqual(finishingSessionId) + await ensureActivitySendsSnapshots(page, ['$session_options', '$posthog_config']) + + // the session id is not rotated by stopping and starting the recording + const finishingSessionId = await page.evaluate(() => { + const ph = (window as WindowWithPostHog).posthog + return ph?.get_session_id() }) + expect(startingSessionId).toEqual(finishingSessionId) + }) - test('captures snapshots when the mouse moves', async ({ page }) => { - // first make sure the page is booted and recording - await ensureActivitySendsSnapshots(page, ['$remote_config_received', '$session_options', '$posthog_config']) - await page.resetCapturedEvents() - - const responsePromise = page.waitForResponse('**/ses/*') - await page.mouse.move(200, 300) - await page.waitForTimeout(25) - await page.mouse.move(210, 300) - await page.waitForTimeout(25) - await page.mouse.move(220, 300) - await page.waitForTimeout(25) - await page.mouse.move(240, 300) - await page.waitForTimeout(25) - await responsePromise - - const capturedEvents = await page.capturedEvents() - const lastCaptured = capturedEvents[capturedEvents.length - 1] - expect(lastCaptured['event']).toEqual('$snapshot') - - const capturedMouseMoves = lastCaptured['properties']['$snapshot_data'].filter((s: any) => { - return s.type === 3 && !!s.data?.positions?.length - }) - expect(capturedMouseMoves.length).toBe(2) - expect(capturedMouseMoves[0].data.positions.length).toBe(1) - expect(capturedMouseMoves[0].data.positions[0].x).toBe(200) - // smoothing varies if this value picks up 220 or 240 - // all we _really_ care about is that it's greater than the previous value - expect(capturedMouseMoves[1].data.positions.length).toBeGreaterThan(0) - expect(capturedMouseMoves[1].data.positions[0].x).toBeGreaterThan(200) + test('captures snapshots when the mouse moves', async ({ page }) => { + // first make sure the page is booted and recording + await ensureActivitySendsSnapshots(page, ['$remote_config_received', '$session_options', '$posthog_config']) + await page.resetCapturedEvents() + + const responsePromise = page.waitForResponse('**/ses/*') + await page.mouse.move(200, 300) + await page.waitForTimeout(25) + await page.mouse.move(210, 300) + await page.waitForTimeout(25) + await page.mouse.move(220, 300) + await page.waitForTimeout(25) + await page.mouse.move(240, 300) + await page.waitForTimeout(25) + await responsePromise + + const capturedEvents = await page.capturedEvents() + const lastCaptured = capturedEvents[capturedEvents.length - 1] + expect(lastCaptured['event']).toEqual('$snapshot') + + const capturedMouseMoves = lastCaptured['properties']['$snapshot_data'].filter((s: any) => { + return s.type === 3 && !!s.data?.positions?.length }) + expect(capturedMouseMoves.length).toBe(2) + expect(capturedMouseMoves[0].data.positions.length).toBe(1) + expect(capturedMouseMoves[0].data.positions[0].x).toBe(200) + // smoothing varies if this value picks up 220 or 240 + // all we _really_ care about is that it's greater than the previous value + expect(capturedMouseMoves[1].data.positions.length).toBeGreaterThan(0) + expect(capturedMouseMoves[1].data.positions[0].x).toBeGreaterThan(200) + }) + + test('continues capturing to the same session when the page reloads', async ({ page }) => { + const responsePromise = page.waitForResponse('**/ses/*') + await page.locator('[data-cy-input]').fill('hello posthog!') + await responsePromise - test('continues capturing to the same session when the page reloads', async ({ page }) => { - const responsePromise = page.waitForResponse('**/ses/*') - await page.locator('[data-cy-input]').fill('hello posthog!') - await responsePromise - - const firstSessionId = await page.evaluate(() => { - const ph = (window as WindowWithPostHog).posthog - return ph?.get_session_id() - }) - const capturedEvents = await page.capturedEvents() - expect(new Set(capturedEvents.map((c) => c['properties']['$session_id']))).toEqual( - new Set([firstSessionId]) - ) - - const waitForRecorder = page.waitForResponse('**/recorder.js*') - await start( - { - ...startOptions, - type: 'reload', - }, - page, - page.context() - ) - - await page.resetCapturedEvents() - await waitForRecorder - - await page.evaluate(() => { - const ph = (window as WindowWithPostHog).posthog - ph?.capture('some_custom_event') - }) - const capturedAfterReload = await page.capturedEvents() - expect(capturedAfterReload.map((x) => x.event)).toEqual(['some_custom_event']) - expect(capturedAfterReload[0]['properties']['$session_id']).toEqual(firstSessionId) - expect(capturedAfterReload[0]['properties']['$session_recording_start_reason']).toEqual( - 'recording_initialized' - ) - expect(capturedAfterReload[0]['properties']['$recording_status']).toEqual('active') - - await page.resetCapturedEvents() - - const moreResponsePromise = page.waitForResponse('**/ses/*') - await page.locator('[data-cy-input]').type('hello posthog!') - await moreResponsePromise - - const capturedAfterActivity = await page.capturedEvents() - expect(capturedAfterActivity.map((x) => x.event)).toEqual(['$snapshot']) - expect(capturedAfterActivity[0]['properties']['$session_id']).toEqual(firstSessionId) + const firstSessionId = await page.evaluate(() => { + const ph = (window as WindowWithPostHog).posthog + return ph?.get_session_id() }) + const capturedEvents = await page.capturedEvents() + expect(new Set(capturedEvents.map((c) => c['properties']['$session_id']))).toEqual(new Set([firstSessionId])) + + const waitForRecorder = page.waitForResponse('**/recorder.js*') + await start( + { + ...startOptions, + type: 'reload', + }, + page, + page.context() + ) - test('starts a new recording after calling reset', async ({ page }) => { - await page.resetCapturedEvents() - const startingSessionId = await page.evaluate(() => { - const ph = (window as WindowWithPostHog).posthog - return ph?.get_session_id() - }) - expect(startingSessionId).not.toBeNull() + await page.resetCapturedEvents() + await waitForRecorder - await ensureActivitySendsSnapshots(page, ['$remote_config_received', '$session_options', '$posthog_config']) + await page.evaluate(() => { + const ph = (window as WindowWithPostHog).posthog + ph?.capture('some_custom_event') + }) + const capturedAfterReload = await page.capturedEvents() + expect(capturedAfterReload.map((x) => x.event)).toEqual(['some_custom_event']) + expect(capturedAfterReload[0]['properties']['$session_id']).toEqual(firstSessionId) + expect(capturedAfterReload[0]['properties']['$session_recording_start_reason']).toEqual('recording_initialized') + expect(capturedAfterReload[0]['properties']['$recording_status']).toEqual('active') - await page.resetCapturedEvents() - await page.evaluate(() => { - const ph = (window as WindowWithPostHog).posthog - ph?.reset() - }) + await page.resetCapturedEvents() - const responsePromise = page.waitForResponse('**/ses/*') - await page.locator('[data-cy-input]').fill('hello posthog!') - await responsePromise + const moreResponsePromise = page.waitForResponse('**/ses/*') + await page.locator('[data-cy-input]').type('hello posthog!') + await moreResponsePromise - const capturedEvents = await page.capturedEvents() - const postResetSessionIds = new Set(capturedEvents.map((c) => c['properties']['$session_id'])) - expect(postResetSessionIds.size).toEqual(1) - const replayCapturedSessionId = Array.from(postResetSessionIds)[0] + const capturedAfterActivity = await page.capturedEvents() + expect(capturedAfterActivity.map((x) => x.event)).toEqual(['$snapshot']) + expect(capturedAfterActivity[0]['properties']['$session_id']).toEqual(firstSessionId) + }) - expect(replayCapturedSessionId).not.toEqual(startingSessionId) + test('starts a new recording after calling reset', async ({ page }) => { + await page.resetCapturedEvents() + const startingSessionId = await page.evaluate(() => { + const ph = (window as WindowWithPostHog).posthog + return ph?.get_session_id() }) + expect(startingSessionId).not.toBeNull() - test('rotates sessions after 24 hours', async ({ page }) => { - const responsePromise = page.waitForResponse('**/ses/*') - await page.locator('[data-cy-input]').fill('hello posthog!') - await responsePromise - - await page.evaluate(() => { - const ph = (window as WindowWithPostHog).posthog - ph?.capture('test_registered_property') - }) - - const capturedEvents = await page.capturedEvents() - expect(capturedEvents.map((x) => x.event)).toEqual(['$snapshot', 'test_registered_property']) - - const firstSessionId = capturedEvents[0]['properties']['$session_id'] - expect(typeof firstSessionId).toEqual('string') - expect(firstSessionId.trim().length).toBeGreaterThan(10) - expect(capturedEvents[1]['properties']['$session_recording_start_reason']).toEqual('recording_initialized') - - await page.resetCapturedEvents() - await page.evaluate(() => { - const ph = (window as WindowWithPostHog).posthog - const activityTs = ph?.sessionManager?.['_sessionActivityTimestamp'] - const startTs = ph?.sessionManager?.['_sessionStartTimestamp'] - const timeout = ph?.sessionManager?.['_sessionTimeoutMs'] - - // move the session values back, - // so that the next event appears to be greater than timeout since those values - // @ts-expect-error can ignore that TS thinks these things might be null - ph.sessionManager['_sessionActivityTimestamp'] = activityTs - timeout - 1000 - // @ts-expect-error can ignore that TS thinks these things might be null - ph.sessionManager['_sessionStartTimestamp'] = startTs - timeout - 1000 - }) - - const anotherResponsePromise = page.waitForResponse('**/ses/*') - // using fill here means the session id doesn't rotate, must need some kind of user interaction - await page.locator('[data-cy-input]').type('hello posthog!') - await anotherResponsePromise - - await page.evaluate(() => { - const ph = (window as WindowWithPostHog).posthog - ph?.capture('test_registered_property') - }) - - const capturedEventsAfter24Hours = await page.capturedEvents() - expect(capturedEventsAfter24Hours.map((x) => x.event)).toEqual(['$snapshot', 'test_registered_property']) - - expect(capturedEventsAfter24Hours[0]['properties']['$session_id']).not.toEqual(firstSessionId) - expect(capturedEventsAfter24Hours[0]['properties']['$snapshot_data'][0].type).toEqual(4) // meta - expect(capturedEventsAfter24Hours[0]['properties']['$snapshot_data'][1].type).toEqual(2) // full_snapshot - - expect(capturedEventsAfter24Hours[1]['properties']['$session_id']).not.toEqual(firstSessionId) - expect(capturedEventsAfter24Hours[1]['properties']['$session_recording_start_reason']).toEqual( - 'session_id_changed' - ) + await ensureActivitySendsSnapshots(page, ['$remote_config_received', '$session_options', '$posthog_config']) + + await page.resetCapturedEvents() + await page.evaluate(() => { + const ph = (window as WindowWithPostHog).posthog + ph?.reset() }) + + const responsePromise = page.waitForResponse('**/ses/*') + await page.locator('[data-cy-input]').fill('hello posthog!') + await responsePromise + + const capturedEvents = await page.capturedEvents() + const postResetSessionIds = new Set(capturedEvents.map((c) => c['properties']['$session_id'])) + expect(postResetSessionIds.size).toEqual(1) + const replayCapturedSessionId = Array.from(postResetSessionIds)[0] + + expect(replayCapturedSessionId).not.toEqual(startingSessionId) }) - test.describe('with sampling', () => { - const sampleZeroStartOptions = { - ...startOptions, - decideResponseOverrides: { - ...startOptions.decideResponseOverrides, - sessionRecording: { - ...startOptions.decideResponseOverrides.sessionRecording, - sampleRate: '0', - }, - }, - } - test.beforeEach(async ({ page, context }) => { - await start(startOptions, page, context) + test('rotates sessions after 24 hours', async ({ page }) => { + const responsePromise = page.waitForResponse('**/ses/*') + await page.locator('[data-cy-input]').fill('hello posthog!') + await responsePromise - await page.waitForResponse('**/recorder.js*') - const capturedEvents = await page.evaluate(() => (window as WindowWithPostHog).capturedEvents || []) - expect(capturedEvents.map((x) => x.event)).toEqual(['$pageview']) - await page.resetCapturedEvents() + await page.evaluate(() => { + const ph = (window as WindowWithPostHog).posthog + ph?.capture('test_registered_property') }) - test('does not capture events when sampling is set to 0', async ({ page }) => { - await page.locator('[data-cy-input]').fill('hello posthog!') - // because it doesn't make sense to wait for a snapshot event that won't happen - await page.waitForTimeout(250) - - const capturedEvents = await page.capturedEvents() - expect(capturedEvents).toEqual([]) + const capturedEvents = await page.capturedEvents() + expect(capturedEvents.map((x) => x.event)).toEqual(['$snapshot', 'test_registered_property']) + + const firstSessionId = capturedEvents[0]['properties']['$session_id'] + expect(typeof firstSessionId).toEqual('string') + expect(firstSessionId.trim().length).toBeGreaterThan(10) + expect(capturedEvents[1]['properties']['$session_recording_start_reason']).toEqual('recording_initialized') + + await page.resetCapturedEvents() + await page.evaluate(() => { + const ph = (window as WindowWithPostHog).posthog + const activityTs = ph?.sessionManager?.['_sessionActivityTimestamp'] + const startTs = ph?.sessionManager?.['_sessionStartTimestamp'] + const timeout = ph?.sessionManager?.['_sessionTimeoutMs'] + + // move the session values back, + // so that the next event appears to be greater than timeout since those values + // @ts-expect-error can ignore that TS thinks these things might be null + ph.sessionManager['_sessionActivityTimestamp'] = activityTs - timeout - 1000 + // @ts-expect-error can ignore that TS thinks these things might be null + ph.sessionManager['_sessionStartTimestamp'] = startTs - timeout - 1000 }) - test('can override sampling when starting session recording', async ({ page, context }) => { - await page.evaluate(() => { - const ph = (window as WindowWithPostHog).posthog - ph?.startSessionRecording({ sampling: true }) - ph?.capture('test_registered_property') - }) - const capturedEvents = await page.capturedEvents() - expect(capturedEvents.map((x) => x.event)).toEqual(['test_registered_property']) - expect(capturedEvents[0]['properties']['$session_recording_start_reason']).toEqual('sampling_overridden') - - // sampling override survives a page refresh - await page.resetCapturedEvents() - await page.reload() - - await start( - { - ...sampleZeroStartOptions, - type: 'reload', - }, - page, - context - ) - await page.waitForResponse('**/recorder.js*') - const responsePromise = page.waitForResponse('**/ses/*') - await page.locator('[data-cy-input]').fill('hello posthog!') - await responsePromise - - const afterReloadCapturedEvents = await page.capturedEvents() - const lastCaptured = afterReloadCapturedEvents[afterReloadCapturedEvents.length - 1] - expect(lastCaptured['event']).toEqual('$snapshot') + const anotherResponsePromise = page.waitForResponse('**/ses/*') + // using fill here means the session id doesn't rotate, must need some kind of user interaction + await page.locator('[data-cy-input]').type('hello posthog!') + await anotherResponsePromise + + await page.evaluate(() => { + const ph = (window as WindowWithPostHog).posthog + ph?.capture('test_registered_property') }) + + const capturedEventsAfter24Hours = await page.capturedEvents() + expect(capturedEventsAfter24Hours.map((x) => x.event)).toEqual(['$snapshot', 'test_registered_property']) + + expect(capturedEventsAfter24Hours[0]['properties']['$session_id']).not.toEqual(firstSessionId) + expect(capturedEventsAfter24Hours[0]['properties']['$snapshot_data'][0].type).toEqual(4) // meta + expect(capturedEventsAfter24Hours[0]['properties']['$snapshot_data'][1].type).toEqual(2) // full_snapshot + + expect(capturedEventsAfter24Hours[1]['properties']['$session_id']).not.toEqual(firstSessionId) + expect(capturedEventsAfter24Hours[1]['properties']['$session_recording_start_reason']).toEqual( + 'session_id_changed' + ) }) })