Skip to content
Merged
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,6 @@ sentryTest(

expect(event0).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 0, session_sample_rate: 0 } },
error_ids: [errorEventId!],
replay_type: 'buffer',
}),
Expand Down Expand Up @@ -150,7 +149,6 @@ sentryTest(

expect(event1).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 0, session_sample_rate: 0 } },
replay_type: 'buffer', // although we're in session mode, we still send 'buffer' as replay_type
segment_id: 1,
urls: [],
Expand All @@ -162,7 +160,6 @@ sentryTest(

expect(event2).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 0, session_sample_rate: 0 } },
replay_type: 'buffer', // although we're in session mode, we still send 'buffer' as replay_type
segment_id: 2,
urls: [],
Expand Down Expand Up @@ -266,7 +263,6 @@ sentryTest(

expect(event0).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 0, session_sample_rate: 0 } },
error_ids: [errorEventId!],
replay_type: 'buffer',
}),
Expand Down Expand Up @@ -372,7 +368,6 @@ sentryTest('[buffer-mode] can sample on each error event', async ({ getLocalTest

expect(event0).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 1, session_sample_rate: 0 } },
error_ids: errorEventIds,
replay_type: 'buffer',
}),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT
},
},
platform: 'javascript',
contexts: { replay: { session_sample_rate: 1, error_sample_rate: 0 } },
});

expect(replayEvent1).toBeDefined();
Expand Down Expand Up @@ -103,6 +102,5 @@ sentryTest('should capture replays (@sentry/browser export)', async ({ getLocalT
},
},
platform: 'javascript',
contexts: { replay: { session_sample_rate: 1, error_sample_rate: 0 } },
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,6 @@ sentryTest('should capture replays (@sentry/replay export)', async ({ getLocalTe
},
},
platform: 'javascript',
contexts: { replay: { session_sample_rate: 1, error_sample_rate: 0 } },
});

expect(replayEvent1).toBeDefined();
Expand Down Expand Up @@ -103,6 +102,5 @@ sentryTest('should capture replays (@sentry/replay export)', async ({ getLocalTe
},
},
platform: 'javascript',
contexts: { replay: { session_sample_rate: 1, error_sample_rate: 0 } },
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -174,3 +174,60 @@ sentryTest(
);
},
);

sentryTest(
'replay recording should contain an "options" breadcrumb for Replay SDK configuration',
async ({ forceFlushReplay, getLocalTestPath, page, browserName }) => {
// TODO(replay): This is flakey on firefox and webkit where clicks are flakey
if (shouldSkipReplayTest() || ['firefox', 'webkit'].includes(browserName)) {
sentryTest.skip();
}

const reqPromise0 = waitForReplayRequest(page, 0);
const reqPromise1 = waitForReplayRequest(page, 1);

await page.route('https://dsn.ingest.sentry.io/**/*', route => {
return route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify({ id: 'test-id' }),
});
});

const url = await getLocalTestPath({ testDir: __dirname });

await page.goto(url);
await forceFlushReplay();

await page.click('#error');
await forceFlushReplay();

const req0 = await reqPromise0;
const content0 = getReplayRecordingContent(req0);

expect(content0.optionsEvents).toEqual([
{
tag: 'options',
payload: {
sessionSampleRate: 1,
errorSampleRate: 0,
useCompressionOption: false,
blockAllMedia: false,
maskAllText: true,
maskAllInputs: true,
useCompression: false,
networkDetailHasUrls: false,
networkCaptureBodies: true,
networkRequestHasHeaders: true,
networkResponseHasHeaders: true,
},
},
]);

const req1 = await reqPromise1;
const content1 = getReplayRecordingContent(req1);

// Should only be on first segment
expect(content1.optionsEvents).toEqual([]);
},
);
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,6 @@ sentryTest(

expect(event0).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 1, session_sample_rate: 0 } },
error_ids: [errorEventId!],
replay_type: 'buffer',
}),
Expand Down Expand Up @@ -119,7 +118,6 @@ sentryTest(

expect(event1).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 1, session_sample_rate: 0 } },
replay_type: 'buffer', // although we're in session mode, we still send 'error' as replay_type
segment_id: 1,
urls: [],
Expand All @@ -134,7 +132,6 @@ sentryTest(
// we continue recording everything
expect(event2).toEqual(
getExpectedReplayEvent({
contexts: { replay: { error_sample_rate: 1, session_sample_rate: 0 } },
replay_type: 'buffer',
segment_id: 2,
urls: [],
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ const DEFAULT_REPLAY_EVENT = {
},
},
platform: 'javascript',
contexts: { replay: { session_sample_rate: 1, error_sample_rate: 0 } },
};

/**
Expand Down
9 changes: 8 additions & 1 deletion packages/browser-integration-tests/utils/replayHelpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,7 @@ type CustomRecordingContent = {
type RecordingContent = {
fullSnapshots: RecordingSnapshot[];
incrementalSnapshots: RecordingSnapshot[];
optionsEvents: CustomRecordingEvent[];
} & CustomRecordingContent;

/**
Expand Down Expand Up @@ -207,6 +208,11 @@ export function getIncrementalRecordingSnapshots(resOrReq: Request | Response):
return events.filter(isIncrementalSnapshot);
}

function getOptionsEvents(replayRequest: Request): CustomRecordingEvent[] {
const events = getDecompressedRecordingEvents(replayRequest);
return getAllCustomRrwebRecordingEvents(events).filter(data => data.tag === 'options');
}

function getDecompressedRecordingEvents(resOrReq: Request | Response): RecordingSnapshot[] {
const replayRequest = getRequest(resOrReq);
return (
Expand All @@ -227,8 +233,9 @@ export function getReplayRecordingContent(resOrReq: Request | Response): Recordi
const fullSnapshots = getFullRecordingSnapshots(replayRequest);
const incrementalSnapshots = getIncrementalRecordingSnapshots(replayRequest);
const customEvents = getCustomRecordingEvents(replayRequest);
const optionsEvents = getOptionsEvents(replayRequest);

return { fullSnapshots, incrementalSnapshots, ...customEvents };
return { fullSnapshots, incrementalSnapshots, optionsEvents, ...customEvents };
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,26 @@ export const ReplayRecordingData = [
data: { href: expect.stringMatching(/http:\/\/localhost:\d+\//), width: 1280, height: 720 },
timestamp: expect.any(Number),
},
{
data: {
payload: {
blockAllMedia: true,
errorSampleRate: 0,
maskAllInputs: true,
maskAllText: true,
networkCaptureBodies: true,
networkDetailHasUrls: false,
networkRequestHasHeaders: true,
networkResponseHasHeaders: true,
sessionSampleRate: 1,
useCompression: false,
useCompressionOption: true,
},
tag: 'options',
},
timestamp: expect.any(Number),
type: 5,
},
{
type: 2,
data: {
Expand Down
7 changes: 6 additions & 1 deletion packages/replay/src/eventBuffer/EventBufferArray.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { AddEventResult, EventBuffer, RecordingEvent } from '../types';
import type { AddEventResult, EventBuffer, EventBufferType, RecordingEvent } from '../types';
import { timestampToMs } from '../util/timestampToMs';

/**
Expand All @@ -18,6 +18,11 @@ export class EventBufferArray implements EventBuffer {
return this.events.length > 0;
}

/** @inheritdoc */
public get type(): EventBufferType {
return 'sync';
}

/** @inheritdoc */
public destroy(): void {
this.events = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import type { ReplayRecordingData } from '@sentry/types';

import type { AddEventResult, EventBuffer, RecordingEvent } from '../types';
import type { AddEventResult, EventBuffer, EventBufferType, RecordingEvent } from '../types';
import { timestampToMs } from '../util/timestampToMs';
import { WorkerHandler } from './WorkerHandler';

Expand All @@ -22,6 +22,11 @@ export class EventBufferCompressionWorker implements EventBuffer {
return !!this._earliestTimestamp;
}

/** @inheritdoc */
public get type(): EventBufferType {
return 'worker';
}

/**
* Ensure the worker is ready (or not).
* This will either resolve when the worker is ready, or reject if an error occured.
Expand Down
7 changes: 6 additions & 1 deletion packages/replay/src/eventBuffer/EventBufferProxy.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { ReplayRecordingData } from '@sentry/types';
import { logger } from '@sentry/utils';

import type { AddEventResult, EventBuffer, RecordingEvent } from '../types';
import type { AddEventResult, EventBuffer, EventBufferType, RecordingEvent } from '../types';
import { EventBufferArray } from './EventBufferArray';
import { EventBufferCompressionWorker } from './EventBufferCompressionWorker';

Expand All @@ -24,6 +24,11 @@ export class EventBufferProxy implements EventBuffer {
this._ensureWorkerIsLoadedPromise = this._ensureWorkerIsLoaded();
}

/** @inheritdoc */
public get type(): EventBufferType {
return this._used.type;
}

/** @inheritDoc */
public get hasEvents(): boolean {
return this._used.hasEvents;
Expand Down
2 changes: 2 additions & 0 deletions packages/replay/src/integration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,8 @@ export class Replay implements Integration {
errorSampleRate,
useCompression,
blockAllMedia,
maskAllInputs,
maskAllText,
networkDetailAllowUrls,
networkCaptureBodies,
networkRequestHeaders: _getMergedNetworkHeaders(networkRequestHeaders),
Expand Down
17 changes: 17 additions & 0 deletions packages/replay/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,16 @@ export interface ReplayPluginOptions extends ReplayNetworkOptions {
*/
blockAllMedia: boolean;

/**
* Mask all inputs in recordings
*/
maskAllInputs: boolean;

/**
* Mask all text in recordings
*/
maskAllText: boolean;

/**
* _experiments allows users to enable experimental or internal features.
* We don't consider such features as part of the public API and hence we don't guarantee semver for them.
Expand Down Expand Up @@ -435,12 +445,19 @@ export interface Session {
shouldRefresh: boolean;
}

export type EventBufferType = 'sync' | 'worker';

export interface EventBuffer {
/**
* If any events have been added to the buffer.
*/
readonly hasEvents: boolean;

/**
* The buffer type
*/
readonly type: EventBufferType;

/**
* Destroy the event buffer.
*/
Expand Down
2 changes: 1 addition & 1 deletion packages/replay/src/types/rrweb.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
type blockClass = string | RegExp;
type maskTextClass = string | RegExp;

enum EventType {
export enum EventType {
DomContentLoaded = 0,
Load = 1,
FullSnapshot = 2,
Expand Down
Loading