11import type { Page } from '@playwright/test'
2+ import type { RemoteConfiguration } from '@datadog/browser-rum-core'
23import { test , expect } from '@playwright/test'
34import { createTest , html , waitForServersIdle } from '../../lib/framework'
45
56const RC_APP_ID = 'e2e'
67const CACHE_KEY = `dd_rc_${ RC_APP_ID } `
78
89test . describe ( 'remote configuration' , ( ) => {
9- createTest ( 'should be fetched and applied' )
10+ createTest ( 'should be fetched on first load, cached, and applied after reload ' )
1011 . withRum ( {
1112 sessionSampleRate : 100 ,
1213 remoteConfigurationId : 'e2e' ,
@@ -17,107 +18,139 @@ test.describe('remote configuration', () => {
1718 . run ( async ( { page } ) => {
1819 await waitForRemoteConfigurationToBeApplied ( page )
1920 const initConfiguration = await page . evaluate ( ( ) => window . DD_RUM ! . getInitConfiguration ( ) ! )
21+ expect ( initConfiguration . applicationId ) . toBe ( RC_APP_ID )
2022 expect ( initConfiguration . sessionSampleRate ) . toBe ( 1 )
2123 } )
2224
23- createTest ( 'should resolve an option value from a cookie ' )
25+ createTest ( 'should preserve init configuration on first load and populate cache from background fetch ' )
2426 . withRum ( {
2527 remoteConfigurationId : 'e2e' ,
2628 } )
29+ . withRemoteConfiguration ( {
30+ rum : { applicationId : RC_APP_ID , sessionSampleRate : 1 } ,
31+ } )
32+ . run ( async ( { page } ) => {
33+ const initConfiguration = await page . evaluate ( ( ) => window . DD_RUM ! . getInitConfiguration ( ) ! )
34+ expect ( initConfiguration . applicationId ) . not . toBe ( RC_APP_ID )
35+
36+ await page . waitForFunction ( ( key ) => localStorage . getItem ( key ) !== null , CACHE_KEY )
37+ const stored = await page . evaluate (
38+ ( key ) => JSON . parse ( localStorage . getItem ( key ) ! ) as { version : number ; config : object } ,
39+ CACHE_KEY
40+ )
41+ expect ( stored . version ) . toBe ( 1 )
42+ expect ( stored . config ) . toEqual ( { applicationId : RC_APP_ID , sessionSampleRate : 1 } )
43+ } )
44+
45+ createTest ( 'should resolve an option value from a cookie' )
46+ . withRum ( { remoteConfigurationId : 'e2e' } )
2747 . withRemoteConfiguration ( {
2848 rum : { applicationId : RC_APP_ID , version : { rcSerializedType : 'dynamic' , strategy : 'cookie' , name : 'e2e_rc' } } ,
2949 } )
30- . withBody ( html `
50+ . withHead ( html `
51+ ${ seedCache ( {
52+ rum : { applicationId : RC_APP_ID , version : { rcSerializedType : 'dynamic' , strategy : 'cookie' , name : 'e2e_rc' } } ,
53+ } ) }
3154 < script >
3255 document . cookie = 'e2e_rc=my-version;'
3356 </ script >
3457 ` )
3558 . run ( async ( { page } ) => {
36- await waitForRemoteConfigurationToBeApplied ( page )
3759 const initConfiguration = await page . evaluate ( ( ) => window . DD_RUM ! . getInitConfiguration ( ) ! )
3860 expect ( initConfiguration . version ) . toBe ( 'my-version' )
3961 } )
4062
4163 createTest ( 'should resolve an option value from an element content' )
42- . withRum ( {
43- remoteConfigurationId : 'e2e' ,
44- } )
64+ . withRum ( { remoteConfigurationId : 'e2e' } )
4565 . withRemoteConfiguration ( {
4666 rum : {
4767 applicationId : RC_APP_ID ,
4868 version : { rcSerializedType : 'dynamic' , strategy : 'dom' , selector : '#version' } ,
4969 } ,
5070 } )
51- . withHead ( html `< span id ="version "> 123</ span > ` )
71+ . withHead ( html `
72+ ${ seedCache ( {
73+ rum : { applicationId : RC_APP_ID , version : { rcSerializedType : 'dynamic' , strategy : 'dom' , selector : '#version' } } ,
74+ } ) }
75+ < span id ="version "> 123</ span >
76+ ` )
5277 . run ( async ( { page } ) => {
53- await waitForRemoteConfigurationToBeApplied ( page )
5478 const initConfiguration = await page . evaluate ( ( ) => window . DD_RUM ! . getInitConfiguration ( ) ! )
5579 expect ( initConfiguration . version ) . toBe ( '123' )
5680 } )
5781
5882 createTest ( 'should resolve an option value from an element attribute' )
59- . withRum ( {
60- remoteConfigurationId : 'e2e' ,
61- } )
83+ . withRum ( { remoteConfigurationId : 'e2e' } )
6284 . withRemoteConfiguration ( {
6385 rum : {
6486 applicationId : RC_APP_ID ,
6587 version : { rcSerializedType : 'dynamic' , strategy : 'dom' , selector : '#version' , attribute : 'data-version' } ,
6688 } ,
6789 } )
68- . withHead ( html `< span id ="version " data-version ="123 "> </ span > ` )
90+ . withHead ( html `
91+ ${ seedCache ( {
92+ rum : {
93+ applicationId : RC_APP_ID ,
94+ version : { rcSerializedType : 'dynamic' , strategy : 'dom' , selector : '#version' , attribute : 'data-version' } ,
95+ } ,
96+ } ) }
97+ < span id ="version " data-version ="123 "> </ span >
98+ ` )
6999 . run ( async ( { page } ) => {
70- await waitForRemoteConfigurationToBeApplied ( page )
71100 const initConfiguration = await page . evaluate ( ( ) => window . DD_RUM ! . getInitConfiguration ( ) ! )
72101 expect ( initConfiguration . version ) . toBe ( '123' )
73102 } )
74103
75104 createTest ( 'should resolve an option value from js variable' )
76- . withRum ( {
77- remoteConfigurationId : 'e2e' ,
78- } )
105+ . withRum ( { remoteConfigurationId : 'e2e' } )
79106 . withRemoteConfiguration ( {
80107 rum : {
81108 applicationId : 'e2e' ,
82109 version : { rcSerializedType : 'dynamic' , strategy : 'js' , path : 'dataLayer.version' } ,
83110 } ,
84111 } )
85112 . withHead ( html `
113+ ${ seedCache ( {
114+ rum : {
115+ applicationId : 'e2e' ,
116+ version : { rcSerializedType : 'dynamic' , strategy : 'js' , path : 'dataLayer.version' } ,
117+ } ,
118+ } ) }
86119 < script >
87120 window . dataLayer = { version : 'js-version' }
88121 </ script >
89122 ` )
90123 . run ( async ( { page } ) => {
91- await waitForRemoteConfigurationToBeApplied ( page )
92124 const initConfiguration = await page . evaluate ( ( ) => window . DD_RUM ! . getInitConfiguration ( ) ! )
93125 expect ( initConfiguration . version ) . toBe ( 'js-version' )
94126 } )
95127
96128 createTest ( 'should resolve an option value from localStorage' )
97- . withRum ( {
98- remoteConfigurationId : 'e2e' ,
99- } )
129+ . withRum ( { remoteConfigurationId : 'e2e' } )
100130 . withRemoteConfiguration ( {
101131 rum : {
102132 applicationId : 'e2e' ,
103133 version : { rcSerializedType : 'dynamic' , strategy : 'localStorage' , key : 'dd_app_version' } ,
104134 } ,
105135 } )
106- . withBody ( html `
136+ . withHead ( html `
137+ ${ seedCache ( {
138+ rum : {
139+ applicationId : 'e2e' ,
140+ version : { rcSerializedType : 'dynamic' , strategy : 'localStorage' , key : 'dd_app_version' } ,
141+ } ,
142+ } ) }
107143 < script >
108144 localStorage . setItem ( 'dd_app_version' , 'localStorage-version' )
109145 </ script >
110146 ` )
111147 . run ( async ( { page } ) => {
112- await waitForRemoteConfigurationToBeApplied ( page )
113148 const initConfiguration = await page . evaluate ( ( ) => window . DD_RUM ! . getInitConfiguration ( ) ! )
114149 expect ( initConfiguration . version ) . toBe ( 'localStorage-version' )
115150 } )
116151
117152 createTest ( 'should resolve an option value from localStorage with an extractor' )
118- . withRum ( {
119- remoteConfigurationId : 'e2e' ,
120- } )
153+ . withRum ( { remoteConfigurationId : 'e2e' } )
121154 . withRemoteConfiguration ( {
122155 rum : {
123156 applicationId : 'e2e' ,
@@ -129,86 +162,114 @@ test.describe('remote configuration', () => {
129162 } ,
130163 } ,
131164 } )
132- . withBody ( html `
165+ . withHead ( html `
166+ ${ seedCache ( {
167+ rum : {
168+ applicationId : 'e2e' ,
169+ version : {
170+ rcSerializedType : 'dynamic' ,
171+ strategy : 'localStorage' ,
172+ key : 'dd_app_version' ,
173+ extractor : { rcSerializedType : 'regex' , value : '\\d+\\.\\d+\\.\\d+' } ,
174+ } ,
175+ } ,
176+ } ) }
133177 < script >
134178 localStorage . setItem ( 'dd_app_version' , 'version-1.2.3-beta' )
135179 </ script >
136180 ` )
137181 . run ( async ( { page } ) => {
138- await waitForRemoteConfigurationToBeApplied ( page )
139182 const initConfiguration = await page . evaluate ( ( ) => window . DD_RUM ! . getInitConfiguration ( ) ! )
140183 expect ( initConfiguration . version ) . toBe ( '1.2.3' )
141184 } )
142185
143186 createTest ( 'should resolve to undefined when localStorage key is missing' )
144- . withRum ( {
145- remoteConfigurationId : 'e2e' ,
146- } )
187+ . withRum ( { remoteConfigurationId : 'e2e' } )
147188 . withRemoteConfiguration ( {
148189 rum : {
149190 applicationId : 'e2e' ,
150191 version : { rcSerializedType : 'dynamic' , strategy : 'localStorage' , key : 'non_existent_key' } ,
151192 } ,
152193 } )
194+ . withHead (
195+ seedCache ( {
196+ rum : {
197+ applicationId : 'e2e' ,
198+ version : { rcSerializedType : 'dynamic' , strategy : 'localStorage' , key : 'non_existent_key' } ,
199+ } ,
200+ } )
201+ )
153202 . run ( async ( { page } ) => {
154- await waitForRemoteConfigurationToBeApplied ( page )
155203 const initConfiguration = await page . evaluate ( ( ) => window . DD_RUM ! . getInitConfiguration ( ) ! )
156204 expect ( initConfiguration . version ) . toBeUndefined ( )
157205 } )
158206
159207 createTest ( 'should resolve user context' )
160- . withRum ( {
161- remoteConfigurationId : 'e2e' ,
162- } )
208+ . withRum ( { remoteConfigurationId : 'e2e' } )
163209 . withRemoteConfiguration ( {
164210 rum : {
165211 applicationId : RC_APP_ID ,
166212 user : [ { key : 'id' , value : { rcSerializedType : 'dynamic' , strategy : 'cookie' , name : 'e2e_rc' } } ] ,
167213 } ,
168214 } )
169- . withBody ( html `
215+ . withHead ( html `
216+ ${ seedCache ( {
217+ rum : {
218+ applicationId : RC_APP_ID ,
219+ user : [ { key : 'id' , value : { rcSerializedType : 'dynamic' , strategy : 'cookie' , name : 'e2e_rc' } } ] ,
220+ } ,
221+ } ) }
170222 < script >
171223 document . cookie = 'e2e_rc=my-user-id;'
172224 </ script >
173225 ` )
174226 . run ( async ( { page } ) => {
175- await waitForRemoteConfigurationToBeApplied ( page )
176227 const user = await page . evaluate ( ( ) => window . DD_RUM ! . getUser ( ) )
177228 expect ( user . id ) . toBe ( 'my-user-id' )
178229 } )
179230
180231 createTest ( 'should resolve global context' )
181- . withRum ( {
182- remoteConfigurationId : 'e2e' ,
183- } )
232+ . withRum ( { remoteConfigurationId : 'e2e' } )
184233 . withRemoteConfiguration ( {
185234 rum : {
186235 applicationId : RC_APP_ID ,
187- context : [
188- {
189- key : 'foo' ,
190- value : { rcSerializedType : 'dynamic' , strategy : 'cookie' , name : 'e2e_rc' } ,
191- } ,
192- ] ,
236+ context : [ { key : 'foo' , value : { rcSerializedType : 'dynamic' , strategy : 'cookie' , name : 'e2e_rc' } } ] ,
193237 } ,
194238 } )
195- . withBody ( html `
239+ . withHead ( html `
240+ ${ seedCache ( {
241+ rum : {
242+ applicationId : RC_APP_ID ,
243+ context : [ { key : 'foo' , value : { rcSerializedType : 'dynamic' , strategy : 'cookie' , name : 'e2e_rc' } } ] ,
244+ } ,
245+ } ) }
196246 < script >
197247 document . cookie = 'e2e_rc=bar;'
198248 </ script >
199249 ` )
200250 . run ( async ( { page } ) => {
201- await waitForRemoteConfigurationToBeApplied ( page )
202251 const globalContext = await page . evaluate ( ( ) => window . DD_RUM ! . getGlobalContext ( ) )
203252 expect ( globalContext . foo ) . toEqual ( 'bar' )
204253 } )
205254} )
206255
256+ /* Embeds a synchronous <script> that pre-populates the remote-configuration cache before the SDK
257+ * loads. It must run before the SDK script in <head>, so pass the returned HTML through .withHead().
258+ * JSON.stringify() is applied twice on purpose: the inner call serializes the cache entry, the
259+ * outer one wraps it as a valid JS string literal with proper escaping.
260+ */
261+ function seedCache ( remoteConfig : RemoteConfiguration ) {
262+ const entry = JSON . stringify ( { version : 1 , config : remoteConfig . rum , fetchedAt : 1000 } )
263+ return html `< script >
264+ localStorage . setItem ( '${ CACHE_KEY } ' , ${ JSON . stringify ( entry ) } )
265+ </ script > `
266+ }
267+
207268/* The background fetch on the initial page load writes the remote configuration into localStorage;
208269 * reloading lets the SDK pick it up synchronously on the next init().
209270 */
210271async function waitForRemoteConfigurationToBeApplied ( page : Page ) {
211272 await page . waitForFunction ( ( key ) => localStorage . getItem ( key ) !== null , CACHE_KEY )
212273 await page . reload ( )
213274 await waitForServersIdle ( )
214- }
275+ }
0 commit comments