From 62375d9f4417cb967afe148bfc2616a9672d38eb Mon Sep 17 00:00:00 2001 From: zcy Date: Wed, 8 Jan 2025 16:53:03 +0100 Subject: [PATCH 1/3] Distinguish session id from anonymous id --- packages/core/src/domain/session/sessionManager.spec.ts | 2 +- packages/logs/src/domain/logsSessionManager.spec.ts | 4 ++-- packages/rum-core/src/domain/rumSessionManager.spec.ts | 6 +++--- test/e2e/scenario/rum/sessions.scenario.ts | 2 +- 4 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/core/src/domain/session/sessionManager.spec.ts b/packages/core/src/domain/session/sessionManager.spec.ts index 850edc70af..7dc4e772bd 100644 --- a/packages/core/src/domain/session/sessionManager.spec.ts +++ b/packages/core/src/domain/session/sessionManager.spec.ts @@ -61,7 +61,7 @@ describe('startSessionManager', () => { expect(sessionManager.findSession()!.id).toMatch(/^[a-f0-9-]+$/) expect(sessionManager.findSession()?.isExpired).toBeUndefined() - expect(getCookie(SESSION_STORE_KEY)).toMatch(/id=[a-f0-9-]+/) + expect(getCookie(SESSION_STORE_KEY)).toMatch(/\bid=[a-f0-9-]+/) expect(getCookie(SESSION_STORE_KEY)).not.toContain('isExpired=1') } diff --git a/packages/logs/src/domain/logsSessionManager.spec.ts b/packages/logs/src/domain/logsSessionManager.spec.ts index 90c5281297..1739be3814 100644 --- a/packages/logs/src/domain/logsSessionManager.spec.ts +++ b/packages/logs/src/domain/logsSessionManager.spec.ts @@ -43,7 +43,7 @@ describe('logs session manager', () => { startLogsSessionManagerWithDefaults() expect(getCookie(SESSION_STORE_KEY)).toContain(`${LOGS_SESSION_KEY}=${LoggerTrackingType.TRACKED}`) - expect(getCookie(SESSION_STORE_KEY)).toMatch(/id=[a-f0-9-]+/) + expect(getCookie(SESSION_STORE_KEY)).toMatch(/\bid=[a-f0-9-]+/) }) it('when not tracked should store tracking type', () => { @@ -79,7 +79,7 @@ describe('logs session manager', () => { document.body.dispatchEvent(createNewEvent(DOM_EVENT.CLICK)) - expect(getCookie(SESSION_STORE_KEY)).toMatch(/id=[a-f0-9-]+/) + expect(getCookie(SESSION_STORE_KEY)).toMatch(/\bid=[a-f0-9-]+/) expect(getCookie(SESSION_STORE_KEY)).toContain(`${LOGS_SESSION_KEY}=${LoggerTrackingType.TRACKED}`) }) diff --git a/packages/rum-core/src/domain/rumSessionManager.spec.ts b/packages/rum-core/src/domain/rumSessionManager.spec.ts index 07918318ea..7153bb6c62 100644 --- a/packages/rum-core/src/domain/rumSessionManager.spec.ts +++ b/packages/rum-core/src/domain/rumSessionManager.spec.ts @@ -64,7 +64,7 @@ describe('rum session manager', () => { expect(getCookie(SESSION_STORE_KEY)).toContain( `${RUM_SESSION_KEY}=${RumTrackingType.TRACKED_WITH_SESSION_REPLAY}` ) - expect(getCookie(SESSION_STORE_KEY)).toMatch(/id=[a-f0-9-]/) + expect(getCookie(SESSION_STORE_KEY)).toMatch(/\bid=[a-f0-9-]/) }) it('when tracked without session replay should store session type and id', () => { @@ -75,7 +75,7 @@ describe('rum session manager', () => { expect(getCookie(SESSION_STORE_KEY)).toContain( `${RUM_SESSION_KEY}=${RumTrackingType.TRACKED_WITHOUT_SESSION_REPLAY}` ) - expect(getCookie(SESSION_STORE_KEY)).toMatch(/id=[a-f0-9-]/) + expect(getCookie(SESSION_STORE_KEY)).toMatch(/\bid=[a-f0-9-]/) }) it('when not tracked should store session type', () => { @@ -129,7 +129,7 @@ describe('rum session manager', () => { expect(getCookie(SESSION_STORE_KEY)).toContain( `${RUM_SESSION_KEY}=${RumTrackingType.TRACKED_WITH_SESSION_REPLAY}` ) - expect(getCookie(SESSION_STORE_KEY)).toMatch(/id=[a-f0-9-]/) + expect(getCookie(SESSION_STORE_KEY)).toMatch(/\bid=[a-f0-9-]/) }) }) diff --git a/test/e2e/scenario/rum/sessions.scenario.ts b/test/e2e/scenario/rum/sessions.scenario.ts index 69fd944382..3b27cde354 100644 --- a/test/e2e/scenario/rum/sessions.scenario.ts +++ b/test/e2e/scenario/rum/sessions.scenario.ts @@ -91,7 +91,7 @@ describe('rum sessions', () => { await flushEvents() expect(await findSessionCookie()).not.toContain('isExpired=1') - expect(await findSessionCookie()).toMatch(/id=[a-f0-9-]+/) + expect(await findSessionCookie()).toMatch(/\bid=[a-f0-9-]+/) expect(intakeRegistry.rumActionEvents.length).toBe(1) }) From ff0e8a86329ecf909def9fd2f1b93336f24f1e06 Mon Sep 17 00:00:00 2001 From: zcy Date: Wed, 8 Jan 2025 18:09:38 +0100 Subject: [PATCH 2/3] Add anonymous id e2e tests and clean up for session id matching --- test/e2e/lib/helpers/session.ts | 7 ++- test/e2e/scenario/rum/sessions.scenario.ts | 52 ++++++++++++++++++++++ 2 files changed, 58 insertions(+), 1 deletion(-) diff --git a/test/e2e/lib/helpers/session.ts b/test/e2e/lib/helpers/session.ts index 945a8df42b..2ac612248e 100644 --- a/test/e2e/lib/helpers/session.ts +++ b/test/e2e/lib/helpers/session.ts @@ -10,7 +10,12 @@ export async function renewSession() { } export async function expireSession() { - await setCookie(SESSION_STORE_KEY, 'isExpired=1', SESSION_TIME_OUT_DELAY) + // mock expire session with anonymous id + const cookies = await browser.getCookies(SESSION_STORE_KEY) + const anonymousId = cookies[0]?.value.match(/aid=[a-z0-9]+/) + const expireCookie = `isExpired=1&${anonymousId && anonymousId[0]}` + + await setCookie(SESSION_STORE_KEY, expireCookie, SESSION_TIME_OUT_DELAY) expect(await findSessionCookie()).toContain('isExpired=1') diff --git a/test/e2e/scenario/rum/sessions.scenario.ts b/test/e2e/scenario/rum/sessions.scenario.ts index 3b27cde354..9878b01861 100644 --- a/test/e2e/scenario/rum/sessions.scenario.ts +++ b/test/e2e/scenario/rum/sessions.scenario.ts @@ -50,6 +50,58 @@ describe('rum sessions', () => { expect(intakeRegistry.isEmpty).toBe(true) }) }) + describe('anonymous user id', () => { + createTest('persists when session is expired') + .withRum() + .run(async () => { + const prevSessionCookie = await findSessionCookie() + const anonymousId = prevSessionCookie?.match(/aid=[a-z0-9-]+/) + expect(anonymousId).not.toBeNull() + + await expireSession() + await flushEvents() + + if (anonymousId) { + expect(await findSessionCookie()).toContain(anonymousId[0]) + } + }) + + createTest('persists when session renewed') + .withRum() + .run(async () => { + const prevSessionCookie = await findSessionCookie() + const anonymousId = prevSessionCookie?.match(/aid=[a-z0-9-]+/) + expect(anonymousId).not.toBeNull() + + await browser.execute(() => { + window.DD_RUM!.stopSession() + }) + await (await $('html')).click() + + // The session is not created right away, let's wait until we see a cookie + await browser.waitUntil(async () => Boolean(await findSessionCookie())) + + await browser.execute(() => { + window.DD_RUM!.addAction('foo') + }) + + await flushEvents() + + if (anonymousId) { + expect(await findSessionCookie()).toContain(anonymousId[0]) + } + }) + + createTest('generated when cookie is cleared') + .withRum() + .run(async () => { + await deleteAllCookies() + await renewSession() + await flushEvents() + + expect(await findSessionCookie()).toMatch(/aid=[a-z0-9-]+/) + }) + }) describe('manual session expiration', () => { createTest('calling stopSession() stops the session') From fb948a41ceb40577fa7c8fce2d517560117eb9f1 Mon Sep 17 00:00:00 2001 From: zcy Date: Thu, 23 Jan 2025 16:17:57 +0100 Subject: [PATCH 3/3] Improve e2e and unit test with getSessionState from cookei --- .../session/oldCookiesMigration.spec.ts | 24 ++++++------ .../src/domain/session/sessionManager.spec.ts | 29 ++++++++------- .../src/domain/session/sessionStore.spec.ts | 24 ++++++------ packages/core/src/index.ts | 1 + packages/core/test/cookie.ts | 7 +++- .../src/domain/logsSessionManager.spec.ts | 23 ++++++------ .../src/domain/rumSessionManager.spec.ts | 37 ++++++++----------- test/e2e/lib/helpers/session.ts | 11 ++++-- test/e2e/scenario/rum/sessions.scenario.ts | 29 ++++----------- test/e2e/scenario/trackingConsent.scenario.ts | 2 +- 10 files changed, 89 insertions(+), 98 deletions(-) diff --git a/packages/core/src/domain/session/oldCookiesMigration.spec.ts b/packages/core/src/domain/session/oldCookiesMigration.spec.ts index 273b7897ea..1404733549 100644 --- a/packages/core/src/domain/session/oldCookiesMigration.spec.ts +++ b/packages/core/src/domain/session/oldCookiesMigration.spec.ts @@ -1,4 +1,5 @@ import { getCookie, resetInitCookies, setCookie } from '../../browser/cookie' +import { getSessionState } from '../../../test' import type { Configuration } from '../configuration' import { OLD_LOGS_COOKIE_NAME, @@ -39,20 +40,19 @@ describe('old cookies migration', () => { tryOldCookiesMigration(sessionStoreStrategy) - expect(getCookie(SESSION_STORE_KEY)).toContain('id=abcde') - expect(getCookie(SESSION_STORE_KEY)).toContain('rum=0') - expect(getCookie(SESSION_STORE_KEY)).toContain('logs=1') - expect(getCookie(SESSION_STORE_KEY)).toMatch(/expire=\d+/) + expect(getSessionState(SESSION_STORE_KEY).id).toBe('abcde') + expect(getSessionState(SESSION_STORE_KEY).rum).toBe('0') + expect(getSessionState(SESSION_STORE_KEY).logs).toBe('1') + expect(getSessionState(SESSION_STORE_KEY).expire).toMatch(/\d+/) }) it('should create new cookie from a single old cookie', () => { setCookie(OLD_RUM_COOKIE_NAME, '0', SESSION_EXPIRATION_DELAY) tryOldCookiesMigration(sessionStoreStrategy) - - expect(getCookie(SESSION_STORE_KEY)).not.toContain('id=') - expect(getCookie(SESSION_STORE_KEY)).toContain('rum=0') - expect(getCookie(SESSION_STORE_KEY)).toMatch(/expire=\d+/) + expect(getSessionState(SESSION_STORE_KEY).id).not.toBeDefined() + expect(getSessionState(SESSION_STORE_KEY).rum).toBe('0') + expect(getSessionState(SESSION_STORE_KEY).expire).toMatch(/\d+/) }) it('should not create a new cookie if no old cookie is present', () => { @@ -68,9 +68,9 @@ describe('old cookies migration', () => { tryOldCookiesMigration(sessionStoreStrategy) tryOldCookiesMigration(sessionStoreStrategy) - expect(getCookie(SESSION_STORE_KEY)).toContain('id=abcde') - expect(getCookie(SESSION_STORE_KEY)).toContain('rum=0') - expect(getCookie(SESSION_STORE_KEY)).toContain('logs=1') - expect(getCookie(SESSION_STORE_KEY)).toMatch(/expire=\d+/) + expect(getSessionState(SESSION_STORE_KEY).id).toBe('abcde') + expect(getSessionState(SESSION_STORE_KEY).rum).toBe('0') + expect(getSessionState(SESSION_STORE_KEY).logs).toBe('1') + expect(getSessionState(SESSION_STORE_KEY).expire).toMatch(/\d+/) }) }) diff --git a/packages/core/src/domain/session/sessionManager.spec.ts b/packages/core/src/domain/session/sessionManager.spec.ts index 7dc4e772bd..345502f1d6 100644 --- a/packages/core/src/domain/session/sessionManager.spec.ts +++ b/packages/core/src/domain/session/sessionManager.spec.ts @@ -1,6 +1,7 @@ import { createNewEvent, expireCookie, + getSessionState, mockClock, registerCleanupTask, restorePageVisibility, @@ -54,25 +55,25 @@ describe('startSessionManager', () => { function expectSessionIdToBe(sessionManager: SessionManager, sessionId: string) { expect(sessionManager.findSession()!.id).toBe(sessionId) - expect(getCookie(SESSION_STORE_KEY)).toContain(`id=${sessionId}`) + expect(getSessionState(SESSION_STORE_KEY).id).toBe(sessionId) } function expectSessionIdToBeDefined(sessionManager: SessionManager) { expect(sessionManager.findSession()!.id).toMatch(/^[a-f0-9-]+$/) expect(sessionManager.findSession()?.isExpired).toBeUndefined() - expect(getCookie(SESSION_STORE_KEY)).toMatch(/\bid=[a-f0-9-]+/) - expect(getCookie(SESSION_STORE_KEY)).not.toContain('isExpired=1') + expect(getSessionState(SESSION_STORE_KEY).id).toMatch(/^[a-f0-9-]+$/) + expect(getSessionState(SESSION_STORE_KEY).isExpired).toBeUndefined() } function expectSessionToBeExpired(sessionManager: SessionManager) { expect(sessionManager.findSession()).toBeUndefined() - expect(getCookie(SESSION_STORE_KEY)).toContain('isExpired=1') + expect(getSessionState(SESSION_STORE_KEY).isExpired).toBe('1') } function expectSessionIdToNotBeDefined(sessionManager: SessionManager) { expect(sessionManager.findSession()!.id).toBeUndefined() - expect(getCookie(SESSION_STORE_KEY)).not.toMatch(/\bid=/) + expect(getSessionState(SESSION_STORE_KEY).id).toBeUndefined() } function expectTrackingTypeToBe( @@ -81,12 +82,12 @@ describe('startSessionManager', () => { trackingType: FakeTrackingType ) { expect(sessionManager.findSession()!.trackingType).toEqual(trackingType) - expect(getCookie(SESSION_STORE_KEY)).toContain(`${productKey}=${trackingType}`) + expect(getSessionState(SESSION_STORE_KEY)[productKey]).toEqual(trackingType) } function expectTrackingTypeToNotBeDefined(sessionManager: SessionManager, productKey: string) { expect(sessionManager.findSession()?.trackingType).toBeUndefined() - expect(getCookie(SESSION_STORE_KEY)).not.toContain(`${productKey}=`) + expect(getSessionState(SESSION_STORE_KEY)[productKey]).toBeUndefined() } beforeEach(() => { @@ -269,14 +270,14 @@ describe('startSessionManager', () => { startSessionManagerWithDefaults({ productKey: SECOND_PRODUCT_KEY }) // cookie correctly set - expect(getCookie(SESSION_STORE_KEY)).toContain('first') - expect(getCookie(SESSION_STORE_KEY)).toContain('second') + expect(getSessionState(SESSION_STORE_KEY).first).toBeDefined() + expect(getSessionState(SESSION_STORE_KEY).second).toBeDefined() clock.tick(STORAGE_POLL_DELAY / 2) // scheduled expandOrRenewSession should not use cached value - expect(getCookie(SESSION_STORE_KEY)).toContain('first') - expect(getCookie(SESSION_STORE_KEY)).toContain('second') + expect(getSessionState(SESSION_STORE_KEY).first).toBeDefined() + expect(getSessionState(SESSION_STORE_KEY).second).toBeDefined() }) it('should have independent tracking types', () => { @@ -342,7 +343,7 @@ describe('startSessionManager', () => { sessionManager.expireObservable.subscribe(expireSessionSpy) expect(sessionManager.findSession()!.id).not.toBe('abcde') - expect(getCookie(SESSION_STORE_KEY)).toContain(`created=${Date.now()}`) + expect(getSessionState(SESSION_STORE_KEY).created).toEqual(Date.now().toString()) expect(expireSessionSpy).not.toHaveBeenCalled() // the session has not been active from the start }) @@ -352,7 +353,7 @@ describe('startSessionManager', () => { const sessionManager = startSessionManagerWithDefaults() expect(sessionManager.findSession()!.id).toBe('abcde') - expect(getCookie(SESSION_STORE_KEY)).not.toContain('created=') + expect(getSessionState(SESSION_STORE_KEY).created).toBeUndefined() }) }) @@ -598,7 +599,7 @@ describe('startSessionManager', () => { trackingConsentState.update(TrackingConsent.NOT_GRANTED) expectSessionToBeExpired(sessionManager) - expect(getCookie(SESSION_STORE_KEY)).toBe('isExpired=1') + expect(getSessionState(SESSION_STORE_KEY).isExpired).toBe('1') }) it('does not renew the session when tracking consent is withdrawn', () => { diff --git a/packages/core/src/domain/session/sessionStore.spec.ts b/packages/core/src/domain/session/sessionStore.spec.ts index 3a7d34f53f..7d8105177c 100644 --- a/packages/core/src/domain/session/sessionStore.spec.ts +++ b/packages/core/src/domain/session/sessionStore.spec.ts @@ -1,6 +1,6 @@ import type { Clock } from '../../../test' -import { expireCookie, mockClock } from '../../../test' -import { getCookie, setCookie } from '../../browser/cookie' +import { expireCookie, mockClock, getSessionState } from '../../../test' +import { setCookie } from '../../browser/cookie' import type { InitConfiguration, Configuration } from '../configuration' import { display } from '../../tools/display' import type { SessionStore } from './sessionStore' @@ -32,25 +32,25 @@ function setSessionInStore(trackingType: FakeTrackingType = FakeTrackingType.TRA } function expectTrackedSessionToBeInStore(id?: string) { - expect(getCookie(SESSION_STORE_KEY)).toMatch(new RegExp(`id=${id ? id : '[a-f0-9-]+'}`)) - expect(getCookie(SESSION_STORE_KEY)).not.toContain('isExpired=1') - expect(getCookie(SESSION_STORE_KEY)).toContain(`${PRODUCT_KEY}=${FakeTrackingType.TRACKED}`) + expect(getSessionState(SESSION_STORE_KEY).id).toEqual(id ? id : jasmine.any(String)) + expect(getSessionState(SESSION_STORE_KEY).isExpired).toBeUndefined() + expect(getSessionState(SESSION_STORE_KEY)[PRODUCT_KEY]).toEqual(FakeTrackingType.TRACKED) } function expectNotTrackedSessionToBeInStore() { - expect(getCookie(SESSION_STORE_KEY)).not.toMatch(/\bid=/) - expect(getCookie(SESSION_STORE_KEY)).not.toContain('isExpired=1') - expect(getCookie(SESSION_STORE_KEY)).toContain(`${PRODUCT_KEY}=${FakeTrackingType.NOT_TRACKED}`) + expect(getSessionState(SESSION_STORE_KEY).id).toBeUndefined() + expect(getSessionState(SESSION_STORE_KEY).isExpired).toBeUndefined() + expect(getSessionState(SESSION_STORE_KEY)[PRODUCT_KEY]).toEqual(FakeTrackingType.NOT_TRACKED) } function expectSessionToBeExpiredInStore() { - expect(getCookie(SESSION_STORE_KEY)).toContain('isExpired=1') - expect(getCookie(SESSION_STORE_KEY)).not.toMatch(/\bid=/) - expect(getCookie(SESSION_STORE_KEY)).not.toContain(`${PRODUCT_KEY}=`) + expect(getSessionState(SESSION_STORE_KEY).isExpired).toEqual(IS_EXPIRED) + expect(getSessionState(SESSION_STORE_KEY).id).toBeUndefined() + expect(getSessionState(SESSION_STORE_KEY)[PRODUCT_KEY]).toBeUndefined() } function getStoreExpiration() { - return /expire=(\d+)/.exec(getCookie(SESSION_STORE_KEY)!)?.[1] + return getSessionState(SESSION_STORE_KEY).expire } function resetSessionInStore() { diff --git a/packages/core/src/index.ts b/packages/core/src/index.ts index 4e5be52f8d..369924b8cc 100644 --- a/packages/core/src/index.ts +++ b/packages/core/src/index.ts @@ -133,6 +133,7 @@ export { export { CustomerDataType } from './domain/context/contextConstants' export { createValueHistory, ValueHistory, ValueHistoryEntry, CLEAR_OLD_VALUES_INTERVAL } from './tools/valueHistory' export { readBytesFromStream } from './tools/readBytesFromStream' +export type { SessionState } from './domain/session/sessionState' export { STORAGE_POLL_DELAY } from './domain/session/sessionStore' export { SESSION_STORE_KEY } from './domain/session/storeStrategies/sessionStoreStrategy' export { diff --git a/packages/core/test/cookie.ts b/packages/core/test/cookie.ts index a791cc3e24..062e622bae 100644 --- a/packages/core/test/cookie.ts +++ b/packages/core/test/cookie.ts @@ -1,7 +1,12 @@ -import { setCookie } from '../src/browser/cookie' +import { getCookie, setCookie } from '../src/browser/cookie' +import { toSessionState } from '../src/domain/session/sessionState' import { SESSION_STORE_KEY } from '../src/domain/session/storeStrategies/sessionStoreStrategy' import { ONE_MINUTE } from '../src/tools/utils/timeUtils' export function expireCookie() { setCookie(SESSION_STORE_KEY, 'isExpired=1', ONE_MINUTE) } + +export function getSessionState(sessionStoreKey: string) { + return toSessionState(getCookie(sessionStoreKey)) +} diff --git a/packages/logs/src/domain/logsSessionManager.spec.ts b/packages/logs/src/domain/logsSessionManager.spec.ts index 1739be3814..81be547335 100644 --- a/packages/logs/src/domain/logsSessionManager.spec.ts +++ b/packages/logs/src/domain/logsSessionManager.spec.ts @@ -2,7 +2,6 @@ import type { RelativeTime } from '@datadog/browser-core' import { STORAGE_POLL_DELAY, SESSION_STORE_KEY, - getCookie, setCookie, stopSessionManager, ONE_SECOND, @@ -13,7 +12,7 @@ import { SessionPersistence, } from '@datadog/browser-core' import type { Clock } from '@datadog/browser-core/test' -import { createNewEvent, expireCookie, mockClock } from '@datadog/browser-core/test' +import { createNewEvent, expireCookie, getSessionState, mockClock } from '@datadog/browser-core/test' import type { LogsConfiguration } from './configuration' import { @@ -42,15 +41,15 @@ describe('logs session manager', () => { it('when tracked should store tracking type and session id', () => { startLogsSessionManagerWithDefaults() - expect(getCookie(SESSION_STORE_KEY)).toContain(`${LOGS_SESSION_KEY}=${LoggerTrackingType.TRACKED}`) - expect(getCookie(SESSION_STORE_KEY)).toMatch(/\bid=[a-f0-9-]+/) + expect(getSessionState(SESSION_STORE_KEY).id).toMatch(/[a-f0-9-]+/) + expect(getSessionState(SESSION_STORE_KEY)[LOGS_SESSION_KEY]).toBe(LoggerTrackingType.TRACKED) }) it('when not tracked should store tracking type', () => { startLogsSessionManagerWithDefaults({ configuration: { sessionSampleRate: 0 } }) - expect(getCookie(SESSION_STORE_KEY)).toContain(`${LOGS_SESSION_KEY}=${LoggerTrackingType.NOT_TRACKED}`) - expect(getCookie(SESSION_STORE_KEY)).not.toContain('isExpired=1') + expect(getSessionState(SESSION_STORE_KEY)[LOGS_SESSION_KEY]).toBe(LoggerTrackingType.NOT_TRACKED) + expect(getSessionState(SESSION_STORE_KEY).isExpired).toBeUndefined() }) it('when tracked should keep existing tracking type and session id', () => { @@ -58,8 +57,8 @@ describe('logs session manager', () => { startLogsSessionManagerWithDefaults() - expect(getCookie(SESSION_STORE_KEY)).toContain(`${LOGS_SESSION_KEY}=${LoggerTrackingType.TRACKED}`) - expect(getCookie(SESSION_STORE_KEY)).toContain('id=abcdef') + expect(getSessionState(SESSION_STORE_KEY).id).toBe('abcdef') + expect(getSessionState(SESSION_STORE_KEY)[LOGS_SESSION_KEY]).toBe(LoggerTrackingType.TRACKED) }) it('when not tracked should keep existing tracking type', () => { @@ -67,20 +66,20 @@ describe('logs session manager', () => { startLogsSessionManagerWithDefaults() - expect(getCookie(SESSION_STORE_KEY)).toContain(`${LOGS_SESSION_KEY}=${LoggerTrackingType.NOT_TRACKED}`) + expect(getSessionState(SESSION_STORE_KEY)[LOGS_SESSION_KEY]).toBe(LoggerTrackingType.NOT_TRACKED) }) it('should renew on activity after expiration', () => { startLogsSessionManagerWithDefaults() expireCookie() - expect(getCookie(SESSION_STORE_KEY)).toBe('isExpired=1') + expect(getSessionState(SESSION_STORE_KEY).isExpired).toBe('1') clock.tick(STORAGE_POLL_DELAY) document.body.dispatchEvent(createNewEvent(DOM_EVENT.CLICK)) - expect(getCookie(SESSION_STORE_KEY)).toMatch(/\bid=[a-f0-9-]+/) - expect(getCookie(SESSION_STORE_KEY)).toContain(`${LOGS_SESSION_KEY}=${LoggerTrackingType.TRACKED}`) + expect(getSessionState(SESSION_STORE_KEY).id).toMatch(/[a-f0-9-]+/) + expect(getSessionState(SESSION_STORE_KEY)[LOGS_SESSION_KEY]).toBe(LoggerTrackingType.TRACKED) }) describe('findTrackedSession', () => { diff --git a/packages/rum-core/src/domain/rumSessionManager.spec.ts b/packages/rum-core/src/domain/rumSessionManager.spec.ts index 7153bb6c62..f95e853a74 100644 --- a/packages/rum-core/src/domain/rumSessionManager.spec.ts +++ b/packages/rum-core/src/domain/rumSessionManager.spec.ts @@ -2,7 +2,6 @@ import type { RelativeTime } from '@datadog/browser-core' import { STORAGE_POLL_DELAY, SESSION_STORE_KEY, - getCookie, setCookie, stopSessionManager, ONE_SECOND, @@ -15,6 +14,7 @@ import type { Clock } from '@datadog/browser-core/test' import { createNewEvent, expireCookie, + getSessionState, mockEventBridge, mockClock, registerCleanupTask, @@ -61,10 +61,9 @@ describe('rum session manager', () => { expect(expireSessionSpy).not.toHaveBeenCalled() expect(renewSessionSpy).not.toHaveBeenCalled() - expect(getCookie(SESSION_STORE_KEY)).toContain( - `${RUM_SESSION_KEY}=${RumTrackingType.TRACKED_WITH_SESSION_REPLAY}` - ) - expect(getCookie(SESSION_STORE_KEY)).toMatch(/\bid=[a-f0-9-]/) + + expect(getSessionState(SESSION_STORE_KEY).id).toMatch(/[a-f0-9-]/) + expect(getSessionState(SESSION_STORE_KEY)[RUM_SESSION_KEY]).toBe(RumTrackingType.TRACKED_WITH_SESSION_REPLAY) }) it('when tracked without session replay should store session type and id', () => { @@ -72,10 +71,8 @@ describe('rum session manager', () => { expect(expireSessionSpy).not.toHaveBeenCalled() expect(renewSessionSpy).not.toHaveBeenCalled() - expect(getCookie(SESSION_STORE_KEY)).toContain( - `${RUM_SESSION_KEY}=${RumTrackingType.TRACKED_WITHOUT_SESSION_REPLAY}` - ) - expect(getCookie(SESSION_STORE_KEY)).toMatch(/\bid=[a-f0-9-]/) + expect(getSessionState(SESSION_STORE_KEY).id).toMatch(/[a-f0-9-]/) + expect(getSessionState(SESSION_STORE_KEY)[RUM_SESSION_KEY]).toBe(RumTrackingType.TRACKED_WITHOUT_SESSION_REPLAY) }) it('when not tracked should store session type', () => { @@ -83,9 +80,9 @@ describe('rum session manager', () => { expect(expireSessionSpy).not.toHaveBeenCalled() expect(renewSessionSpy).not.toHaveBeenCalled() - expect(getCookie(SESSION_STORE_KEY)).toContain(`${RUM_SESSION_KEY}=${RumTrackingType.NOT_TRACKED}`) - expect(getCookie(SESSION_STORE_KEY)).not.toMatch(/\bid=/) - expect(getCookie(SESSION_STORE_KEY)).not.toContain('isExpired=1') + expect(getSessionState(SESSION_STORE_KEY)[RUM_SESSION_KEY]).toBe(RumTrackingType.NOT_TRACKED) + expect(getSessionState(SESSION_STORE_KEY).id).not.toBeDefined() + expect(getSessionState(SESSION_STORE_KEY).isExpired).not.toBeDefined() }) it('when tracked should keep existing session type and id', () => { @@ -95,10 +92,8 @@ describe('rum session manager', () => { expect(expireSessionSpy).not.toHaveBeenCalled() expect(renewSessionSpy).not.toHaveBeenCalled() - expect(getCookie(SESSION_STORE_KEY)).toContain( - `${RUM_SESSION_KEY}=${RumTrackingType.TRACKED_WITH_SESSION_REPLAY}` - ) - expect(getCookie(SESSION_STORE_KEY)).toContain('id=abcdef') + expect(getSessionState(SESSION_STORE_KEY).id).toBe('abcdef') + expect(getSessionState(SESSION_STORE_KEY)[RUM_SESSION_KEY]).toBe(RumTrackingType.TRACKED_WITH_SESSION_REPLAY) }) it('when not tracked should keep existing session type', () => { @@ -108,7 +103,7 @@ describe('rum session manager', () => { expect(expireSessionSpy).not.toHaveBeenCalled() expect(renewSessionSpy).not.toHaveBeenCalled() - expect(getCookie(SESSION_STORE_KEY)).toContain(`${RUM_SESSION_KEY}=${RumTrackingType.NOT_TRACKED}`) + expect(getSessionState(SESSION_STORE_KEY)[RUM_SESSION_KEY]).toBe(RumTrackingType.NOT_TRACKED) }) it('should renew on activity after expiration', () => { @@ -117,7 +112,7 @@ describe('rum session manager', () => { startRumSessionManagerWithDefaults({ configuration: { sessionSampleRate: 100, sessionReplaySampleRate: 100 } }) expireCookie() - expect(getCookie(SESSION_STORE_KEY)).toEqual('isExpired=1') + expect(getSessionState(SESSION_STORE_KEY).isExpired).toBe('1') expect(expireSessionSpy).not.toHaveBeenCalled() expect(renewSessionSpy).not.toHaveBeenCalled() clock.tick(STORAGE_POLL_DELAY) @@ -126,10 +121,8 @@ describe('rum session manager', () => { expect(expireSessionSpy).toHaveBeenCalled() expect(renewSessionSpy).toHaveBeenCalled() - expect(getCookie(SESSION_STORE_KEY)).toContain( - `${RUM_SESSION_KEY}=${RumTrackingType.TRACKED_WITH_SESSION_REPLAY}` - ) - expect(getCookie(SESSION_STORE_KEY)).toMatch(/\bid=[a-f0-9-]/) + expect(getSessionState(SESSION_STORE_KEY)[RUM_SESSION_KEY]).toBe(RumTrackingType.TRACKED_WITH_SESSION_REPLAY) + expect(getSessionState(SESSION_STORE_KEY).id).toMatch(/[a-f0-9-]/) }) }) diff --git a/test/e2e/lib/helpers/session.ts b/test/e2e/lib/helpers/session.ts index 2ac612248e..ada8a60a0a 100644 --- a/test/e2e/lib/helpers/session.ts +++ b/test/e2e/lib/helpers/session.ts @@ -1,4 +1,5 @@ import { SESSION_STORE_KEY, SESSION_TIME_OUT_DELAY } from '@datadog/browser-core' +import type { SessionState } from '@datadog/browser-core' import { setCookie } from './browser' export async function renewSession() { @@ -6,7 +7,7 @@ export async function renewSession() { const documentElement = await $('html') await documentElement.click() - expect(await findSessionCookie()).not.toContain('isExpired=1') + expect((await findSessionCookie())?.isExpired).not.toEqual('1') } export async function expireSession() { @@ -17,7 +18,7 @@ export async function expireSession() { await setCookie(SESSION_STORE_KEY, expireCookie, SESSION_TIME_OUT_DELAY) - expect(await findSessionCookie()).toContain('isExpired=1') + expect((await findSessionCookie())?.isExpired).toEqual('1') // Cookies are cached for 1s, wait until the cache expires await browser.pause(1100) @@ -27,5 +28,9 @@ export async function findSessionCookie() { const cookies = await browser.getCookies(SESSION_STORE_KEY) // In some case, the session cookie is returned but with an empty value. Let's consider it expired // in this case. - return cookies[0]?.value || undefined + const rawValue = cookies[0]?.value + if (!rawValue) { + return + } + return Object.fromEntries(rawValue.split('&').map((part: string) => part.split('='))) as SessionState } diff --git a/test/e2e/scenario/rum/sessions.scenario.ts b/test/e2e/scenario/rum/sessions.scenario.ts index 9878b01861..8efe06d563 100644 --- a/test/e2e/scenario/rum/sessions.scenario.ts +++ b/test/e2e/scenario/rum/sessions.scenario.ts @@ -54,23 +54,18 @@ describe('rum sessions', () => { createTest('persists when session is expired') .withRum() .run(async () => { - const prevSessionCookie = await findSessionCookie() - const anonymousId = prevSessionCookie?.match(/aid=[a-z0-9-]+/) - expect(anonymousId).not.toBeNull() + const anonymousId = (await findSessionCookie())?.aid await expireSession() await flushEvents() - if (anonymousId) { - expect(await findSessionCookie()).toContain(anonymousId[0]) - } + expect((await findSessionCookie())?.aid).toEqual(anonymousId) }) createTest('persists when session renewed') .withRum() .run(async () => { - const prevSessionCookie = await findSessionCookie() - const anonymousId = prevSessionCookie?.match(/aid=[a-z0-9-]+/) + const anonymousId = (await findSessionCookie())?.aid expect(anonymousId).not.toBeNull() await browser.execute(() => { @@ -81,15 +76,7 @@ describe('rum sessions', () => { // The session is not created right away, let's wait until we see a cookie await browser.waitUntil(async () => Boolean(await findSessionCookie())) - await browser.execute(() => { - window.DD_RUM!.addAction('foo') - }) - - await flushEvents() - - if (anonymousId) { - expect(await findSessionCookie()).toContain(anonymousId[0]) - } + expect((await findSessionCookie())?.aid).toEqual(anonymousId) }) createTest('generated when cookie is cleared') @@ -99,7 +86,7 @@ describe('rum sessions', () => { await renewSession() await flushEvents() - expect(await findSessionCookie()).toMatch(/aid=[a-z0-9-]+/) + expect((await findSessionCookie())?.aid).toBeDefined() }) }) @@ -121,7 +108,7 @@ describe('rum sessions', () => { }) await flushEvents() - expect(await findSessionCookie()).toContain('isExpired=1') + expect((await findSessionCookie())?.isExpired).toEqual('1') expect(intakeRegistry.rumActionEvents.length).toBe(0) }) @@ -142,8 +129,8 @@ describe('rum sessions', () => { await flushEvents() - expect(await findSessionCookie()).not.toContain('isExpired=1') - expect(await findSessionCookie()).toMatch(/\bid=[a-f0-9-]+/) + expect((await findSessionCookie())?.isExpired).not.toEqual('1') + expect((await findSessionCookie())?.id).toBeDefined() expect(intakeRegistry.rumActionEvents.length).toBe(1) }) diff --git a/test/e2e/scenario/trackingConsent.scenario.ts b/test/e2e/scenario/trackingConsent.scenario.ts index ff35e4de9c..deadf16d6f 100644 --- a/test/e2e/scenario/trackingConsent.scenario.ts +++ b/test/e2e/scenario/trackingConsent.scenario.ts @@ -38,7 +38,7 @@ describe('tracking consent', () => { await flushEvents() expect(intakeRegistry.rumActionEvents).toEqual([]) - expect(await findSessionCookie()).toContain('isExpired=1') + expect((await findSessionCookie())?.isExpired).toEqual('1') }) createTest('starts a new session when tracking consent is granted again')