|
1 | | -import { generateCombinedBundle } from '@datadog/browser-sdk-endpoint' |
| 1 | +import { generateCombinedBundle, CONTEXT_RESOLUTION_HELPERS } from '@datadog/browser-sdk-endpoint' |
2 | 2 | import { test, expect } from '@playwright/test' |
3 | 3 | import { createTest, DEFAULT_RUM_CONFIGURATION, html, basePage, createCrossOriginScriptUrls } from '../../lib/framework' |
4 | 4 | import type { SetupOptions, Servers } from '../../lib/framework' |
5 | 5 |
|
| 6 | +export interface ContextItem { |
| 7 | + key: string |
| 8 | + value: { rcSerializedType: string; value?: string; strategy?: string; name?: string; path?: string; selector?: string; attribute?: string; extractor?: unknown } |
| 9 | +} |
| 10 | + |
| 11 | +/** |
| 12 | + * Creates a SetupFactory that generates an HTML page mimicking the embedded config bundle |
| 13 | + * produced by generateCombinedBundle(), with context resolution for user and globalContext. |
| 14 | + * Uses CONTEXT_RESOLUTION_HELPERS verbatim so tests exercise the same code as production. |
| 15 | + * |
| 16 | + * @param extraConfig - optional user[] / context[] arrays and optional pre-SDK script |
| 17 | + * @param extraConfig.user - optional user context items |
| 18 | + * @param extraConfig.context - optional global context items |
| 19 | + * @param extraConfig.preSDKScript - extra inline JS injected before the SDK script tag (e.g. to pre-set a cookie) |
| 20 | + */ |
| 21 | +export function createEmbeddedConfigSetup(extraConfig: { |
| 22 | + user?: ContextItem[] |
| 23 | + context?: ContextItem[] |
| 24 | + /** Extra inline JS injected before the SDK script tag (e.g. to pre-set a cookie). */ |
| 25 | + preSDKScript?: string |
| 26 | +}) { |
| 27 | + return (options: SetupOptions, servers: Servers): string => { |
| 28 | + const { rumScriptUrl } = createCrossOriginScriptUrls(servers, options) |
| 29 | + |
| 30 | + const embeddedConfig = { |
| 31 | + ...DEFAULT_RUM_CONFIGURATION, |
| 32 | + sessionSampleRate: 100, |
| 33 | + proxy: servers.intake.origin, |
| 34 | + ...extraConfig, |
| 35 | + } |
| 36 | + const configJson = JSON.stringify(embeddedConfig) |
| 37 | + const testContextJson = JSON.stringify(options.context) |
| 38 | + |
| 39 | + const header = html` |
| 40 | + ${extraConfig.preSDKScript ? `<script type="text/javascript">${extraConfig.preSDKScript}</script>` : ''} |
| 41 | + <script type="text/javascript" src="${rumScriptUrl}"></script> |
| 42 | + <script type="text/javascript"> |
| 43 | + (function () { |
| 44 | + 'use strict'; |
| 45 | + var __DATADOG_REMOTE_CONFIG__ = ${configJson}; |
| 46 | + var __dd_user = {}; |
| 47 | + (__DATADOG_REMOTE_CONFIG__.user || []).forEach(function (item) { |
| 48 | + __dd_user[item.key] = __dd_resolveContextValue(item.value); |
| 49 | + }); |
| 50 | + var __dd_globalContext = {}; |
| 51 | + (__DATADOG_REMOTE_CONFIG__.context || []).forEach(function (item) { |
| 52 | + __dd_globalContext[item.key] = __dd_resolveContextValue(item.value); |
| 53 | + }); |
| 54 | + var hasUser = Object.keys(__dd_user).length > 0; |
| 55 | + var hasGlobalContext = Object.keys(__dd_globalContext).length > 0; |
| 56 | + window.DD_RUM.init(Object.assign({}, __DATADOG_REMOTE_CONFIG__, { |
| 57 | + user: hasUser ? __dd_user : undefined, |
| 58 | + context: undefined, |
| 59 | + globalContext: hasGlobalContext ? __dd_globalContext : undefined |
| 60 | + })); |
| 61 | + // Re-apply test isolation context after init so it is not overwritten by the |
| 62 | + // globalContext init option (setContext replaces; we add test properties back). |
| 63 | + var __dd_testContext = ${testContextJson}; |
| 64 | + Object.keys(__dd_testContext).forEach(function(key) { |
| 65 | + window.DD_RUM.setGlobalContextProperty(key, __dd_testContext[key]); |
| 66 | + }); |
| 67 | +
|
| 68 | + ${CONTEXT_RESOLUTION_HELPERS.trim()} |
| 69 | + })(); |
| 70 | + </script> |
| 71 | + ` |
| 72 | + return basePage({ header }) |
| 73 | + } |
| 74 | +} |
| 75 | + |
6 | 76 | test.describe('embedded configuration', () => { |
7 | 77 | createTest('should load SDK with embedded config and expose getInitConfiguration') |
8 | 78 | .withRum({ |
@@ -131,111 +201,18 @@ test.describe('embedded configuration', () => { |
131 | 201 | }) |
132 | 202 |
|
133 | 203 | createTest('should apply static user context from embedded config to view events') |
134 | | - .withSetup(embeddedConfigSetupFactory({ user: [{ key: 'id', value: { rcSerializedType: 'string', value: 'test-user-42' } }] })) |
| 204 | + .withSetup(createEmbeddedConfigSetup({ user: [{ key: 'id', value: { rcSerializedType: 'string', value: 'test-user-42' } }] })) |
135 | 205 | .run(async ({ intakeRegistry, flushEvents }) => { |
136 | 206 | await flushEvents() |
137 | 207 | expect(intakeRegistry.rumViewEvents.length).toBeGreaterThanOrEqual(1) |
138 | 208 | expect(intakeRegistry.rumViewEvents[0].usr?.id).toBe('test-user-42') |
139 | 209 | }) |
140 | 210 |
|
141 | 211 | createTest('should apply static globalContext from embedded config to view events') |
142 | | - .withSetup(embeddedConfigSetupFactory({ context: [{ key: 'plan', value: { rcSerializedType: 'string', value: 'enterprise' } }] })) |
| 212 | + .withSetup(createEmbeddedConfigSetup({ context: [{ key: 'plan', value: { rcSerializedType: 'string', value: 'enterprise' } }] })) |
143 | 213 | .run(async ({ intakeRegistry, flushEvents }) => { |
144 | 214 | await flushEvents() |
145 | 215 | expect(intakeRegistry.rumViewEvents.length).toBeGreaterThanOrEqual(1) |
146 | 216 | expect(intakeRegistry.rumViewEvents[0].context?.plan).toBe('enterprise') |
147 | 217 | }) |
148 | 218 | }) |
149 | | - |
150 | | -/** |
151 | | - * Creates a SetupFactory that generates an HTML page mimicking the embedded config bundle |
152 | | - * produced by generateCombinedBundle(), with context resolution for user and globalContext. |
153 | | - */ |
154 | | -function embeddedConfigSetupFactory(extraConfig: { |
155 | | - user?: Array<{ key: string; value: { rcSerializedType: string; value?: string; strategy?: string; name?: string } }> |
156 | | - context?: Array<{ key: string; value: { rcSerializedType: string; value?: string; strategy?: string; name?: string } }> |
157 | | -}) { |
158 | | - return (options: SetupOptions, servers: Servers): string => { |
159 | | - const { rumScriptUrl } = createCrossOriginScriptUrls(servers, options) |
160 | | - |
161 | | - const embeddedConfig = { |
162 | | - ...DEFAULT_RUM_CONFIGURATION, |
163 | | - sessionSampleRate: 100, |
164 | | - proxy: servers.intake.origin, |
165 | | - ...extraConfig, |
166 | | - } |
167 | | - const configJson = JSON.stringify(embeddedConfig) |
168 | | - const testContextJson = JSON.stringify(options.context) |
169 | | - |
170 | | - const header = html` |
171 | | - <script type="text/javascript" src="${rumScriptUrl}"></script> |
172 | | - <script type="text/javascript"> |
173 | | - (function () { |
174 | | - 'use strict'; |
175 | | - var __DATADOG_REMOTE_CONFIG__ = ${configJson}; |
176 | | - var __dd_user = {}; |
177 | | - (__DATADOG_REMOTE_CONFIG__.user || []).forEach(function (item) { |
178 | | - __dd_user[item.key] = __dd_resolveContextValue(item.value); |
179 | | - }); |
180 | | - var __dd_globalContext = {}; |
181 | | - (__DATADOG_REMOTE_CONFIG__.context || []).forEach(function (item) { |
182 | | - __dd_globalContext[item.key] = __dd_resolveContextValue(item.value); |
183 | | - }); |
184 | | - var hasUser = Object.keys(__dd_user).length > 0; |
185 | | - var hasGlobalContext = Object.keys(__dd_globalContext).length > 0; |
186 | | - window.DD_RUM.setGlobalContext(${testContextJson}); |
187 | | - window.DD_RUM.init(Object.assign({}, __DATADOG_REMOTE_CONFIG__, { |
188 | | - user: hasUser ? __dd_user : undefined, |
189 | | - context: undefined, |
190 | | - globalContext: hasGlobalContext ? __dd_globalContext : undefined |
191 | | - })); |
192 | | -
|
193 | | - function __dd_resolveContextValue(value) { |
194 | | - if (!value || typeof value !== 'object') { return value; } |
195 | | - var serializedType = value.rcSerializedType; |
196 | | - if (serializedType === 'string') { return value.value; } |
197 | | - if (serializedType !== 'dynamic') { return undefined; } |
198 | | - var strategy = value.strategy; |
199 | | - var resolved; |
200 | | - if (strategy === 'cookie') { |
201 | | - resolved = __dd_getCookie(value.name); |
202 | | - } else if (strategy === 'js') { |
203 | | - resolved = __dd_resolveJsPath(value.path); |
204 | | - } else if (strategy === 'dom') { |
205 | | - resolved = __dd_resolveDom(value.selector, value.attribute); |
206 | | - } else if (strategy === 'localStorage') { |
207 | | - try { resolved = localStorage.getItem(value.key); } catch(e) { resolved = undefined; } |
208 | | - } |
209 | | - return resolved; |
210 | | - } |
211 | | -
|
212 | | - function __dd_getCookie(name) { |
213 | | - if (typeof name !== 'string') { return undefined; } |
214 | | - var match = document.cookie.match(new RegExp('(?:^|;\\\\s*)' + name + '=([^;]*)')); |
215 | | - return match ? decodeURIComponent(match[1]) : undefined; |
216 | | - } |
217 | | -
|
218 | | - function __dd_resolveJsPath(path) { |
219 | | - if (typeof path !== 'string' || path === '') { return undefined; } |
220 | | - var parts = path.split('.'); |
221 | | - var obj = window; |
222 | | - for (var i = 0; i < parts.length; i++) { |
223 | | - if (obj == null || !(parts[i] in Object(obj))) { return undefined; } |
224 | | - obj = obj[parts[i]]; |
225 | | - } |
226 | | - return obj; |
227 | | - } |
228 | | -
|
229 | | - function __dd_resolveDom(selector, attribute) { |
230 | | - var el; |
231 | | - try { el = document.querySelector(selector); } catch(e) { return undefined; } |
232 | | - if (!el) { return undefined; } |
233 | | - if (attribute !== undefined) { return el.getAttribute(attribute); } |
234 | | - return el.textContent; |
235 | | - } |
236 | | - })(); |
237 | | - </script> |
238 | | - ` |
239 | | - return basePage({ header }) |
240 | | - } |
241 | | -} |
0 commit comments