From 0ee120f6dcef24525826a7c47b4e8e5a5e1bf30e Mon Sep 17 00:00:00 2001 From: Paul D'Ambra Date: Fri, 27 Dec 2024 06:33:49 +0000 Subject: [PATCH] port two more tests --- playwright/session-recording.spec.ts | 125 +++++++++++++++++++++++++-- 1 file changed, 117 insertions(+), 8 deletions(-) diff --git a/playwright/session-recording.spec.ts b/playwright/session-recording.spec.ts index d35e904de..a28f9c55b 100644 --- a/playwright/session-recording.spec.ts +++ b/playwright/session-recording.spec.ts @@ -1,5 +1,51 @@ import { expect, test } from './utils/posthog-js-assets-mocks' import { captures, fullCaptures, resetCaptures, start, WindowWithPostHog } from './utils/setup' +import { Page } from '@playwright/test' +import { isUndefined } from '../src/utils/type-utils' + +async function ensureRecordingIsStopped(page: Page) { + resetCaptures() + + await page.locator('[data-cy-input]').type('hello posthog!') + // wait a little since we can't wait for the absence of a call to /ses/* + await page.waitForTimeout(250) + expect(fullCaptures.length).toBe(0) +} + +async function ensureActivitySendsSnapshots(page: Page, expectedCustomTags: string[] = []) { + resetCaptures() + + const responsePromise = page.waitForResponse('**/ses/*') + await page.locator('[data-cy-input]').type('hello posthog!') + await responsePromise + + const capturedSnapshot = fullCaptures.find((e) => e.event === '$snapshot') + if (isUndefined(capturedSnapshot)) { + throw new Error('No snapshot captured') + } + + const capturedSnapshotData = capturedSnapshot['properties']['$snapshot_data'].filter((s: any) => s.type !== 6) + // first a meta and then a full snapshot + expect(capturedSnapshotData.shift()?.type).toEqual(4) + expect(capturedSnapshotData.shift()?.type).toEqual(2) + + // now the list should be all custom events until it is incremental + // and then only incremental snapshots + const customEvents = [] + let seenIncremental = false + for (const snapshot of capturedSnapshotData) { + if (snapshot.type === 5) { + expect(seenIncremental).toBeFalsy() + customEvents.push(snapshot) + } else if (snapshot.type === 3) { + seenIncremental = true + } else { + throw new Error(`Unexpected snapshot type: ${snapshot.type}`) + } + } + const customEventTags = customEvents.map((s) => s.data.tag) + expect(customEventTags).toEqual(expectedCustomTags) +} test.describe('Session recording', () => { test.describe('array.full.js', () => { @@ -55,11 +101,7 @@ test.describe('Session recording', () => { test.fixme('network capture', () => {}) test.describe('array.js', () => { - test.fixme('captures session events', () => {}) - test.fixme('captures snapshots when the mouse moves', () => {}) - test.fixme('continues capturing to the same session when the page reloads', () => {}) - test.fixme('starts a new recording after calling reset', () => {}) - test('rotates sessions after 24 hours', async ({ page, context }) => { + test.beforeEach(async ({ page, context }) => { await start( { options: { @@ -78,7 +120,74 @@ test.describe('Session recording', () => { page, context ) + await page.waitForResponse('**/recorder.js*') + expect(captures).toEqual(['$pageview']) + resetCaptures() + }) + + 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) + }) + + 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']) + resetCaptures() + + 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 lastCaptured = fullCaptures[fullCaptures.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) + expect(capturedMouseMoves[1].data.positions.length).toBe(1) + // 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[0].x).toBeGreaterThan(200) + }) + + test.fixme('continues capturing to the same session when the page reloads', () => {}) + test.fixme('starts a new recording after calling reset', () => {}) + test('rotates sessions after 24 hours', async ({ page }) => { await page.locator('[data-cy-input]').fill('hello world! ') const responsePromise = page.waitForResponse('**/ses/*') await page.locator('[data-cy-input]').fill('hello posthog!') @@ -89,12 +198,12 @@ test.describe('Session recording', () => { ph?.capture('test_registered_property') }) - expect(captures).toEqual(['$pageview', '$snapshot', 'test_registered_property']) + expect(captures).toEqual(['$snapshot', 'test_registered_property']) - const firstSessionId = fullCaptures[1]['properties']['$session_id'] + const firstSessionId = fullCaptures[0]['properties']['$session_id'] expect(typeof firstSessionId).toEqual('string') expect(firstSessionId.trim().length).toBeGreaterThan(10) - expect(fullCaptures[2]['properties']['$session_recording_start_reason']).toEqual('recording_initialized') + expect(fullCaptures[1]['properties']['$session_recording_start_reason']).toEqual('recording_initialized') resetCaptures() await page.evaluate(() => {