Skip to content

Commit 5ee3f10

Browse files
committed
fix(replay): Ignore older performance entries when starting manually
1 parent 0ccf8ce commit 5ee3f10

File tree

2 files changed

+119
-1
lines changed
  • dev-packages/browser-integration-tests/suites/replay/bufferModeManual
  • packages/replay-internal/src

2 files changed

+119
-1
lines changed

dev-packages/browser-integration-tests/suites/replay/bufferModeManual/test.ts

+106
Original file line numberDiff line numberDiff line change
@@ -287,6 +287,112 @@ sentryTest(
287287
},
288288
);
289289

290+
sentryTest(
291+
'[buffer-mode] manually starting replay ignores earlier performance entries',
292+
async ({ getLocalTestUrl, page, browserName }) => {
293+
// This was sometimes flaky on webkit, so skipping for now
294+
if (shouldSkipReplayTest() || browserName === 'webkit') {
295+
sentryTest.skip();
296+
}
297+
298+
const reqPromise0 = waitForReplayRequest(page, 0);
299+
300+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
301+
return route.fulfill({
302+
status: 200,
303+
contentType: 'application/json',
304+
body: JSON.stringify({ id: 'test-id' }),
305+
});
306+
});
307+
308+
const url = await getLocalTestUrl({ testDir: __dirname });
309+
310+
await page.goto(url);
311+
312+
// Wait for everything to be initialized - Replay is not running yet
313+
await page.waitForFunction('!!window.Replay');
314+
315+
// Wait for a second, then start replay
316+
await new Promise(resolve => setTimeout(resolve, 1000));
317+
await page.evaluate('window.Replay.start()');
318+
319+
const req0 = await reqPromise0;
320+
321+
const event0 = getReplayEvent(req0);
322+
const content0 = getReplayRecordingContent(req0);
323+
324+
expect(event0).toEqual(
325+
getExpectedReplayEvent({
326+
replay_type: 'session',
327+
}),
328+
);
329+
330+
const { performanceSpans } = content0;
331+
332+
// Here, we test that this does not contain any web-vital etc. performance spans
333+
// as these have been emitted _before_ the replay was manually started
334+
expect(performanceSpans).toEqual([
335+
{
336+
op: 'memory',
337+
description: 'memory',
338+
startTimestamp: expect.any(Number),
339+
endTimestamp: expect.any(Number),
340+
data: {
341+
memory: {
342+
jsHeapSizeLimit: expect.any(Number),
343+
totalJSHeapSize: expect.any(Number),
344+
usedJSHeapSize: expect.any(Number),
345+
},
346+
},
347+
},
348+
]);
349+
},
350+
);
351+
352+
sentryTest(
353+
'[buffer-mode] manually starting replay includes performance entries with 1s wiggle room',
354+
async ({ getLocalTestUrl, page, browserName }) => {
355+
// This was sometimes flaky on webkit, so skipping for now
356+
if (shouldSkipReplayTest() || browserName === 'webkit') {
357+
sentryTest.skip();
358+
}
359+
360+
const reqPromise0 = waitForReplayRequest(page, 0);
361+
362+
await page.route('https://dsn.ingest.sentry.io/**/*', route => {
363+
return route.fulfill({
364+
status: 200,
365+
contentType: 'application/json',
366+
body: JSON.stringify({ id: 'test-id' }),
367+
});
368+
});
369+
370+
const url = await getLocalTestUrl({ testDir: __dirname });
371+
372+
page.goto(url);
373+
374+
// Wait for everything to be initialized, then start replay as soon as possible
375+
await page.waitForFunction('!!window.Replay');
376+
await page.evaluate('window.Replay.start()');
377+
378+
const req0 = await reqPromise0;
379+
380+
const event0 = getReplayEvent(req0);
381+
const content0 = getReplayRecordingContent(req0);
382+
383+
expect(event0).toEqual(
384+
getExpectedReplayEvent({
385+
replay_type: 'session',
386+
}),
387+
);
388+
389+
const { performanceSpans } = content0;
390+
391+
// web vitals etc. are included with 1s wiggle room, to accomodate "immediate" start
392+
expect(performanceSpans.length).toBeGreaterThan(1);
393+
},
394+
);
395+
290396
// Doing this in buffer mode to test changing error sample rate after first
291397
// error happens.
292398
sentryTest(

packages/replay-internal/src/replay.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -1046,11 +1046,23 @@ export class ReplayContainer implements ReplayContainerInterface {
10461046
* are included in the replay event before it is finished and sent to Sentry.
10471047
*/
10481048
private _addPerformanceEntries(): Promise<Array<AddEventResult | null>> {
1049-
const performanceEntries = createPerformanceEntries(this.performanceEntries).concat(this.replayPerformanceEntries);
1049+
let performanceEntries = createPerformanceEntries(this.performanceEntries).concat(this.replayPerformanceEntries);
10501050

10511051
this.performanceEntries = [];
10521052
this.replayPerformanceEntries = [];
10531053

1054+
// If we are manually starting, we want to ensure we only include performance entries
1055+
// that are after the initial timestamp, with 1s wiggle room.
1056+
// The reason for this is that we may have performance entries from the page load, but may decide to start
1057+
// the replay later on, in which case we do not want to include these entries.
1058+
// without this, manually started replays can have events long before the actual replay recording starts,
1059+
// which messes with the timeline etc.
1060+
if (this._requiresManualStart) {
1061+
// We leave 1s wiggle room to accomodate timing differences for "immedidate" manual starts
1062+
const initialTimestampInSeconds = this._context.initialTimestamp / 1000 - 1;
1063+
performanceEntries = performanceEntries.filter(entry => entry.start >= initialTimestampInSeconds);
1064+
}
1065+
10541066
return Promise.all(createPerformanceSpans(this, performanceEntries));
10551067
}
10561068

0 commit comments

Comments
 (0)