Skip to content

Commit a500d14

Browse files
authored
fix(replay): wait for new config before recording decision (#3128)
## Problem had a couple tickets recently where it seems like recording is starting based on a Null config even though later receiving a config that indicates a recording SHOULDNT have started. ## Changes ![Screenshot 2026-02-20 at 3.09.27 PM.png](https://app.graphite.com/user-attachments/assets/d30097b8-968d-4d60-b085-dbc4e0af7b3c.png) wait for remote config before deciding to record or not if unload before receiving commit, clear hte buffer rather than flush it <!-- What is changed and what information would be useful to a reviewer? --> ## Release info Sub-libraries affected ### Libraries affected <!-- Please mark which libraries will require a version bump. --> - [ ] All of them - [x] posthog-js (web) - [ ] posthog-js-lite (web lite) - [ ] posthog-node - [ ] posthog-react-native - [ ] @posthog/react - [ ] @posthog/ai - [ ] @posthog/convex - [ ] @posthog/nextjs-config - [ ] @posthog/nuxt - [ ] @posthog/rollup-plugin - [ ] @posthog/webpack-plugin - [ ] @posthog/types ## Checklist - [x] Tests for new code - [ ] Accounted for the impact of any changes across different platforms - [ ] Accounted for backwards compatibility of any changes (no breaking changes!) - [ ] Took care not to unnecessarily increase the bundle size ### If releasing new changes - [x] Ran `pnpm changeset` to generate a changeset file - [x] Added the "release" label to the PR to indicate we're publishing new versions for the affected packages <!-- For more details check RELEASING.md -->
1 parent e90364f commit a500d14

20 files changed

+313
-160
lines changed

.changeset/tiny-glasses-pump.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
'posthog-js': patch
3+
---
4+
5+
wait for fresh config before recording start decision, add new recorder status, output recording started event

packages/browser/playwright/mocked/session-recording/lazy-session-recording-sampling.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, test, WindowWithPostHog } from '../utils/posthog-playwright-test-base'
2-
import { start } from '../utils/setup'
2+
import { start, waitForSessionRecordingToStart } from '../utils/setup'
33

44
const startOptions = {
55
options: {
@@ -34,6 +34,7 @@ test.describe('Session recording - sampling', () => {
3434
await start(startOptions, page, context)
3535
},
3636
})
37+
await waitForSessionRecordingToStart(page)
3738

3839
await page.expectCapturedEventsToBe(['$pageview'])
3940
await page.resetCapturedEvents()

packages/browser/playwright/mocked/session-recording/lazy-session-recording.spec.ts

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, test, WindowWithPostHog } from '../utils/posthog-playwright-test-base'
2-
import { start } from '../utils/setup'
2+
import { start, waitForSessionRecordingToStart } from '../utils/setup'
33
import { Page } from '@playwright/test'
44
import { isUndefined } from '@posthog/core'
55

@@ -99,6 +99,7 @@ test.describe('Session recording - array.js', () => {
9999
await start(startOptions, page, context)
100100
},
101101
})
102+
await waitForSessionRecordingToStart(page)
102103
await page.expectCapturedEventsToBe(['$pageview'])
103104
await page.resetCapturedEvents()
104105
})
@@ -108,7 +109,12 @@ test.describe('Session recording - array.js', () => {
108109
const ph = (window as WindowWithPostHog).posthog
109110
return ph?.get_session_id()
110111
})
111-
await ensureActivitySendsSnapshots(page, ['$remote_config_received', '$session_options', '$posthog_config'])
112+
await ensureActivitySendsSnapshots(page, [
113+
'$remote_config_received',
114+
'$session_options',
115+
'$posthog_config',
116+
'$recording_started',
117+
])
112118

113119
await page.evaluate(() => {
114120
const ph = (window as WindowWithPostHog).posthog
@@ -122,7 +128,12 @@ test.describe('Session recording - array.js', () => {
122128
ph?.startSessionRecording()
123129
})
124130

125-
await ensureActivitySendsSnapshots(page, ['$remote_config_received', '$session_options', '$posthog_config'])
131+
await ensureActivitySendsSnapshots(page, [
132+
'$remote_config_received',
133+
'$session_options',
134+
'$posthog_config',
135+
'$recording_started',
136+
])
126137

127138
// the session id is not rotated by stopping and starting the recording
128139
const finishingSessionId = await page.evaluate(() => {
@@ -134,7 +145,14 @@ test.describe('Session recording - array.js', () => {
134145

135146
test('captures snapshots when the mouse moves', async ({ page }) => {
136147
// first make sure the page is booted and recording
137-
await ensureActivitySendsSnapshots(page, ['$remote_config_received', '$session_options', '$posthog_config'])
148+
await ensureActivitySendsSnapshots(page, [
149+
'$remote_config_received',
150+
'$session_options',
151+
'$posthog_config',
152+
'$recording_started',
153+
])
154+
// Explicitly wait for recording to be fully started before timing-sensitive operations
155+
await waitForSessionRecordingToStart(page)
138156
await page.resetCapturedEvents()
139157
// Allow any pending async operations (e.g. flags loading, buffer flushes) to settle
140158
// before starting the timing-sensitive mouse move sequence
@@ -228,7 +246,12 @@ test.describe('Session recording - array.js', () => {
228246
})
229247
expect(startingSessionId).not.toBeNull()
230248

231-
await ensureActivitySendsSnapshots(page, ['$remote_config_received', '$session_options', '$posthog_config'])
249+
await ensureActivitySendsSnapshots(page, [
250+
'$remote_config_received',
251+
'$session_options',
252+
'$posthog_config',
253+
'$recording_started',
254+
])
232255

233256
await page.resetCapturedEvents()
234257
await page.evaluate(() => {
@@ -336,7 +359,12 @@ test.describe('Session recording - array.js', () => {
336359

337360
test('adds debug properties to captured events', async ({ page }) => {
338361
// make sure recording is running
339-
await ensureActivitySendsSnapshots(page, ['$remote_config_received', '$session_options', '$posthog_config'])
362+
await ensureActivitySendsSnapshots(page, [
363+
'$remote_config_received',
364+
'$session_options',
365+
'$posthog_config',
366+
'$recording_started',
367+
])
340368

341369
await page.evaluate(() => {
342370
const ph = (window as WindowWithPostHog).posthog

packages/browser/playwright/mocked/session-recording/session-linking.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, test, WindowWithPostHog } from '../utils/posthog-playwright-test-base'
2-
import { start } from '../utils/setup'
2+
import { start, waitForSessionRecordingToStart } from '../utils/setup'
33

44
const startOptions = {
55
options: {
@@ -25,6 +25,7 @@ test.describe('Session Recording - Session Linking', () => {
2525
await start(startOptions, page, context)
2626
},
2727
})
28+
await waitForSessionRecordingToStart(page)
2829
await page.expectCapturedEventsToBe(['$pageview'])
2930
await page.resetCapturedEvents()
3031
})

packages/browser/playwright/mocked/session-recording/session-recording-array-full.spec.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, test, WindowWithPostHog } from '../utils/posthog-playwright-test-base'
2-
import { start } from '../utils/setup'
2+
import { start, waitForSessionRecordingToStart } from '../utils/setup'
33

44
const startOptions = {
55
options: {
@@ -21,6 +21,7 @@ const startOptions = {
2121
test.describe('session recording in array.full.js', () => {
2222
test('captures session events', async ({ page, context }) => {
2323
await start(startOptions, page, context)
24+
await waitForSessionRecordingToStart(page)
2425

2526
await page.waitingForNetworkCausedBy({
2627
urlPatternsToWaitFor: ['**/ses/*'],
@@ -46,8 +47,9 @@ test.describe('session recording in array.full.js', () => {
4647
expect(snapshotData[2].type).toEqual(5) // custom event with remote config
4748
expect(snapshotData[3].type).toEqual(5) // custom event with options
4849
expect(snapshotData[4].type).toEqual(5) // custom event with posthog config
50+
expect(snapshotData[5].type).toEqual(5) // custom event with recording_started
4951
// Making a set from the rest should all be 3 - incremental snapshots
50-
const incrementalSnapshots = snapshotData.slice(5)
52+
const incrementalSnapshots = snapshotData.slice(6)
5153
expect(Array.from(new Set(incrementalSnapshots.map((s: any) => s.type)))).toStrictEqual([3])
5254

5355
expect(capturedEvents[2]['properties']['$session_recording_start_reason']).toEqual('recording_initialized')

packages/browser/playwright/mocked/session-recording/session-recording-idle-timeout.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, test, WindowWithPostHog } from '../utils/posthog-playwright-test-base'
2-
import { start } from '../utils/setup'
2+
import { start, waitForSessionRecordingToStart } from '../utils/setup'
33
import { Page } from '@playwright/test'
44

55
async function ensureRecordingIsStopped(page: Page) {
@@ -88,6 +88,7 @@ test.describe('Session recording - idle timeout behavior', () => {
8888
await start(startOptions, page, context)
8989
},
9090
})
91+
await waitForSessionRecordingToStart(page)
9192
await page.expectCapturedEventsToBe(['$pageview'])
9293
await page.resetCapturedEvents()
9394
})

packages/browser/playwright/mocked/session-recording/session-recording-linked-flags.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { test, WindowWithPostHog } from '../utils/posthog-playwright-test-base'
2-
import { start, StartOptions } from '../utils/setup'
2+
import { start, StartOptions, waitForRemoteConfig } from '../utils/setup'
33
import { assertThatRecordingStarted, pollUntilEventCaptured } from '../utils/event-capture-utils'
44
import { BrowserContext, Page } from '@playwright/test'
55

@@ -42,6 +42,7 @@ test.describe('Session recording - linked flags', () => {
4242
page,
4343
context
4444
)
45+
await waitForRemoteConfig(page)
4546
await page.expectCapturedEventsToBe(expectedStartingEvents)
4647
await page.resetCapturedEvents()
4748
}

packages/browser/playwright/mocked/session-recording/session-recording-network-recorder.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { test, expect } from '../utils/posthog-playwright-test-base'
2-
import { start } from '../utils/setup'
2+
import { start, waitForSessionRecordingToStart } from '../utils/setup'
33
import { Page } from '@playwright/test'
44

55
test.beforeEach(async ({ context }) => {
@@ -90,6 +90,7 @@ test.beforeEach(async ({ context }) => {
9090
)
9191
},
9292
})
93+
await waitForSessionRecordingToStart(page)
9394

9495
// also wrap after posthog is loaded
9596
await page.evaluate((isBadlyBehaved) => {

packages/browser/playwright/mocked/session-recording/session-recording-sampling.spec.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, test, WindowWithPostHog } from '../utils/posthog-playwright-test-base'
2-
import { start } from '../utils/setup'
2+
import { start, waitForSessionRecordingToStart } from '../utils/setup'
33

44
const startOptions = {
55
options: {
@@ -36,6 +36,7 @@ test.describe('Session recording - sampling', () => {
3636
await start(startOptions, page, context)
3737
},
3838
})
39+
await waitForSessionRecordingToStart(page)
3940

4041
await page.expectCapturedEventsToBe(['$pageview'])
4142
await page.resetCapturedEvents()

packages/browser/playwright/mocked/session-recording/session-recording.spec.ts

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { expect, test, WindowWithPostHog } from '../utils/posthog-playwright-test-base'
2-
import { start } from '../utils/setup'
2+
import { start, waitForSessionRecordingToStart } from '../utils/setup'
33
import { Page } from '@playwright/test'
44
import { isUndefined } from '@posthog/core'
55

@@ -101,6 +101,7 @@ test.describe('Session recording - array.js', () => {
101101
await start(startOptions, page, context)
102102
},
103103
})
104+
await waitForSessionRecordingToStart(page)
104105
await page.expectCapturedEventsToBe(['$pageview'])
105106
await page.resetCapturedEvents()
106107
})
@@ -110,7 +111,12 @@ test.describe('Session recording - array.js', () => {
110111
const ph = (window as WindowWithPostHog).posthog
111112
return ph?.get_session_id()
112113
})
113-
await ensureActivitySendsSnapshots(page, ['$remote_config_received', '$session_options', '$posthog_config'])
114+
await ensureActivitySendsSnapshots(page, [
115+
'$remote_config_received',
116+
'$session_options',
117+
'$posthog_config',
118+
'$recording_started',
119+
])
114120

115121
await page.evaluate(() => {
116122
const ph = (window as WindowWithPostHog).posthog
@@ -124,7 +130,12 @@ test.describe('Session recording - array.js', () => {
124130
ph?.startSessionRecording()
125131
})
126132

127-
await ensureActivitySendsSnapshots(page, ['$remote_config_received', '$session_options', '$posthog_config'])
133+
await ensureActivitySendsSnapshots(page, [
134+
'$remote_config_received',
135+
'$session_options',
136+
'$posthog_config',
137+
'$recording_started',
138+
])
128139

129140
// the session id is not rotated by stopping and starting the recording
130141
const finishingSessionId = await page.evaluate(() => {
@@ -136,7 +147,14 @@ test.describe('Session recording - array.js', () => {
136147

137148
test('captures snapshots when the mouse moves', async ({ page }) => {
138149
// first make sure the page is booted and recording
139-
await ensureActivitySendsSnapshots(page, ['$remote_config_received', '$session_options', '$posthog_config'])
150+
await ensureActivitySendsSnapshots(page, [
151+
'$remote_config_received',
152+
'$session_options',
153+
'$posthog_config',
154+
'$recording_started',
155+
])
156+
// Explicitly wait for recording to be fully started before timing-sensitive operations
157+
await waitForSessionRecordingToStart(page)
140158
await page.resetCapturedEvents()
141159
// Allow any pending async operations (e.g. flags loading, buffer flushes) to settle
142160
// before starting the timing-sensitive mouse move sequence
@@ -230,7 +248,12 @@ test.describe('Session recording - array.js', () => {
230248
})
231249
expect(startingSessionId).not.toBeNull()
232250

233-
await ensureActivitySendsSnapshots(page, ['$remote_config_received', '$session_options', '$posthog_config'])
251+
await ensureActivitySendsSnapshots(page, [
252+
'$remote_config_received',
253+
'$session_options',
254+
'$posthog_config',
255+
'$recording_started',
256+
])
234257

235258
await page.resetCapturedEvents()
236259
await page.evaluate(() => {
@@ -338,7 +361,12 @@ test.describe('Session recording - array.js', () => {
338361

339362
test('adds debug properties to captured events', async ({ page }) => {
340363
// make sure recording is running
341-
await ensureActivitySendsSnapshots(page, ['$remote_config_received', '$session_options', '$posthog_config'])
364+
await ensureActivitySendsSnapshots(page, [
365+
'$remote_config_received',
366+
'$session_options',
367+
'$posthog_config',
368+
'$recording_started',
369+
])
342370

343371
await page.evaluate(() => {
344372
const ph = (window as WindowWithPostHog).posthog

0 commit comments

Comments
 (0)