Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion test/e2e/lib/framework/createTest.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { LogsInitConfiguration } from '@datadog/browser-logs'
import type { RumInitConfiguration, RemoteConfiguration } from '@datadog/browser-rum-core'
import type { RemoteConfiguration } from '@datadog/browser-remote-config'
import type { RumInitConfiguration } from '@datadog/browser-rum-core'
import type { BrowserContext, Page } from '@playwright/test'
import { test, expect } from '@playwright/test'
import { addTag, addTestOptimizationTags } from '../helpers/tags'
Expand Down
3 changes: 2 additions & 1 deletion test/e2e/lib/framework/pageSetups.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { generateUUID, INTAKE_URL_PARAMETERS } from '@datadog/browser-core'
import type { LogsInitConfiguration } from '@datadog/browser-logs'
import type { RumInitConfiguration, RemoteConfiguration } from '@datadog/browser-rum-core'
import type { RemoteConfiguration } from '@datadog/browser-remote-config'
import type { RumInitConfiguration } from '@datadog/browser-rum-core'
import type test from '@playwright/test'
import { isBrowserStack, isContinuousIntegration } from './environment'
import type { Servers } from './httpServers'
Expand Down
3 changes: 1 addition & 2 deletions test/e2e/lib/framework/serverApps/mock.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ import type { ServerResponse } from 'http'
import * as url from 'url'
import cors from 'cors'
import express from 'express'
import type { RemoteConfiguration } from '@datadog/browser-rum-core'
import { getSdkBundlePath, getTestAppBundlePath } from '../sdkBuilds'
import type { MockServerApp, Servers } from '../httpServers'
import { DEV_SERVER_BASE_URL } from '../../helpers/playwright'
Expand All @@ -14,7 +13,7 @@ export const LARGE_RESPONSE_MIN_BYTE_SIZE = 100_000
export function createMockServerApp(
servers: Servers,
setup: string,
remoteConfiguration?: RemoteConfiguration,
remoteConfiguration?: unknown,
worker?: WorkerOptions
): MockServerApp {
const app = express()
Expand Down
131 changes: 131 additions & 0 deletions test/e2e/scenario/rum/embeddedConfig.scenario.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
import { generateCombinedBundle } from '@datadog/browser-sdk-endpoint'
import { test, expect } from '@playwright/test'
import { createTest } from '../../lib/framework'

test.describe('embedded configuration', () => {
createTest('should load SDK with embedded config and expose getInitConfiguration')
.withRum({
sessionSampleRate: 75,
service: 'embedded-config-test',
env: 'staging',
})
.run(async ({ page }) => {
const initConfig = await page.evaluate(() => window.DD_RUM!.getInitConfiguration()!)
expect(initConfig).toBeDefined()
expect(initConfig.service).toBe('embedded-config-test')
expect(initConfig.env).toBe('staging')
})

createTest('should send RUM view events with embedded config')
.withRum({
sessionSampleRate: 100,
})
.run(async ({ intakeRegistry, flushEvents }) => {
await flushEvents()
expect(intakeRegistry.rumViewEvents.length).toBeGreaterThanOrEqual(1)
})

createTest('should work with rum-slim variant')
.withRum({
service: 'slim-embedded-test',
})
.withRumSlim()
.run(async ({ page }) => {
const initConfig = await page.evaluate(() => window.DD_RUM!.getInitConfiguration()!)
expect(initConfig).toBeDefined()
expect(initConfig.service).toBe('slim-embedded-test')
})

test('generateCombinedBundle produces valid JavaScript bundle', () => {
const sdkCode = 'window.__TEST_SDK_LOADED__ = true;'
const config = {
applicationId: 'test-app-id',
clientToken: 'pub-test-token',
sessionSampleRate: 100,
}

const bundle = generateCombinedBundle({
sdkCode,
config,
variant: 'rum',
})

// Bundle is valid JavaScript
// eslint-disable-next-line no-new-func, @typescript-eslint/no-implied-eval
expect(() => new Function(bundle)).not.toThrow()

// Bundle contains the embedded config
expect(bundle).toContain('"applicationId": "test-app-id"')
expect(bundle).toContain('"sessionSampleRate": 100')

// Bundle contains SDK code
expect(bundle).toContain('__TEST_SDK_LOADED__')

// Bundle has correct metadata
expect(bundle).toContain('SDK Variant: rum')
expect(bundle).toContain('Embedded Remote Configuration')
expect(bundle).toContain('No additional network requests needed')
})

test('generateCombinedBundle wraps content in IIFE with auto-init', () => {
const sdkCode = '// SDK placeholder'
const config = {
applicationId: 'iife-test-app',
clientToken: 'pub-test-token',
}

const bundle = generateCombinedBundle({
sdkCode,
config,
variant: 'rum',
})

expect(bundle).toContain('(function() {')
expect(bundle).toContain("'use strict';")
expect(bundle).toContain('__DATADOG_REMOTE_CONFIG__')
expect(bundle).toContain('DD_RUM.init(__DATADOG_REMOTE_CONFIG__)')
expect(bundle).toContain('})();')
})

test('generateCombinedBundle preserves config with dynamic value markers', () => {
const sdkCode = '// SDK placeholder'
const config = {
applicationId: 'dynamic-test-app',
clientToken: 'pub-test-token',
version: { rcSerializedType: 'dynamic', strategy: 'cookie', name: 'app_version' },
}

const bundle = generateCombinedBundle({
sdkCode,
config,
variant: 'rum',
})

// Dynamic markers should be preserved as-is in the bundle
expect(bundle).toContain('"rcSerializedType": "dynamic"')
expect(bundle).toContain('"strategy": "cookie"')
expect(bundle).toContain('"name": "app_version"')
})

createTest('should not make requests to remote config endpoint when config is embedded')
.withRum({
sessionSampleRate: 100,
})
.run(async ({ page, servers: _servers }) => {
// The SDK with embedded config (via .withRum()) should not fetch remote config
// since no remoteConfigurationId is provided
const configRequests: string[] = []

page.on('request', (request) => {
if (request.url().includes('/config') || request.url().includes('sdk-configuration')) {
configRequests.push(request.url())
}
})

// Wait for any potential config fetches
await page.waitForTimeout(2000)

// No remote config requests should have been made
expect(configRequests).toHaveLength(0)
})
})
158 changes: 158 additions & 0 deletions test/e2e/scenario/rum/embeddedConfigDynamic.scenario.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
import { test, expect } from '@playwright/test'
import { generateCombinedBundle } from '@datadog/browser-sdk-endpoint'

test.describe('embedded configuration with dynamic values', () => {
test('preserves cookie dynamic value markers in generated bundle', () => {
const config = {
applicationId: 'dynamic-cookie-app',
clientToken: 'pub-test-token',
version: { rcSerializedType: 'dynamic', strategy: 'cookie', name: 'app_version' },
}

const bundle = generateCombinedBundle({
sdkCode: '// SDK',
config,
variant: 'rum',
})

expect(bundle).toContain('"rcSerializedType": "dynamic"')
expect(bundle).toContain('"strategy": "cookie"')
expect(bundle).toContain('"name": "app_version"')
})

test('preserves DOM selector dynamic value markers in generated bundle', () => {
const config = {
applicationId: 'dynamic-dom-app',
clientToken: 'pub-test-token',
version: { rcSerializedType: 'dynamic', strategy: 'dom', selector: '#tracking-id' },
}

const bundle = generateCombinedBundle({
sdkCode: '// SDK',
config,
variant: 'rum',
})

expect(bundle).toContain('"strategy": "dom"')
expect(bundle).toContain('"selector": "#tracking-id"')
})

test('preserves DOM attribute dynamic value markers in generated bundle', () => {
const config = {
applicationId: 'dynamic-dom-attr-app',
clientToken: 'pub-test-token',
version: {
rcSerializedType: 'dynamic',
strategy: 'dom',
selector: '#app-meta',
attribute: 'data-version',
},
}

const bundle = generateCombinedBundle({
sdkCode: '// SDK',
config,
variant: 'rum',
})

expect(bundle).toContain('"strategy": "dom"')
expect(bundle).toContain('"selector": "#app-meta"')
expect(bundle).toContain('"attribute": "data-version"')
})

test('preserves JS path dynamic value markers in generated bundle', () => {
const config = {
applicationId: 'dynamic-js-app',
clientToken: 'pub-test-token',
version: { rcSerializedType: 'dynamic', strategy: 'js', path: 'appState.tracking.version' },
}

const bundle = generateCombinedBundle({
sdkCode: '// SDK',
config,
variant: 'rum',
})

expect(bundle).toContain('"strategy": "js"')
expect(bundle).toContain('"path": "appState.tracking.version"')
})

test('mixed static and dynamic values coexist in generated bundle', () => {
const config = {
applicationId: 'mixed-config-app',
clientToken: 'pub-test-token',
sessionSampleRate: 80,
service: 'static-service',
version: { rcSerializedType: 'dynamic', strategy: 'cookie', name: 'app_version' },
env: { rcSerializedType: 'dynamic', strategy: 'js', path: 'deployment.env' },
}

const bundle = generateCombinedBundle({
sdkCode: '// SDK',
config,
variant: 'rum',
})

// Static values embedded
expect(bundle).toContain('"applicationId": "mixed-config-app"')
expect(bundle).toContain('"sessionSampleRate": 80')
expect(bundle).toContain('"service": "static-service"')

// Dynamic markers preserved
expect(bundle).toContain('"strategy": "cookie"')
expect(bundle).toContain('"strategy": "js"')
expect(bundle).toContain('"name": "app_version"')
expect(bundle).toContain('"path": "deployment.env"')
})

test('nested dynamic values preserve structure in generated bundle', () => {
const config = {
applicationId: 'nested-dynamic-app',
clientToken: 'pub-test-token',
allowedTracingUrls: [
{
match: { rcSerializedType: 'string' as const, value: 'https://api.example.com' },
propagatorTypes: ['tracecontext'],
},
],
user: [
{ key: 'id', value: { rcSerializedType: 'dynamic', strategy: 'cookie', name: 'user_id' } },
{ key: 'email', value: { rcSerializedType: 'dynamic', strategy: 'js', path: 'userData.email' } },
],
}

const bundle = generateCombinedBundle({
sdkCode: '// SDK',
config,
variant: 'rum',
})

expect(bundle).toContain('"rcSerializedType": "string"')
expect(bundle).toContain('"value": "https://api.example.com"')
expect(bundle).toContain('"propagatorTypes"')
expect(bundle).toContain('"key": "id"')
expect(bundle).toContain('"key": "email"')
expect(bundle).toContain('"name": "user_id"')
expect(bundle).toContain('"path": "userData.email"')
})

test('generated bundle with dynamic values is valid JavaScript', () => {
const config = {
applicationId: 'valid-js-app',
clientToken: 'pub-test-token',
version: { rcSerializedType: 'dynamic', strategy: 'cookie', name: 'missing_cookie' },
env: { rcSerializedType: 'dynamic', strategy: 'dom', selector: '#nonexistent' },
service: { rcSerializedType: 'dynamic', strategy: 'js', path: 'window.nonexistent.path' },
}

const bundle = generateCombinedBundle({
sdkCode: 'window.__TEST_LOADED__ = true;',
config,
variant: 'rum',
})

// Bundle should be valid JavaScript even with complex dynamic values
// eslint-disable-next-line no-new-func, @typescript-eslint/no-implied-eval
expect(() => new Function(bundle)).not.toThrow()
})
})