From f875c2b50d464eafe6186e903c2f837dda520954 Mon Sep 17 00:00:00 2001 From: Bastien Caudan <1331991+bcaudan@users.noreply.github.com> Date: Mon, 5 Jun 2023 14:25:52 +0200 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=A5=20[RUMF-1589]=20automatically=20st?= =?UTF-8?q?art=20recording=20(#2275)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ♻️ buffer start/stop recorder API calls before init * ✨add option to opt out of automatic recording * 💥automatically start recording by default * ♻️ cleanup scenarios using replay * 👌fix test description --- .../rum-core/src/boot/rumPublicApi.spec.ts | 35 ++++- packages/rum-core/src/boot/rumPublicApi.ts | 16 ++- .../rum-core/src/domain/configuration.spec.ts | 28 ++++ packages/rum-core/src/domain/configuration.ts | 4 + packages/rum/src/boot/recorderApi.spec.ts | 126 +++++++++--------- packages/rum/src/boot/recorderApi.ts | 5 +- test/e2e/lib/helpers/replay.ts | 6 - .../scenario/recorder/recorder.scenario.ts | 21 +-- .../scenario/recorder/shadowDom.scenario.ts | 11 -- .../scenario/recorder/viewports.scenario.ts | 10 -- test/e2e/scenario/rum/sessions.scenario.ts | 7 +- 11 files changed, 152 insertions(+), 117 deletions(-) diff --git a/packages/rum-core/src/boot/rumPublicApi.spec.ts b/packages/rum-core/src/boot/rumPublicApi.spec.ts index e741cb6068..171069c7de 100644 --- a/packages/rum-core/src/boot/rumPublicApi.spec.ts +++ b/packages/rum-core/src/boot/rumPublicApi.spec.ts @@ -829,10 +829,12 @@ describe('rum public api', () => { let recorderApiOnRumStartSpy: jasmine.Spy let setupBuilder: TestSetupBuilder let rumPublicApi: RumPublicApi + let recorderApi: RecorderApi beforeEach(() => { recorderApiOnRumStartSpy = jasmine.createSpy('recorderApiOnRumStart') - rumPublicApi = makeRumPublicApi(noopStartRum, { ...noopRecorderApi, onRumStart: recorderApiOnRumStartSpy }) + recorderApi = { ...noopRecorderApi, onRumStart: recorderApiOnRumStartSpy } + rumPublicApi = makeRumPublicApi(noopStartRum, recorderApi) setupBuilder = setup() }) @@ -840,12 +842,12 @@ describe('rum public api', () => { setupBuilder.cleanup() }) - it('recording is started with the default defaultPrivacyLevel', () => { + it('is started with the default defaultPrivacyLevel', () => { rumPublicApi.init(DEFAULT_INIT_CONFIGURATION) expect(recorderApiOnRumStartSpy.calls.mostRecent().args[1].defaultPrivacyLevel).toBe(DefaultPrivacyLevel.MASK) }) - it('recording is started with the configured defaultPrivacyLevel', () => { + it('is started with the configured defaultPrivacyLevel', () => { rumPublicApi.init({ ...DEFAULT_INIT_CONFIGURATION, defaultPrivacyLevel: DefaultPrivacyLevel.MASK_USER_INPUT, @@ -854,6 +856,33 @@ describe('rum public api', () => { DefaultPrivacyLevel.MASK_USER_INPUT ) }) + + it('api calls before init are performed after onRumStart', () => { + // in order to let recording initial state to be defined by init configuration + const callOrders: string[] = [] + spyOn(recorderApi, 'start').and.callFake(() => callOrders.push('start')) + spyOn(recorderApi, 'stop').and.callFake(() => callOrders.push('stop')) + recorderApiOnRumStartSpy.and.callFake(() => callOrders.push('onRumStart')) + + rumPublicApi.startSessionReplayRecording() + rumPublicApi.stopSessionReplayRecording() + rumPublicApi.init(DEFAULT_INIT_CONFIGURATION) + + expect(callOrders).toEqual(['onRumStart', 'start', 'stop']) + }) + + it('is started with the default startSessionReplayRecordingManually', () => { + rumPublicApi.init(DEFAULT_INIT_CONFIGURATION) + expect(recorderApiOnRumStartSpy.calls.mostRecent().args[1].startSessionReplayRecordingManually).toBe(false) + }) + + it('is started with the configured startSessionReplayRecordingManually', () => { + rumPublicApi.init({ + ...DEFAULT_INIT_CONFIGURATION, + startSessionReplayRecordingManually: true, + }) + expect(recorderApiOnRumStartSpy.calls.mostRecent().args[1].startSessionReplayRecordingManually).toBe(true) + }) }) it('should provide sdk version', () => { diff --git a/packages/rum-core/src/boot/rumPublicApi.ts b/packages/rum-core/src/boot/rumPublicApi.ts index 284cb6bddb..7be43fdf9e 100644 --- a/packages/rum-core/src/boot/rumPublicApi.ts +++ b/packages/rum-core/src/boot/rumPublicApi.ts @@ -94,6 +94,14 @@ export function makeRumPublicApi( bufferApiCalls.add(() => addErrorStrategy(providedError, commonContext)) } + let recorderStartStrategy: typeof recorderApi.start = () => { + bufferApiCalls.add(() => recorderStartStrategy()) + } + + let recorderStopStrategy: typeof recorderApi.stop = () => { + bufferApiCalls.add(() => recorderStopStrategy()) + } + let addFeatureFlagEvaluationStrategy: StartRumResult['addFeatureFlagEvaluation'] = (key: string, value: any) => { bufferApiCalls.add(() => addFeatureFlagEvaluationStrategy(key, value)) } @@ -158,6 +166,8 @@ export function makeRumPublicApi( ) getSessionReplayLinkStrategy = () => recorderApi.getSessionReplayLink(configuration, startRumResults.session, startRumResults.viewContexts) + recorderStartStrategy = recorderApi.start + recorderStopStrategy = recorderApi.stop ;({ startView: startViewStrategy, addAction: addActionStrategy, @@ -167,7 +177,6 @@ export function makeRumPublicApi( getInternalContext: getInternalContextStrategy, stopSession: stopSessionStrategy, } = startRumResults) - bufferApiCalls.drain() recorderApi.onRumStart( startRumResults.lifeCycle, @@ -175,6 +184,7 @@ export function makeRumPublicApi( startRumResults.session, startRumResults.viewContexts ) + bufferApiCalls.drain() } const startView: { @@ -249,8 +259,8 @@ export function makeRumPublicApi( stopSessionStrategy() }), - startSessionReplayRecording: monitor(recorderApi.start), - stopSessionReplayRecording: monitor(recorderApi.stop), + startSessionReplayRecording: monitor(() => recorderStartStrategy()), + stopSessionReplayRecording: monitor(() => recorderStopStrategy()), /** * This feature is currently in beta. For more information see the full [feature flag tracking guide](https://docs.datadoghq.com/real_user_monitoring/feature_flag_tracking/). diff --git a/packages/rum-core/src/domain/configuration.spec.ts b/packages/rum-core/src/domain/configuration.spec.ts index 963a4c8295..b3dbd9489a 100644 --- a/packages/rum-core/src/domain/configuration.spec.ts +++ b/packages/rum-core/src/domain/configuration.spec.ts @@ -240,6 +240,34 @@ describe('validateAndBuildRumConfiguration', () => { }) }) + describe('startSessionReplayRecordingManually', () => { + it('defaults to false', () => { + expect( + validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.startSessionReplayRecordingManually + ).toBeFalse() + }) + + it('is set to provided value', () => { + expect( + validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, startSessionReplayRecordingManually: true })! + .startSessionReplayRecordingManually + ).toBeTrue() + expect( + validateAndBuildRumConfiguration({ ...DEFAULT_INIT_CONFIGURATION, startSessionReplayRecordingManually: false })! + .startSessionReplayRecordingManually + ).toBeFalse() + }) + + it('the provided value is cast to boolean', () => { + expect( + validateAndBuildRumConfiguration({ + ...DEFAULT_INIT_CONFIGURATION, + startSessionReplayRecordingManually: 'foo' as any, + })!.startSessionReplayRecordingManually + ).toBeTrue() + }) + }) + describe('actionNameAttribute', () => { it('defaults to undefined', () => { expect(validateAndBuildRumConfiguration(DEFAULT_INIT_CONFIGURATION)!.actionNameAttribute).toBeUndefined() diff --git a/packages/rum-core/src/domain/configuration.ts b/packages/rum-core/src/domain/configuration.ts index 845eb47f09..eba13ac2c4 100644 --- a/packages/rum-core/src/domain/configuration.ts +++ b/packages/rum-core/src/domain/configuration.ts @@ -30,6 +30,7 @@ export interface RumInitConfiguration extends InitConfiguration { defaultPrivacyLevel?: DefaultPrivacyLevel | undefined subdomain?: string sessionReplaySampleRate?: number | undefined + startSessionReplayRecordingManually?: boolean | undefined // action options trackUserInteractions?: boolean | undefined @@ -53,6 +54,7 @@ export interface RumConfiguration extends Configuration { applicationId: string defaultPrivacyLevel: DefaultPrivacyLevel sessionReplaySampleRate: number + startSessionReplayRecordingManually: boolean trackUserInteractions: boolean trackViewsManually: boolean trackResources: boolean @@ -104,6 +106,7 @@ export function validateAndBuildRumConfiguration( version: initConfiguration.version, actionNameAttribute: initConfiguration.actionNameAttribute, sessionReplaySampleRate: initConfiguration.sessionReplaySampleRate ?? 0, + startSessionReplayRecordingManually: !!initConfiguration.startSessionReplayRecordingManually, traceSampleRate: initConfiguration.traceSampleRate, allowedTracingUrls, excludedActivityUrls: initConfiguration.excludedActivityUrls ?? [], @@ -181,6 +184,7 @@ export function serializeRumConfiguration(configuration: RumInitConfiguration): return assign( { session_replay_sample_rate: configuration.sessionReplaySampleRate, + start_session_replay_recording_manually: configuration.startSessionReplayRecordingManually, trace_sample_rate: configuration.traceSampleRate, action_name_attribute: configuration.actionNameAttribute, use_allowed_tracing_urls: diff --git a/packages/rum/src/boot/recorderApi.spec.ts b/packages/rum/src/boot/recorderApi.spec.ts index 77598cff30..3be3b0a583 100644 --- a/packages/rum/src/boot/recorderApi.spec.ts +++ b/packages/rum/src/boot/recorderApi.spec.ts @@ -32,11 +32,13 @@ describe('makeRecorderApi', () => { } let rumInit: () => void + let startSessionReplayRecordingManually: boolean beforeEach(() => { if (isIE()) { pending('IE not supported') } + startSessionReplayRecordingManually = false setupBuilder = setup().beforeBuild(({ lifeCycle, sessionManager }) => { stopRecordingSpy = jasmine.createSpy('stopRecording') startRecordingSpy = jasmine.createSpy('startRecording').and.callFake(() => ({ @@ -46,7 +48,12 @@ describe('makeRecorderApi', () => { startDeflateWorkerWith(FAKE_WORKER) recorderApi = makeRecorderApi(startRecordingSpy, startDeflateWorkerSpy) rumInit = () => { - recorderApi.onRumStart(lifeCycle, {} as RumConfiguration, sessionManager, {} as ViewContexts) + recorderApi.onRumStart( + lifeCycle, + { startSessionReplayRecordingManually } as RumConfiguration, + sessionManager, + {} as ViewContexts + ) } }) }) @@ -55,26 +62,48 @@ describe('makeRecorderApi', () => { setupBuilder.cleanup() }) - describe('boot', () => { - it('does not start recording when init() is called', () => { - setupBuilder.build() - expect(startRecordingSpy).not.toHaveBeenCalled() - rumInit() - expect(startRecordingSpy).not.toHaveBeenCalled() + describe('recorder boot', () => { + describe('with automatic start', () => { + it('starts recording when init() is called', () => { + setupBuilder.build() + expect(startRecordingSpy).not.toHaveBeenCalled() + rumInit() + expect(startRecordingSpy).toHaveBeenCalled() + }) + + it('starts recording after the DOM is loaded', () => { + setupBuilder.build() + const { triggerOnDomLoaded } = mockDocumentReadyState() + rumInit() + expect(startRecordingSpy).not.toHaveBeenCalled() + triggerOnDomLoaded() + expect(startRecordingSpy).toHaveBeenCalled() + }) }) - it('does not start recording after the DOM is loaded', () => { - setupBuilder.build() - const { triggerOnDomLoaded } = mockDocumentReadyState() - rumInit() - expect(startRecordingSpy).not.toHaveBeenCalled() - triggerOnDomLoaded() - expect(startRecordingSpy).not.toHaveBeenCalled() + describe('with manual start', () => { + it('does not start recording when init() is called', () => { + startSessionReplayRecordingManually = true + setupBuilder.build() + expect(startRecordingSpy).not.toHaveBeenCalled() + rumInit() + expect(startRecordingSpy).not.toHaveBeenCalled() + }) + + it('does not start recording after the DOM is loaded', () => { + startSessionReplayRecordingManually = true + setupBuilder.build() + const { triggerOnDomLoaded } = mockDocumentReadyState() + rumInit() + expect(startRecordingSpy).not.toHaveBeenCalled() + triggerOnDomLoaded() + expect(startRecordingSpy).not.toHaveBeenCalled() + }) }) }) - describe('startSessionReplayRecording()', () => { - it('ignores calls while recording is already started', () => { + describe('recorder start', () => { + it('ignores additional start calls while recording is already started', () => { setupBuilder.build() rumInit() recorderApi.start() @@ -83,32 +112,24 @@ describe('makeRecorderApi', () => { expect(startRecordingSpy).toHaveBeenCalledTimes(1) }) - it('starts recording if called before init()', () => { - setupBuilder.build() - recorderApi.start() - rumInit() - expect(startRecordingSpy).toHaveBeenCalled() - }) - - it('does not start recording multiple times if restarted before the DOM is loaded', () => { + it('ignores restart before the DOM is loaded', () => { setupBuilder.build() const { triggerOnDomLoaded } = mockDocumentReadyState() rumInit() - recorderApi.start() recorderApi.stop() recorderApi.start() triggerOnDomLoaded() expect(startRecordingSpy).toHaveBeenCalledTimes(1) }) - it('ignores calls if the session is not tracked', () => { + it('ignores start calls if the session is not tracked', () => { setupBuilder.withSessionManager(createRumSessionManagerMock().setNotTracked()).build() rumInit() recorderApi.start() expect(startRecordingSpy).not.toHaveBeenCalled() }) - it('ignores calls if the session plan is WITHOUT_REPLAY', () => { + it('ignores start calls if the session plan is WITHOUT_REPLAY', () => { setupBuilder.withSessionManager(createRumSessionManagerMock().setPlanWithoutSessionReplay()).build() rumInit() recorderApi.start() @@ -117,17 +138,15 @@ describe('makeRecorderApi', () => { it('do not start recording if worker fails to be instantiated', () => { setupBuilder.build() - rumInit() startDeflateWorkerWith(undefined) - recorderApi.start() + rumInit() expect(startRecordingSpy).not.toHaveBeenCalled() }) it('does not start recording multiple times if restarted before worker is initialized', () => { setupBuilder.build() - rumInit() stopDeflateWorker() - recorderApi.start() + rumInit() recorderApi.stop() callLastRegisteredInitialisationCallback() @@ -174,30 +193,20 @@ describe('makeRecorderApi', () => { }) }) - describe('stopSessionReplayRecording()', () => { + describe('recorder stop', () => { it('ignores calls while recording is already stopped', () => { setupBuilder.build() rumInit() - recorderApi.start() recorderApi.stop() recorderApi.stop() recorderApi.stop() expect(stopRecordingSpy).toHaveBeenCalledTimes(1) }) - it('does not start recording if called before init()', () => { - setupBuilder.build() - recorderApi.start() - recorderApi.stop() - rumInit() - expect(startRecordingSpy).not.toHaveBeenCalled() - }) - it('prevents recording to start when the DOM is loaded', () => { setupBuilder.build() const { triggerOnDomLoaded } = mockDocumentReadyState() rumInit() - recorderApi.start() recorderApi.stop() triggerOnDomLoaded() expect(startRecordingSpy).not.toHaveBeenCalled() @@ -211,7 +220,6 @@ describe('makeRecorderApi', () => { sessionManager = createRumSessionManagerMock() setupBuilder.withSessionManager(sessionManager) ;({ lifeCycle } = setupBuilder.build()) - rumInit() }) describe('from WITHOUT_REPLAY to WITH_REPLAY', () => { @@ -220,7 +228,7 @@ describe('makeRecorderApi', () => { }) it('starts recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() sessionManager.setPlanWithSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) expect(startRecordingSpy).not.toHaveBeenCalled() @@ -230,7 +238,7 @@ describe('makeRecorderApi', () => { }) it('does not starts recording if stopSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() recorderApi.stop() sessionManager.setPlanWithSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) @@ -245,7 +253,7 @@ describe('makeRecorderApi', () => { }) it('keeps not recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() sessionManager.setNotTracked() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) @@ -260,7 +268,7 @@ describe('makeRecorderApi', () => { }) it('keeps not recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) expect(startRecordingSpy).not.toHaveBeenCalled() @@ -274,7 +282,7 @@ describe('makeRecorderApi', () => { }) it('stops recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() expect(startRecordingSpy).toHaveBeenCalledTimes(1) sessionManager.setPlanWithoutSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) @@ -284,10 +292,8 @@ describe('makeRecorderApi', () => { }) it('prevents session recording to start if the session is renewed before the DOM is loaded', () => { - setupBuilder.build() const { triggerOnDomLoaded } = mockDocumentReadyState() rumInit() - recorderApi.start() sessionManager.setPlanWithoutSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) @@ -302,7 +308,7 @@ describe('makeRecorderApi', () => { }) it('stops recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() expect(startRecordingSpy).toHaveBeenCalledTimes(1) sessionManager.setNotTracked() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) @@ -318,7 +324,7 @@ describe('makeRecorderApi', () => { }) it('keeps recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() expect(startRecordingSpy).toHaveBeenCalledTimes(1) lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) expect(stopRecordingSpy).toHaveBeenCalled() @@ -327,7 +333,7 @@ describe('makeRecorderApi', () => { }) it('does not starts recording if stopSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() expect(startRecordingSpy).toHaveBeenCalledTimes(1) recorderApi.stop() expect(stopRecordingSpy).toHaveBeenCalledTimes(1) @@ -344,7 +350,7 @@ describe('makeRecorderApi', () => { }) it('starts recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() sessionManager.setPlanWithSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) @@ -353,7 +359,7 @@ describe('makeRecorderApi', () => { }) it('does not starts recording if stopSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() recorderApi.stop() sessionManager.setPlanWithSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) @@ -369,7 +375,7 @@ describe('makeRecorderApi', () => { }) it('keeps not recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() sessionManager.setPlanWithoutSessionReplay() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) @@ -384,7 +390,7 @@ describe('makeRecorderApi', () => { }) it('keeps not recording if startSessionReplayRecording was called', () => { - recorderApi.start() + rumInit() lifeCycle.notify(LifeCycleEventType.SESSION_EXPIRED) lifeCycle.notify(LifeCycleEventType.SESSION_RENEWED) expect(startRecordingSpy).not.toHaveBeenCalled() @@ -397,11 +403,11 @@ describe('makeRecorderApi', () => { it('is true only if recording', () => { setupBuilder.build() rumInit() - expect(recorderApi.isRecording()).toBeFalse() - recorderApi.start() expect(recorderApi.isRecording()).toBeTrue() recorderApi.stop() expect(recorderApi.isRecording()).toBeFalse() + recorderApi.start() + expect(recorderApi.isRecording()).toBeTrue() }) it('is false before the DOM is loaded', () => { @@ -409,8 +415,6 @@ describe('makeRecorderApi', () => { const { triggerOnDomLoaded } = mockDocumentReadyState() rumInit() expect(recorderApi.isRecording()).toBeFalse() - recorderApi.start() - expect(recorderApi.isRecording()).toBeFalse() triggerOnDomLoaded() expect(recorderApi.isRecording()).toBeTrue() }) diff --git a/packages/rum/src/boot/recorderApi.ts b/packages/rum/src/boot/recorderApi.ts index 65a8a2a6d9..5e057eb1b2 100644 --- a/packages/rum/src/boot/recorderApi.ts +++ b/packages/rum/src/boot/recorderApi.ts @@ -58,7 +58,7 @@ export function makeRecorderApi( } let state: RecorderState = { - status: RecorderStatus.Stopped, + status: RecorderStatus.IntentToStart, } let startStrategy = () => { @@ -79,6 +79,9 @@ export function makeRecorderApi( sessionManager: RumSessionManager, viewContexts: ViewContexts ) => { + if (configuration.startSessionReplayRecordingManually) { + state = { status: RecorderStatus.Stopped } + } lifeCycle.subscribe(LifeCycleEventType.SESSION_EXPIRED, () => { if (state.status === RecorderStatus.Starting || state.status === RecorderStatus.Started) { stopStrategy() diff --git a/test/e2e/lib/helpers/replay.ts b/test/e2e/lib/helpers/replay.ts index 3ce6ef93f4..1a0331ef1f 100644 --- a/test/e2e/lib/helpers/replay.ts +++ b/test/e2e/lib/helpers/replay.ts @@ -1,4 +1,3 @@ -import type { RumInitConfiguration } from '@datadog/browser-rum-core' import type { EventRegistry } from '../framework' export function getFirstSegment(events: EventRegistry) { @@ -8,8 +7,3 @@ export function getFirstSegment(events: EventRegistry) { export function getLastSegment(events: EventRegistry) { return events.sessionReplay[events.sessionReplay.length - 1].segment.data } - -export function initRumAndStartRecording(initConfiguration: RumInitConfiguration) { - window.DD_RUM!.init(initConfiguration) - window.DD_RUM!.startSessionReplayRecording() -} diff --git a/test/e2e/scenario/recorder/recorder.scenario.ts b/test/e2e/scenario/recorder/recorder.scenario.ts index 5652aad37d..4a26a129bd 100644 --- a/test/e2e/scenario/recorder/recorder.scenario.ts +++ b/test/e2e/scenario/recorder/recorder.scenario.ts @@ -20,7 +20,7 @@ import { } from '@datadog/browser-rum/test' import { flushEvents, createTest, bundleSetup, html } from '../../lib/framework' import { browserExecute, browserExecuteAsync } from '../../lib/helpers/browser' -import { getFirstSegment, getLastSegment, initRumAndStartRecording } from '../../lib/helpers/replay' +import { getFirstSegment, getLastSegment } from '../../lib/helpers/replay' const TIMESTAMP_RE = /^\d{13}$/ const UUID_RE = /^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/ @@ -28,7 +28,6 @@ const UUID_RE = /^[0-9a-f]{8}-([0-9a-f]{4}-){3}[0-9a-f]{12}$/ describe('recorder', () => { createTest('record mouse move') .withRum() - .withRumInit(initRumAndStartRecording) .run(async ({ serverEvents }) => { await browserExecute(() => document.documentElement.outerHTML) const html = await $('html') @@ -79,7 +78,6 @@ describe('recorder', () => { describe('full snapshot', () => { createTest('obfuscate elements') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -125,7 +123,6 @@ describe('recorder', () => { describe('mutations observer', () => { createTest('record mutations') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -172,7 +169,6 @@ describe('recorder', () => { createTest('record character data mutations') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -225,7 +221,6 @@ describe('recorder', () => { createTest('record attributes mutations') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -270,7 +265,6 @@ describe('recorder', () => { createTest("don't record hidden elements mutations") .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -298,7 +292,6 @@ describe('recorder', () => { createTest('record DOM node movement 1') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( // prettier-ignore @@ -347,7 +340,6 @@ describe('recorder', () => { createTest('record DOM node movement 2') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( // prettier-ignore @@ -399,7 +391,6 @@ describe('recorder', () => { createTest('serialize node before record') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( // prettier-ignore @@ -455,7 +446,6 @@ describe('recorder', () => { .withRum({ defaultPrivacyLevel: DefaultPrivacyLevel.ALLOW, }) - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -537,7 +527,6 @@ describe('recorder', () => { .withRum({ defaultPrivacyLevel: DefaultPrivacyLevel.ALLOW, }) - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -574,7 +563,6 @@ describe('recorder', () => { createTest('replace masked values by asterisks') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -608,7 +596,6 @@ describe('recorder', () => { describe('stylesheet rules observer', () => { createTest('record dynamic CSS changes') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -643,7 +630,6 @@ describe('recorder', () => { createTest('record nested css rules changes') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -691,7 +677,6 @@ describe('recorder', () => { describe('frustration records', () => { createTest('should detect a dead click and match it to mouse interaction record') .withRum({ trackUserInteractions: true }) - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .run(async ({ serverEvents }) => { const html = await $('html') @@ -715,7 +700,6 @@ describe('recorder', () => { createTest('should detect a rage click and match it to mouse interaction records') .withRum({ trackUserInteractions: true }) - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -748,7 +732,8 @@ describe('recorder', () => { describe('scroll positions', () => { createTest('should be recorded across navigation') - .withRum() + // to control initial position before recording + .withRum({ startSessionReplayRecordingManually: true }) .withSetup(bundleSetup) .withBody( html` diff --git a/test/e2e/scenario/recorder/shadowDom.scenario.ts b/test/e2e/scenario/recorder/shadowDom.scenario.ts index b01f4ed545..3b9057e0ee 100644 --- a/test/e2e/scenario/recorder/shadowDom.scenario.ts +++ b/test/e2e/scenario/recorder/shadowDom.scenario.ts @@ -1,4 +1,3 @@ -import type { RumInitConfiguration } from '@datadog/browser-rum-core' import { IncrementalSource, NodeType } from '@datadog/browser-rum/src/types' import type { DocumentFragmentNode, MouseInteractionData, SerializedNodeWithId } from '@datadog/browser-rum/src/types' @@ -113,7 +112,6 @@ class DivWithStyle extends HTMLElement { describe('recorder with shadow DOM', () => { createTest('can record fullsnapshot with the detail inside the shadow root') .withRum({ defaultPrivacyLevel: 'allow' }) - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -136,7 +134,6 @@ describe('recorder with shadow DOM', () => { createTest('can record fullsnapshot with adoptedStylesheet') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -164,7 +161,6 @@ describe('recorder with shadow DOM', () => { createTest('can apply privacy level set from outside or inside the shadow DOM') .withRum({ defaultPrivacyLevel: 'allow' }) - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -202,7 +198,6 @@ describe('recorder with shadow DOM', () => { createTest('can record click with target from inside the shadow root') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -228,7 +223,6 @@ describe('recorder with shadow DOM', () => { createTest('can record mutation from inside the shadow root') .withRum({ defaultPrivacyLevel: 'allow' }) - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody( html` @@ -287,11 +281,6 @@ function getFirstSegment(events: EventRegistry) { return events.sessionReplay[0].segment.data } -function initRumAndStartRecording(initConfiguration: RumInitConfiguration) { - window.DD_RUM!.init(initConfiguration) - window.DD_RUM!.startSessionReplayRecording() -} - async function getNodeInsideShadowDom(hostTag: string, selector: string) { const host = await $(hostTag) return host.shadow$(selector) diff --git a/test/e2e/scenario/recorder/viewports.scenario.ts b/test/e2e/scenario/recorder/viewports.scenario.ts index fc30e31e6a..65d429299b 100644 --- a/test/e2e/scenario/recorder/viewports.scenario.ts +++ b/test/e2e/scenario/recorder/viewports.scenario.ts @@ -1,6 +1,5 @@ import type { ViewportResizeData, ScrollData } from '@datadog/browser-rum/cjs/types' import { IncrementalSource } from '@datadog/browser-rum/cjs/types' -import type { RumInitConfiguration } from '@datadog/browser-rum-core' import { findAllIncrementalSnapshots, findAllVisualViewports } from '@datadog/browser-rum/test' import type { EventRegistry } from '../../lib/framework' @@ -26,7 +25,6 @@ describe('recorder', () => { describe('layout viewport properties', () => { createTest('getWindowWidth/Height should not be affected by pinch zoom') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody(html`${VIEWPORT_META_TAGS}`) .run(async ({ serverEvents }) => { @@ -57,7 +55,6 @@ describe('recorder', () => { */ createTest('getScrollX/Y should not be affected by pinch scroll') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody(html`${VIEWPORT_META_TAGS}`) .run(async ({ serverEvents }) => { @@ -99,7 +96,6 @@ describe('recorder', () => { describe('visual viewport properties', () => { createTest('pinch zoom "scroll" event reports visual viewport position') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody(html`${VIEWPORT_META_TAGS}`) .run(async ({ serverEvents }) => { @@ -114,7 +110,6 @@ describe('recorder', () => { createTest('pinch zoom "resize" event reports visual viewport scale') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .withBody(html`${VIEWPORT_META_TAGS}`) .run(async ({ serverEvents }) => { @@ -130,11 +125,6 @@ function getLastSegment(serverEvents: EventRegistry) { return serverEvents.sessionReplay[serverEvents.sessionReplay.length - 1].segment.data } -function initRumAndStartRecording(initConfiguration: RumInitConfiguration) { - window.DD_RUM!.init(initConfiguration) - window.DD_RUM!.startSessionReplayRecording() -} - const isGestureUnsupported = () => /firefox|safari|edge/.test(getBrowserName()) || /windows|linux/.test(getPlatformName()) diff --git a/test/e2e/scenario/rum/sessions.scenario.ts b/test/e2e/scenario/rum/sessions.scenario.ts index c912930dbf..01523b457d 100644 --- a/test/e2e/scenario/rum/sessions.scenario.ts +++ b/test/e2e/scenario/rum/sessions.scenario.ts @@ -2,7 +2,7 @@ import { RecordType } from '@datadog/browser-rum/src/types' import { expireSession, findSessionCookie, renewSession } from '../../lib/helpers/session' import { bundleSetup, createTest, flushEvents, waitForRequests } from '../../lib/framework' import { browserExecute, browserExecuteAsync, sendXhr } from '../../lib/helpers/browser' -import { getLastSegment, initRumAndStartRecording } from '../../lib/helpers/replay' +import { getLastSegment } from '../../lib/helpers/replay' describe('rum sessions', () => { describe('session renewal', () => { @@ -23,7 +23,6 @@ describe('rum sessions', () => { createTest('a single fullSnapshot is taken when the session is renewed') .withRum() - .withRumInit(initRumAndStartRecording) .withSetup(bundleSetup) .run(async ({ serverEvents }) => { await renewSession() @@ -43,7 +42,8 @@ describe('rum sessions', () => { describe('session expiration', () => { createTest("don't send events when session is expired") - .withRum() + // prevent recording start to generate late events + .withRum({ startSessionReplayRecordingManually: true }) .run(async ({ serverEvents }) => { await expireSession() serverEvents.empty() @@ -98,7 +98,6 @@ describe('rum sessions', () => { createTest('flush events when the session expires') .withRum() .withLogs() - .withRumInit(initRumAndStartRecording) .run(async ({ serverEvents }) => { expect(serverEvents.rumViews.length).toBe(0) expect(serverEvents.logs.length).toBe(0)