From c72a99cd27e9f79a6ffbfdbb069f9ce1bea2b419 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Tue, 28 Oct 2025 18:05:19 -0400 Subject: [PATCH 1/5] feat(replay): Add `traces_by_timestamp` to replay event In order to support moving our custom rrweb events to EAP, we need to send timestamps w/ trace ids so that we can identify which trace the event belongs to. In order to avoid breaking changes, we should not change the current type of trace_ids field in the replay event, instead we add a new field traces_by_timestamp --- packages/core/src/types-hoist/replay.ts | 1 + .../src/coreHandlers/handleAfterSendEvent.ts | 4 ++-- packages/replay-internal/src/types/replay.ts | 6 ++--- .../src/util/sendReplayRequest.ts | 3 ++- .../coreHandlers/handleAfterSendEvent.test.ts | 22 ++++++++++++++++--- 5 files changed, 27 insertions(+), 9 deletions(-) diff --git a/packages/core/src/types-hoist/replay.ts b/packages/core/src/types-hoist/replay.ts index 65641ce011bd..8527145cb4df 100644 --- a/packages/core/src/types-hoist/replay.ts +++ b/packages/core/src/types-hoist/replay.ts @@ -9,6 +9,7 @@ export interface ReplayEvent extends Event { replay_start_timestamp?: number; error_ids: string[]; trace_ids: string[]; + traces_by_timestamp: [number, string][]; replay_id: string; segment_id: number; replay_type: ReplayRecordingMode; diff --git a/packages/replay-internal/src/coreHandlers/handleAfterSendEvent.ts b/packages/replay-internal/src/coreHandlers/handleAfterSendEvent.ts index 4df1b62532ac..e1f4699b92b0 100644 --- a/packages/replay-internal/src/coreHandlers/handleAfterSendEvent.ts +++ b/packages/replay-internal/src/coreHandlers/handleAfterSendEvent.ts @@ -37,8 +37,8 @@ function handleTransactionEvent(replay: ReplayContainer, event: TransactionEvent // Collect traceIds in _context regardless of `recordingMode` // In error mode, _context gets cleared on every checkout // We limit to max. 100 transactions linked - if (event.contexts?.trace?.trace_id && replayContext.traceIds.size < 100) { - replayContext.traceIds.add(event.contexts.trace.trace_id); + if (event.contexts?.trace?.trace_id && event.timestamp && replayContext.traceIds.size < 100) { + replayContext.traceIds.add([event.timestamp, event.contexts.trace.trace_id]); } } diff --git a/packages/replay-internal/src/types/replay.ts b/packages/replay-internal/src/types/replay.ts index 1e7891a84e76..8e5611d4e15d 100644 --- a/packages/replay-internal/src/types/replay.ts +++ b/packages/replay-internal/src/types/replay.ts @@ -335,7 +335,7 @@ export interface PopEventContext extends CommonEventContext { /** * List of Sentry trace ids that have occurred during a replay segment */ - traceIds: Array; + traceIds: Array<[number, string]>; } /** @@ -348,9 +348,9 @@ export interface InternalEventContext extends CommonEventContext { errorIds: Set; /** - * Set of Sentry trace ids that have occurred during a replay segment + * Set of [timestamp, trace_id] tuples for Sentry traces that have occurred during a replay segment */ - traceIds: Set; + traceIds: Set<[number, string]>; } export type Sampled = false | 'session' | 'buffer'; diff --git a/packages/replay-internal/src/util/sendReplayRequest.ts b/packages/replay-internal/src/util/sendReplayRequest.ts index 4f40934f37d3..bdd07ed71205 100644 --- a/packages/replay-internal/src/util/sendReplayRequest.ts +++ b/packages/replay-internal/src/util/sendReplayRequest.ts @@ -42,7 +42,8 @@ export async function sendReplayRequest({ replay_start_timestamp: initialTimestamp / 1000, timestamp: timestamp / 1000, error_ids: errorIds, - trace_ids: traceIds, + trace_ids: traceIds.map(([_ts, traceId]) => traceId), + traces_by_timestamp: traceIds.map(([ts, traceId]) => [ts, traceId]), urls, replay_id: replayId, segment_id, diff --git a/packages/replay-internal/test/integration/coreHandlers/handleAfterSendEvent.test.ts b/packages/replay-internal/test/integration/coreHandlers/handleAfterSendEvent.test.ts index f45441a34caf..377c7e495907 100644 --- a/packages/replay-internal/test/integration/coreHandlers/handleAfterSendEvent.test.ts +++ b/packages/replay-internal/test/integration/coreHandlers/handleAfterSendEvent.test.ts @@ -84,13 +84,20 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { handler(transaction4, { statusCode: undefined }); expect(Array.from(replay.getContext().errorIds)).toEqual([]); - expect(Array.from(replay.getContext().traceIds)).toEqual(['tr2']); + // traceIds is now a Set of [timestamp, trace_id] tuples + const traceIds = Array.from(replay.getContext().traceIds); + expect(traceIds).toHaveLength(1); + expect(traceIds[0][1]).toBe('tr2'); + expect(typeof traceIds[0][0]).toBe('number'); // Does not affect error session await vi.advanceTimersToNextTimerAsync(); expect(Array.from(replay.getContext().errorIds)).toEqual([]); - expect(Array.from(replay.getContext().traceIds)).toEqual(['tr2']); + // Verify traceIds are still there after advancing timers + const traceIdsAfter = Array.from(replay.getContext().traceIds); + expect(traceIdsAfter).toHaveLength(1); + expect(traceIdsAfter[0][1]).toBe('tr2'); expect(replay.isEnabled()).toBe(true); expect(replay.isPaused()).toBe(false); expect(replay.recordingMode).toBe('buffer'); @@ -141,11 +148,20 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { } expect(Array.from(replay.getContext().errorIds)).toEqual([]); - expect(Array.from(replay.getContext().traceIds)).toEqual( + // traceIds is now a Set of [timestamp, trace_id] tuples + const traceIds = Array.from(replay.getContext().traceIds); + expect(traceIds).toHaveLength(100); + // Check that all trace IDs are present + expect(traceIds.map(([_timestamp, traceId]) => traceId)).toEqual( Array(100) .fill(undefined) .map((_, i) => `tr-${i}`), ); + // Check that all tuples have timestamps + traceIds.forEach(([timestamp, _traceId]) => { + expect(typeof timestamp).toBe('number'); + expect(timestamp).toBeGreaterThan(0); + }); }); it('flushes when in buffer mode', async () => { From ffc6cd922cc819b1804297ec4b18475f720868a7 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 29 Oct 2025 12:40:23 -0400 Subject: [PATCH 2/5] fix tests --- .../suites/replay/captureReplay/test.ts | 85 ++----------------- .../captureReplayFromReplayPackage/test.ts | 83 ++---------------- .../utils/replayEventTemplates.ts | 1 + 3 files changed, 14 insertions(+), 155 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts index 8167552fb0d6..130ff7f69116 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts @@ -1,6 +1,6 @@ import { expect } from '@playwright/test'; -import { SDK_VERSION } from '@sentry/browser'; -import { sentryTest, TEST_HOST } from '../../../utils/fixtures'; +import { sentryTest } from '../../../utils/fixtures'; +import { getExpectedReplayEvent } from '../../../utils/replayEventTemplates'; import { getReplayEvent, shouldSkipReplayTest, waitForReplayRequest } from '../../../utils/replayHelpers'; sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalTestUrl, page }) => { @@ -20,84 +20,13 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT const replayEvent1 = getReplayEvent(await reqPromise1); expect(replayEvent0).toBeDefined(); - expect(replayEvent0).toEqual({ - type: 'replay_event', - timestamp: expect.any(Number), - error_ids: [], - trace_ids: [], - urls: [`${TEST_HOST}/index.html`], - replay_id: expect.stringMatching(/\w{32}/), - replay_start_timestamp: expect.any(Number), + expect(replayEvent0).toEqual(getExpectedReplayEvent({ segment_id: 0, - replay_type: 'session', - event_id: expect.stringMatching(/\w{32}/), - environment: 'production', - sdk: { - integrations: expect.arrayContaining([ - 'InboundFilters', - 'FunctionToString', - 'BrowserApiErrors', - 'Breadcrumbs', - 'GlobalHandlers', - 'LinkedErrors', - 'Dedupe', - 'HttpContext', - 'BrowserSession', - 'Replay', - ]), - version: SDK_VERSION, - name: 'sentry.javascript.browser', - settings: { - infer_ip: 'never', - }, - }, - request: { - url: `${TEST_HOST}/index.html`, - headers: { - 'User-Agent': expect.stringContaining(''), - }, - }, - platform: 'javascript', - }); + })); expect(replayEvent1).toBeDefined(); - expect(replayEvent1).toEqual({ - type: 'replay_event', - timestamp: expect.any(Number), - error_ids: [], - trace_ids: [], - urls: [], - replay_id: expect.stringMatching(/\w{32}/), - replay_start_timestamp: expect.any(Number), + expect(replayEvent1).toEqual(getExpectedReplayEvent({ segment_id: 1, - replay_type: 'session', - event_id: expect.stringMatching(/\w{32}/), - environment: 'production', - sdk: { - integrations: expect.arrayContaining([ - 'InboundFilters', - 'FunctionToString', - 'BrowserApiErrors', - 'Breadcrumbs', - 'GlobalHandlers', - 'LinkedErrors', - 'Dedupe', - 'HttpContext', - 'BrowserSession', - 'Replay', - ]), - version: SDK_VERSION, - name: 'sentry.javascript.browser', - settings: { - infer_ip: 'never', - }, - }, - request: { - url: `${TEST_HOST}/index.html`, - headers: { - 'User-Agent': expect.stringContaining(''), - }, - }, - platform: 'javascript', - }); + urls: [], + })); }); diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts index f6c3dcf17b23..7669775783a5 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts @@ -1,6 +1,6 @@ import { expect } from '@playwright/test'; -import { SDK_VERSION } from '@sentry/browser'; -import { sentryTest, TEST_HOST } from '../../../utils/fixtures'; +import { sentryTest } from '../../../utils/fixtures'; +import { getExpectedReplayEvent } from '../../../utils/replayEventTemplates'; import { getReplayEvent, shouldSkipReplayTest, waitForReplayRequest } from '../../../utils/replayHelpers'; sentryTest('should capture replays (@sentry-internal/replay export)', async ({ getLocalTestUrl, page }) => { @@ -20,84 +20,13 @@ sentryTest('should capture replays (@sentry-internal/replay export)', async ({ g const replayEvent1 = getReplayEvent(await reqPromise1); expect(replayEvent0).toBeDefined(); - expect(replayEvent0).toEqual({ - type: 'replay_event', - timestamp: expect.any(Number), - error_ids: [], - trace_ids: [], - urls: [`${TEST_HOST}/index.html`], - replay_id: expect.stringMatching(/\w{32}/), - replay_start_timestamp: expect.any(Number), + expect(replayEvent0).toEqual(getExpectedReplayEvent({ segment_id: 0, - replay_type: 'session', - event_id: expect.stringMatching(/\w{32}/), - environment: 'production', - sdk: { - integrations: expect.arrayContaining([ - 'InboundFilters', - 'FunctionToString', - 'BrowserApiErrors', - 'Breadcrumbs', - 'GlobalHandlers', - 'LinkedErrors', - 'Dedupe', - 'HttpContext', - 'BrowserSession', - 'Replay', - ]), - version: SDK_VERSION, - name: 'sentry.javascript.browser', - settings: { - infer_ip: 'never', - }, - }, - request: { - url: `${TEST_HOST}/index.html`, - headers: { - 'User-Agent': expect.stringContaining(''), - }, - }, - platform: 'javascript', - }); + })); expect(replayEvent1).toBeDefined(); - expect(replayEvent1).toEqual({ - type: 'replay_event', - timestamp: expect.any(Number), - error_ids: [], - trace_ids: [], + expect(replayEvent1).toEqual(getExpectedReplayEvent({ urls: [], - replay_id: expect.stringMatching(/\w{32}/), - replay_start_timestamp: expect.any(Number), segment_id: 1, - replay_type: 'session', - event_id: expect.stringMatching(/\w{32}/), - environment: 'production', - sdk: { - integrations: expect.arrayContaining([ - 'InboundFilters', - 'FunctionToString', - 'BrowserApiErrors', - 'Breadcrumbs', - 'GlobalHandlers', - 'LinkedErrors', - 'Dedupe', - 'HttpContext', - 'BrowserSession', - 'Replay', - ]), - version: SDK_VERSION, - name: 'sentry.javascript.browser', - settings: { - infer_ip: 'never', - }, - }, - request: { - url: `${TEST_HOST}/index.html`, - headers: { - 'User-Agent': expect.stringContaining(''), - }, - }, - platform: 'javascript', - }); + })); }); diff --git a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts index 53a9e733a908..5951fa12f300 100644 --- a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts +++ b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts @@ -8,6 +8,7 @@ const DEFAULT_REPLAY_EVENT = { timestamp: expect.any(Number), error_ids: [], trace_ids: [], + traces_by_timestamp: [], urls: [expect.stringContaining('/index.html')], replay_id: expect.stringMatching(/\w{32}/), replay_start_timestamp: expect.any(Number), From 1c9f09fc66971dbed4263a456f2577ac93e9b4c6 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 29 Oct 2025 16:52:01 -0400 Subject: [PATCH 3/5] keep most recent trace id, use the current trace id if no transaction event --- .../utils/replayEventTemplates.ts | 6 +++-- .../src/coreHandlers/handleAfterSendEvent.ts | 4 +-- packages/replay-internal/src/replay.ts | 25 ++++++++++++++++--- packages/replay-internal/src/types/replay.ts | 4 +-- .../src/util/sendReplayRequest.ts | 5 ++-- 5 files changed, 32 insertions(+), 12 deletions(-) diff --git a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts index 5951fa12f300..9d83fb5e2e72 100644 --- a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts +++ b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts @@ -7,8 +7,10 @@ const DEFAULT_REPLAY_EVENT = { type: 'replay_event', timestamp: expect.any(Number), error_ids: [], - trace_ids: [], - traces_by_timestamp: [], + trace_ids: [expect.any(String)], + traces_by_timestamp: [ + [expect.any(Number), expect.any(String)], + ], urls: [expect.stringContaining('/index.html')], replay_id: expect.stringMatching(/\w{32}/), replay_start_timestamp: expect.any(Number), diff --git a/packages/replay-internal/src/coreHandlers/handleAfterSendEvent.ts b/packages/replay-internal/src/coreHandlers/handleAfterSendEvent.ts index e1f4699b92b0..3752457721f9 100644 --- a/packages/replay-internal/src/coreHandlers/handleAfterSendEvent.ts +++ b/packages/replay-internal/src/coreHandlers/handleAfterSendEvent.ts @@ -37,8 +37,8 @@ function handleTransactionEvent(replay: ReplayContainer, event: TransactionEvent // Collect traceIds in _context regardless of `recordingMode` // In error mode, _context gets cleared on every checkout // We limit to max. 100 transactions linked - if (event.contexts?.trace?.trace_id && event.timestamp && replayContext.traceIds.size < 100) { - replayContext.traceIds.add([event.timestamp, event.contexts.trace.trace_id]); + if (event.contexts?.trace?.trace_id && event.start_timestamp && replayContext.traceIds.length < 100) { + replayContext.traceIds.push([event.start_timestamp, event.contexts.trace.trace_id]); } } diff --git a/packages/replay-internal/src/replay.ts b/packages/replay-internal/src/replay.ts index 61676f790b4d..465e23e1b090 100644 --- a/packages/replay-internal/src/replay.ts +++ b/packages/replay-internal/src/replay.ts @@ -1,6 +1,13 @@ /* eslint-disable max-lines */ // TODO: We might want to split this file up import type { ReplayRecordingMode, Span } from '@sentry/core'; -import { getActiveSpan, getClient, getRootSpan, SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, spanToJSON } from '@sentry/core'; +import { + getActiveSpan, + getClient, + getCurrentScope, + getRootSpan, + SEMANTIC_ATTRIBUTE_SENTRY_SOURCE, + spanToJSON, +} from '@sentry/core'; import { EventType, record } from '@sentry-internal/rrweb'; import { BUFFER_CHECKOUT_TIME, @@ -192,7 +199,7 @@ export class ReplayContainer implements ReplayContainerInterface { this._hasInitializedCoreListeners = false; this._context = { errorIds: new Set(), - traceIds: new Set(), + traceIds: [], urls: [], initialTimestamp: Date.now(), initialUrl: '', @@ -1097,7 +1104,11 @@ export class ReplayContainer implements ReplayContainerInterface { private _clearContext(): void { // XXX: `initialTimestamp` and `initialUrl` do not get cleared this._context.errorIds.clear(); - this._context.traceIds.clear(); + // We want to preserve the most recent trace id for the next replay segment. + // This is so that we can associate replay events w/ the trace. + if (this._context.traceIds.length > 1) { + this._context.traceIds = this._context.traceIds.slice(-1); + } this._context.urls = []; } @@ -1125,11 +1136,17 @@ export class ReplayContainer implements ReplayContainerInterface { * Return and clear _context */ private _popEventContext(): PopEventContext { + if (this._context.traceIds.length === 0) { + const currentTraceId = getCurrentScope().getPropagationContext().traceId; + if (currentTraceId) { + this._context.traceIds.push([-1, currentTraceId]); + } + } const _context = { initialTimestamp: this._context.initialTimestamp, initialUrl: this._context.initialUrl, errorIds: Array.from(this._context.errorIds), - traceIds: Array.from(this._context.traceIds), + traceIds: this._context.traceIds, urls: this._context.urls, }; diff --git a/packages/replay-internal/src/types/replay.ts b/packages/replay-internal/src/types/replay.ts index 8e5611d4e15d..279bf14be3b1 100644 --- a/packages/replay-internal/src/types/replay.ts +++ b/packages/replay-internal/src/types/replay.ts @@ -348,9 +348,9 @@ export interface InternalEventContext extends CommonEventContext { errorIds: Set; /** - * Set of [timestamp, trace_id] tuples for Sentry traces that have occurred during a replay segment + * List of for Sentry traces that have occurred during a replay segment */ - traceIds: Set<[number, string]>; + traceIds: Array<[number, string]>; } export type Sampled = false | 'session' | 'buffer'; diff --git a/packages/replay-internal/src/util/sendReplayRequest.ts b/packages/replay-internal/src/util/sendReplayRequest.ts index bdd07ed71205..948217894ddf 100644 --- a/packages/replay-internal/src/util/sendReplayRequest.ts +++ b/packages/replay-internal/src/util/sendReplayRequest.ts @@ -37,13 +37,14 @@ export async function sendReplayRequest({ return Promise.resolve({}); } + const uniqueTraceIds = Array.from(new Set(traceIds.map(([_ts, traceId]) => traceId))); const baseEvent: ReplayEvent = { type: REPLAY_EVENT_NAME, replay_start_timestamp: initialTimestamp / 1000, timestamp: timestamp / 1000, error_ids: errorIds, - trace_ids: traceIds.map(([_ts, traceId]) => traceId), - traces_by_timestamp: traceIds.map(([ts, traceId]) => [ts, traceId]), + trace_ids: uniqueTraceIds, + traces_by_timestamp: traceIds.filter(([_ts, traceId]) => uniqueTraceIds.includes(traceId)).map(([ts, traceId]) => [ts, traceId]), urls, replay_id: replayId, segment_id, From 424964da9e512ec0f811891baee6bd7487af1718 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 29 Oct 2025 17:02:32 -0400 Subject: [PATCH 4/5] format --- .../suites/replay/captureReplay/test.ts | 18 +++++++++++------- .../captureReplayFromReplayPackage/test.ts | 18 +++++++++++------- .../utils/replayEventTemplates.ts | 4 +--- .../src/util/sendReplayRequest.ts | 4 +++- 4 files changed, 26 insertions(+), 18 deletions(-) diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts index 130ff7f69116..d53b141acd35 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureReplay/test.ts @@ -20,13 +20,17 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT const replayEvent1 = getReplayEvent(await reqPromise1); expect(replayEvent0).toBeDefined(); - expect(replayEvent0).toEqual(getExpectedReplayEvent({ - segment_id: 0, - })); + expect(replayEvent0).toEqual( + getExpectedReplayEvent({ + segment_id: 0, + }), + ); expect(replayEvent1).toBeDefined(); - expect(replayEvent1).toEqual(getExpectedReplayEvent({ - segment_id: 1, - urls: [], - })); + expect(replayEvent1).toEqual( + getExpectedReplayEvent({ + segment_id: 1, + urls: [], + }), + ); }); diff --git a/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts b/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts index 7669775783a5..f4743dc58070 100644 --- a/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts +++ b/dev-packages/browser-integration-tests/suites/replay/captureReplayFromReplayPackage/test.ts @@ -20,13 +20,17 @@ sentryTest('should capture replays (@sentry-internal/replay export)', async ({ g const replayEvent1 = getReplayEvent(await reqPromise1); expect(replayEvent0).toBeDefined(); - expect(replayEvent0).toEqual(getExpectedReplayEvent({ - segment_id: 0, - })); + expect(replayEvent0).toEqual( + getExpectedReplayEvent({ + segment_id: 0, + }), + ); expect(replayEvent1).toBeDefined(); - expect(replayEvent1).toEqual(getExpectedReplayEvent({ - urls: [], - segment_id: 1, - })); + expect(replayEvent1).toEqual( + getExpectedReplayEvent({ + urls: [], + segment_id: 1, + }), + ); }); diff --git a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts index 9d83fb5e2e72..e0b192437993 100644 --- a/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts +++ b/dev-packages/browser-integration-tests/utils/replayEventTemplates.ts @@ -8,9 +8,7 @@ const DEFAULT_REPLAY_EVENT = { timestamp: expect.any(Number), error_ids: [], trace_ids: [expect.any(String)], - traces_by_timestamp: [ - [expect.any(Number), expect.any(String)], - ], + traces_by_timestamp: [[expect.any(Number), expect.any(String)]], urls: [expect.stringContaining('/index.html')], replay_id: expect.stringMatching(/\w{32}/), replay_start_timestamp: expect.any(Number), diff --git a/packages/replay-internal/src/util/sendReplayRequest.ts b/packages/replay-internal/src/util/sendReplayRequest.ts index 948217894ddf..a1fe0b847079 100644 --- a/packages/replay-internal/src/util/sendReplayRequest.ts +++ b/packages/replay-internal/src/util/sendReplayRequest.ts @@ -44,7 +44,9 @@ export async function sendReplayRequest({ timestamp: timestamp / 1000, error_ids: errorIds, trace_ids: uniqueTraceIds, - traces_by_timestamp: traceIds.filter(([_ts, traceId]) => uniqueTraceIds.includes(traceId)).map(([ts, traceId]) => [ts, traceId]), + traces_by_timestamp: traceIds + .filter(([_ts, traceId]) => uniqueTraceIds.includes(traceId)) + .map(([ts, traceId]) => [ts, traceId]), urls, replay_id: replayId, segment_id, From 56599e5ac509945072d7ac15ad0f94f7400b8529 Mon Sep 17 00:00:00 2001 From: Billy Vong Date: Wed, 29 Oct 2025 17:36:56 -0400 Subject: [PATCH 5/5] fix integration tests --- .../coreHandlers/handleAfterSendEvent.test.ts | 16 ++++++---------- .../test/integration/errorSampleRate.test.ts | 6 ++++-- .../test/integration/sampling.test.ts | 4 ++-- .../test/integration/sendReplayEvent.test.ts | 3 ++- .../test/integration/session.test.ts | 2 +- 5 files changed, 15 insertions(+), 16 deletions(-) diff --git a/packages/replay-internal/test/integration/coreHandlers/handleAfterSendEvent.test.ts b/packages/replay-internal/test/integration/coreHandlers/handleAfterSendEvent.test.ts index 377c7e495907..879049ddc9cd 100644 --- a/packages/replay-internal/test/integration/coreHandlers/handleAfterSendEvent.test.ts +++ b/packages/replay-internal/test/integration/coreHandlers/handleAfterSendEvent.test.ts @@ -53,7 +53,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { handler(error4, { statusCode: undefined }); expect(Array.from(replay.getContext().errorIds)).toEqual(['err2']); - expect(Array.from(replay.getContext().traceIds)).toEqual([]); + expect(Array.from(replay.getContext().traceIds)).toEqual([[-1, expect.any(String)]]); }); it('records traceIds from sent transaction events', async () => { @@ -84,20 +84,16 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { handler(transaction4, { statusCode: undefined }); expect(Array.from(replay.getContext().errorIds)).toEqual([]); - // traceIds is now a Set of [timestamp, trace_id] tuples - const traceIds = Array.from(replay.getContext().traceIds); - expect(traceIds).toHaveLength(1); - expect(traceIds[0][1]).toBe('tr2'); - expect(typeof traceIds[0][0]).toBe('number'); + const traceIds = replay.getContext().traceIds; + expect(traceIds).toEqual([[expect.any(Number), 'tr2']]); // Does not affect error session await vi.advanceTimersToNextTimerAsync(); expect(Array.from(replay.getContext().errorIds)).toEqual([]); // Verify traceIds are still there after advancing timers - const traceIdsAfter = Array.from(replay.getContext().traceIds); - expect(traceIdsAfter).toHaveLength(1); - expect(traceIdsAfter[0][1]).toBe('tr2'); + const traceIdsAfter = replay.getContext().traceIds; + expect(traceIdsAfter).toEqual([[expect.any(Number), 'tr2']]); expect(replay.isEnabled()).toBe(true); expect(replay.isPaused()).toBe(false); expect(replay.recordingMode).toBe('buffer'); @@ -126,7 +122,7 @@ describe('Integration | coreHandlers | handleAfterSendEvent', () => { .fill(undefined) .map((_, i) => `err-${i}`), ); - expect(Array.from(replay.getContext().traceIds)).toEqual([]); + expect(replay.getContext().traceIds).toEqual([[-1, expect.any(String)]]); }); it('limits traceIds to max. 100', async () => { diff --git a/packages/replay-internal/test/integration/errorSampleRate.test.ts b/packages/replay-internal/test/integration/errorSampleRate.test.ts index f79e393df7e3..6a05a3594717 100644 --- a/packages/replay-internal/test/integration/errorSampleRate.test.ts +++ b/packages/replay-internal/test/integration/errorSampleRate.test.ts @@ -702,7 +702,8 @@ describe('Integration | errorSampleRate', () => { replay_start_timestamp: BASE_TIMESTAMP / 1000, timestamp: (BASE_TIMESTAMP + DEFAULT_FLUSH_MIN_DELAY + DEFAULT_FLUSH_MIN_DELAY) / 1000, error_ids: [expect.any(String)], - trace_ids: [], + trace_ids: [expect.any(String)], + traces_by_timestamp: [[-1, expect.any(String)]], urls: ['http://localhost:3000/'], replay_id: expect.any(String), }), @@ -909,7 +910,8 @@ describe('Integration | errorSampleRate', () => { replayEventPayload: expect.objectContaining({ replay_start_timestamp: (BASE_TIMESTAMP + stepDuration * steps) / 1000, error_ids: [expect.any(String)], - trace_ids: [], + trace_ids: [expect.any(String)], + traces_by_timestamp: [[-1, expect.any(String)]], urls: ['http://localhost:3000/'], replay_id: expect.any(String), }), diff --git a/packages/replay-internal/test/integration/sampling.test.ts b/packages/replay-internal/test/integration/sampling.test.ts index 9ffa00c349c6..2feb4a77db67 100644 --- a/packages/replay-internal/test/integration/sampling.test.ts +++ b/packages/replay-internal/test/integration/sampling.test.ts @@ -37,7 +37,7 @@ describe('Integration | sampling', () => { // This is what the `_context` member is initialized with expect(replay.getContext()).toEqual({ errorIds: new Set(), - traceIds: new Set(), + traceIds: [], urls: [], initialTimestamp: expect.any(Number), initialUrl: '', @@ -78,7 +78,7 @@ describe('Integration | sampling', () => { errorIds: new Set(), initialTimestamp: expect.any(Number), initialUrl: 'http://localhost:3000/', - traceIds: new Set(), + traceIds: [], urls: ['http://localhost:3000/'], }); expect(replay.recordingMode).toBe('buffer'); diff --git a/packages/replay-internal/test/integration/sendReplayEvent.test.ts b/packages/replay-internal/test/integration/sendReplayEvent.test.ts index 1e870a8f577b..a24dd1de8ffe 100644 --- a/packages/replay-internal/test/integration/sendReplayEvent.test.ts +++ b/packages/replay-internal/test/integration/sendReplayEvent.test.ts @@ -340,7 +340,8 @@ describe('Integration | sendReplayEvent', () => { replay_start_timestamp: BASE_TIMESTAMP / 1000, // timestamp is set on first try, after 5s flush timestamp: (BASE_TIMESTAMP + 5000) / 1000, - trace_ids: [], + trace_ids: [expect.any(String)], + traces_by_timestamp: [[-1, expect.any(String)]], urls: ['http://localhost:3000/'], }), recordingPayloadHeader: { segment_id: 0 }, diff --git a/packages/replay-internal/test/integration/session.test.ts b/packages/replay-internal/test/integration/session.test.ts index f867c43efbe8..3c4644959210 100644 --- a/packages/replay-internal/test/integration/session.test.ts +++ b/packages/replay-internal/test/integration/session.test.ts @@ -252,7 +252,7 @@ describe('Integration | session', () => { initialTimestamp: newTimestamp - DEFAULT_FLUSH_MIN_DELAY, urls: [], errorIds: new Set(), - traceIds: new Set(), + traceIds: [[-1, expect.any(String)]], }); });