11import type { RumEvent , RumEventDomainContext , RumInitConfiguration } from '@datadog/browser-rum-core'
22import type { LogsEvent , LogsInitConfiguration , LogsEventDomainContext } from '@datadog/browser-logs'
3+ import type { Page } from '@playwright/test'
34import { test , expect } from '@playwright/test'
4- import { createTest } from '../lib/framework'
5+ import { ExperimentalFeature } from '@datadog/browser-core'
6+ import { createTest , html } from '../lib/framework'
57
68const HANDLING_STACK_REGEX = / ^ H a n d l i n g S t a c k : .* \n \s + a t t e s t H a n d l i n g S t a c k @ /
79
810const RUM_CONFIG : Partial < RumInitConfiguration > = {
911 service : 'main-service' ,
1012 version : '1.0.0' ,
13+ enableExperimentalFeatures : [ ExperimentalFeature . SOURCE_CODE_CONTEXT ] ,
1114 beforeSend : ( event : RumEvent , domainContext : RumEventDomainContext ) => {
1215 if ( 'handlingStack' in domainContext ) {
1316 event . context ! . handlingStack = domainContext . handlingStack
@@ -28,6 +31,31 @@ const LOGS_CONFIG: Partial<LogsInitConfiguration> = {
2831 } ,
2932}
3033
34+ // because the evaluation the script in a different context than the page, resulting in unexpected stack traces
35+ function createBody ( eventGenerator : string ) {
36+ return html `
37+ < button > click me</ button >
38+ < script >
39+ const button = document . querySelector ( 'button' )
40+ button . addEventListener ( 'click' , function handler ( ) {
41+ ${ eventGenerator }
42+ } )
43+ </ script >
44+ `
45+ }
46+
47+ function setSourceCodeContext ( page : Page , baseUrl : string ) {
48+ return page . evaluate ( ( baseUrl ) => {
49+ window . DD_SOURCE_CODE_CONTEXT = {
50+ [ `Error: Test error
51+ at testFunction (${ baseUrl } :41:27)` ] : {
52+ service : 'mf-service' ,
53+ version : '0.1.0' ,
54+ } ,
55+ }
56+ } , baseUrl )
57+ }
58+
3159test . describe ( 'microfrontend' , ( ) => {
3260 createTest ( 'expose handling stack for fetch requests' )
3361 . withRum ( RUM_CONFIG )
@@ -244,4 +272,108 @@ test.describe('microfrontend', () => {
244272 expect ( viewEvent . service ) . toBe ( 'mf-service' )
245273 expect ( viewEvent . version ) . toBe ( '0.1.0' )
246274 } )
275+
276+ test . describe ( 'source code context' , ( ) => {
277+ createTest ( 'errors from DD_RUM.addError should have service and version from source code context' )
278+ . withRum ( RUM_CONFIG )
279+ . withBody ( createBody ( 'window.DD_RUM.addError(new Error("foo"))' ) )
280+ . run ( async ( { intakeRegistry, flushEvents, page, baseUrl } ) => {
281+ await setSourceCodeContext ( page , baseUrl )
282+ await page . locator ( 'button' ) . click ( )
283+ await flushEvents ( )
284+
285+ const errorEvent = intakeRegistry . rumErrorEvents [ 0 ]
286+ expect ( errorEvent ) . toMatchObject ( { service : 'mf-service' , version : '0.1.0' } )
287+ } )
288+
289+ createTest ( 'errors from console.error should have service and version from source code context' )
290+ . withRum ( RUM_CONFIG )
291+ . withBody ( createBody ( 'console.error("foo")' ) )
292+ . run ( async ( { intakeRegistry, flushEvents, page, baseUrl, withBrowserLogs } ) => {
293+ await setSourceCodeContext ( page , baseUrl )
294+ await page . locator ( 'button' ) . click ( )
295+ await flushEvents ( )
296+
297+ const errorEvent = intakeRegistry . rumErrorEvents [ 0 ]
298+ expect ( errorEvent ) . toMatchObject ( { service : 'mf-service' , version : '0.1.0' } )
299+
300+ withBrowserLogs ( ( browserLogs ) => {
301+ expect ( browserLogs ) . toHaveLength ( 1 )
302+ } )
303+ } )
304+
305+ createTest ( 'runtime errors should have service and version from source code context' )
306+ . withRum ( RUM_CONFIG )
307+ . withBody ( createBody ( 'throw new Error("oh snap")' ) )
308+ . run ( async ( { intakeRegistry, flushEvents, page, baseUrl, withBrowserLogs } ) => {
309+ await setSourceCodeContext ( page , baseUrl )
310+ await page . locator ( 'button' ) . click ( )
311+ await flushEvents ( )
312+
313+ const errorEvent = intakeRegistry . rumErrorEvents [ 0 ]
314+ expect ( errorEvent ) . toMatchObject ( { service : 'mf-service' , version : '0.1.0' } )
315+
316+ withBrowserLogs ( ( browserLogs ) => {
317+ expect ( browserLogs ) . toHaveLength ( 1 )
318+ } )
319+ } )
320+
321+ createTest ( 'fetch requests should have service and version from source code context' )
322+ . withRum ( RUM_CONFIG )
323+ . withBody ( createBody ( 'fetch("/ok").then(() => {}, () => {})' ) )
324+ . run ( async ( { intakeRegistry, flushEvents, page, baseUrl } ) => {
325+ await setSourceCodeContext ( page , baseUrl )
326+ await page . locator ( 'button' ) . click ( )
327+ await flushEvents ( )
328+
329+ const resourceEvent = intakeRegistry . rumResourceEvents . find ( ( event ) => event . resource . type === 'fetch' ) !
330+ expect ( resourceEvent ) . toMatchObject ( { service : 'mf-service' , version : '0.1.0' } )
331+ } )
332+
333+ createTest ( 'xhr requests should have service and version from source code context' )
334+ . withRum ( RUM_CONFIG )
335+ . withBody ( createBody ( "const xhr = new XMLHttpRequest(); xhr.open('GET', '/ok'); xhr.send();" ) )
336+ . run ( async ( { intakeRegistry, flushEvents, page, baseUrl } ) => {
337+ await setSourceCodeContext ( page , baseUrl )
338+ await page . locator ( 'button' ) . click ( )
339+ await flushEvents ( )
340+
341+ const resourceEvent = intakeRegistry . rumResourceEvents . find ( ( event ) => event . resource . type === 'xhr' ) !
342+ expect ( resourceEvent ) . toMatchObject ( { service : 'mf-service' , version : '0.1.0' } )
343+ } )
344+
345+ createTest ( 'custom actions should have service and version from source code context' )
346+ . withRum ( RUM_CONFIG )
347+ . withBody ( createBody ( "window.DD_RUM.addAction('foo')" ) )
348+ . run ( async ( { intakeRegistry, flushEvents, page, baseUrl } ) => {
349+ await setSourceCodeContext ( page , baseUrl )
350+ await page . locator ( 'button' ) . click ( )
351+ await flushEvents ( )
352+
353+ const actionEvent = intakeRegistry . rumActionEvents [ 0 ]
354+ expect ( actionEvent ) . toMatchObject ( { service : 'mf-service' , version : '0.1.0' } )
355+ } )
356+
357+ createTest ( 'LOAf should have service and version from source code context' )
358+ . withRum ( RUM_CONFIG )
359+ . withBody (
360+ createBody ( `
361+ const end = performance.now() + 55
362+ while (performance.now() < end) {} // block the handler for ~55ms to trigger a long task
363+ ` )
364+ )
365+ . run ( async ( { intakeRegistry, flushEvents, page, baseUrl, browserName } ) => {
366+ test . skip ( browserName !== 'chromium' , 'Non-Chromium browsers do not support long tasks' )
367+
368+ await setSourceCodeContext ( page , baseUrl )
369+ await page . locator ( 'button' ) . click ( )
370+ await flushEvents ( )
371+
372+ const longTaskEvent = intakeRegistry . rumLongTaskEvents . find ( ( event ) =>
373+ event . long_task . scripts ?. [ 0 ] ?. invoker ?. includes ( 'BUTTON.onclick' )
374+ )
375+
376+ expect ( longTaskEvent ) . toMatchObject ( { service : 'mf-service' , version : '0.1.0' } )
377+ } )
378+ } )
247379} )
0 commit comments