diff --git a/scripts/build/build-test-apps.ts b/scripts/build/build-test-apps.ts index 863be3af4f..86026291d1 100644 --- a/scripts/build/build-test-apps.ts +++ b/scripts/build/build-test-apps.ts @@ -29,6 +29,7 @@ const APPS: AppConfig[] = [ { name: 'angular-app' }, { name: 'vue-router-app' }, { name: 'nuxt-app' }, + { name: 'instrumentation-overhead' }, // React Router apps { name: 'react-router-v6-app' }, diff --git a/scripts/dev-server/lib/server.ts b/scripts/dev-server/lib/server.ts index 2ba9fca460..980792d902 100644 --- a/scripts/dev-server/lib/server.ts +++ b/scripts/dev-server/lib/server.ts @@ -15,7 +15,7 @@ const sandboxPath = './sandbox' const START_PORT = 8080 const MAX_PORT = 8180 -const PACKAGES_WITH_BUNDLE = ['rum', 'rum-slim', 'logs', 'worker'] +const PACKAGES_WITH_BUNDLE = ['rum', 'rum-slim', 'logs', 'worker', 'debugger'] export function runServer({ writeIntakeFile = true }: { writeIntakeFile?: boolean } = {}): void { if (writeIntakeFile) { diff --git a/test/apps/.gitignore b/test/apps/.gitignore index 3c6fddc975..b779dd1e8c 100644 --- a/test/apps/.gitignore +++ b/test/apps/.gitignore @@ -3,3 +3,5 @@ invalid-tracking-origin/ react-router-v7-app/ cdn-extension/ appendChild-extension/ +*/dist/ +*/.yarn/ diff --git a/test/e2e/lib/framework/createTest.ts b/test/e2e/lib/framework/createTest.ts index a1d5042867..4fc8f2fd32 100644 --- a/test/e2e/lib/framework/createTest.ts +++ b/test/e2e/lib/framework/createTest.ts @@ -1,4 +1,5 @@ import type { LogsInitConfiguration } from '@datadog/browser-logs' +import type { DebuggerInitConfiguration } from '@datadog/browser-debugger' import type { RumInitConfiguration, RemoteConfiguration } from '@datadog/browser-rum-core' import type { BrowserContext, Page } from '@playwright/test' import { test, expect } from '@playwright/test' @@ -6,7 +7,11 @@ import { addTag, addTestOptimizationTags } from '../helpers/tags' import { getRunId } from '../../../envUtils' import type { BrowserLog } from '../helpers/browser' import { BrowserLogsManager, deleteAllCookies, getBrowserName, sendXhr } from '../helpers/browser' -import { DEFAULT_LOGS_CONFIGURATION, DEFAULT_RUM_CONFIGURATION } from '../helpers/configuration' +import { + DEFAULT_DEBUGGER_CONFIGURATION, + DEFAULT_LOGS_CONFIGURATION, + DEFAULT_RUM_CONFIGURATION, +} from '../helpers/configuration' import { validateRumFormat } from '../helpers/validation' import type { BrowserConfiguration } from '../../../browsers.conf' import { NEXTJS_APP_ROUTER_PORT, NUXT_APP_PORT, VUE_ROUTER_APP_PORT } from '../helpers/playwright' @@ -48,6 +53,7 @@ class TestBuilder { private rumConfiguration: RumInitConfiguration | undefined = undefined private alsoRunWithRumSlim = false private logsConfiguration: LogsInitConfiguration | undefined = undefined + private debuggerConfiguration: DebuggerInitConfiguration | undefined = undefined private remoteConfiguration?: RemoteConfiguration = undefined private head = '' private body = '' @@ -91,6 +97,11 @@ class TestBuilder { return this } + withDebugger(debuggerInitConfiguration?: Partial) { + this.debuggerConfiguration = { ...DEFAULT_DEBUGGER_CONFIGURATION, ...debuggerInitConfiguration } + return this + } + withHead(head: string) { this.head = head return this @@ -218,6 +229,7 @@ class TestBuilder { head: this.head, logs: this.logsConfiguration, rum: this.rumConfiguration, + debugger: this.debuggerConfiguration, remoteConfiguration: this.remoteConfiguration, rumInit: this.rumInit, logsInit: this.logsInit, diff --git a/test/e2e/lib/framework/httpServers.ts b/test/e2e/lib/framework/httpServers.ts index 2fb3b84fe0..be3d293456 100644 --- a/test/e2e/lib/framework/httpServers.ts +++ b/test/e2e/lib/framework/httpServers.ts @@ -14,6 +14,7 @@ export type ServerApp = (req: http.IncomingMessage, res: http.ServerResponse) => export type MockServerApp = ServerApp & { getLargeResponseWroteSize(): number + setDebuggerProbes(probes: object[]): void } export interface Server { diff --git a/test/e2e/lib/framework/index.ts b/test/e2e/lib/framework/index.ts index 5e275e1610..e8aec78f37 100644 --- a/test/e2e/lib/framework/index.ts +++ b/test/e2e/lib/framework/index.ts @@ -1,5 +1,9 @@ export { createTest } from './createTest' -export { DEFAULT_RUM_CONFIGURATION, DEFAULT_LOGS_CONFIGURATION } from '../helpers/configuration' +export { + DEFAULT_RUM_CONFIGURATION, + DEFAULT_LOGS_CONFIGURATION, + DEFAULT_DEBUGGER_CONFIGURATION, +} from '../helpers/configuration' export { createExtension } from './createExtension' export { createWorker } from './createWorker' export { @@ -13,6 +17,7 @@ export { } from './pageSetups' export { IntakeRegistry } from './intakeRegistry' export { getTestServers, waitForServersIdle } from './httpServers' +export type { Servers } from './httpServers' export { flushEvents } from './flushEvents' export { waitForRequests } from './waitForRequests' export { LARGE_RESPONSE_MIN_BYTE_SIZE } from './serverApps/mock' diff --git a/test/e2e/lib/framework/intakeProxyMiddleware.ts b/test/e2e/lib/framework/intakeProxyMiddleware.ts index 4cfc0dbf24..7dbd68bc2c 100644 --- a/test/e2e/lib/framework/intakeProxyMiddleware.ts +++ b/test/e2e/lib/framework/intakeProxyMiddleware.ts @@ -51,7 +51,17 @@ export type ProfileIntakeRequest = { } } & BaseIntakeRequest -export type IntakeRequest = LogsIntakeRequest | RumIntakeRequest | ReplayIntakeRequest | ProfileIntakeRequest +export type DebuggerIntakeRequest = { + intakeType: 'debugger' + events: Array> +} & BaseIntakeRequest + +export type IntakeRequest = + | LogsIntakeRequest + | RumIntakeRequest + | ReplayIntakeRequest + | ProfileIntakeRequest + | DebuggerIntakeRequest interface IntakeRequestInfos { isBridge: boolean @@ -104,7 +114,13 @@ function computeIntakeRequestInfos(req: express.Request): IntakeRequestInfos { let intakeType: IntakeRequest['intakeType'] // pathname = /api/v2/rum const endpoint = pathname.split(/[/?]/)[3] - if (endpoint === 'logs' || endpoint === 'rum' || endpoint === 'replay' || endpoint === 'profile') { + if ( + endpoint === 'logs' || + endpoint === 'rum' || + endpoint === 'replay' || + endpoint === 'profile' || + endpoint === 'debugger' + ) { intakeType = endpoint } else { throw new Error("Can't find intake type") @@ -124,6 +140,9 @@ function readIntakeRequest(req: express.Request, infos: IntakeRequestInfos): Pro if (infos.intakeType === 'profile') { return readProfileIntakeRequest(req, infos as IntakeRequestInfos & { intakeType: 'profile' }) } + if (infos.intakeType === 'debugger') { + return readDebuggerIntakeRequest(req, infos as IntakeRequestInfos & { intakeType: 'debugger' }) + } return readRumOrLogsIntakeRequest(req, infos as IntakeRequestInfos & { intakeType: 'rum' | 'logs' }) } @@ -143,6 +162,23 @@ async function readRumOrLogsIntakeRequest( } } +async function readDebuggerIntakeRequest( + req: express.Request, + infos: IntakeRequestInfos & { intakeType: 'debugger' } +): Promise { + const rawBody = await readStream(req) + const encodedBody = infos.encoding === 'deflate' ? inflateSync(rawBody) : rawBody + + return { + ...infos, + events: encodedBody + .toString('utf-8') + .split('\n') + // eslint-disable-next-line @typescript-eslint/no-unsafe-return + .map((line): Record => JSON.parse(line)), + } +} + function readReplayIntakeRequest( req: express.Request, infos: IntakeRequestInfos & { intakeType: 'replay' } diff --git a/test/e2e/lib/framework/intakeRegistry.ts b/test/e2e/lib/framework/intakeRegistry.ts index dcff6b46a6..dc703f844f 100644 --- a/test/e2e/lib/framework/intakeRegistry.ts +++ b/test/e2e/lib/framework/intakeRegistry.ts @@ -14,6 +14,7 @@ import type { TelemetryUsageEvent, } from '@datadog/browser-core' import type { + DebuggerIntakeRequest, IntakeRequest, LogsIntakeRequest, ProfileIntakeRequest, @@ -138,6 +139,18 @@ export class IntakeRegistry { get profileEvents() { return this.profileRequests.map((request) => request.event) } + + // + // Debugger + // + + get debuggerRequests() { + return this.requests.filter(isDebuggerIntakeRequest) + } + + get debuggerEvents() { + return this.debuggerRequests.flatMap((request) => request.events) + } } function isLogsIntakeRequest(request: IntakeRequest): request is LogsIntakeRequest { @@ -156,6 +169,10 @@ function isProfileIntakeRequest(request: IntakeRequest): request is ProfileIntak return request.intakeType === 'profile' } +function isDebuggerIntakeRequest(request: IntakeRequest): request is DebuggerIntakeRequest { + return request.intakeType === 'debugger' +} + function isRumEvent(event: RumEvent | TelemetryEvent): event is RumEvent { return !isTelemetryEvent(event) } diff --git a/test/e2e/lib/framework/pageSetups.ts b/test/e2e/lib/framework/pageSetups.ts index dac6bec2a2..33954fe4e5 100644 --- a/test/e2e/lib/framework/pageSetups.ts +++ b/test/e2e/lib/framework/pageSetups.ts @@ -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 { DebuggerInitConfiguration } from '@datadog/browser-debugger' import type test from '@playwright/test' import { isBrowserStack, isContinuousIntegration } from './environment' import type { Servers } from './httpServers' @@ -11,6 +12,7 @@ export interface SetupOptions { logs?: LogsInitConfiguration logsInit: (initConfiguration: LogsInitConfiguration) => void rumInit: (initConfiguration: RumInitConfiguration) => void + debugger?: DebuggerInitConfiguration remoteConfiguration?: RemoteConfiguration eventBridge: boolean head?: string @@ -75,7 +77,7 @@ n=o.getElementsByTagName(u)[0];n.parentNode.insertBefore(d,n) })(window,document,'script','${url}','${globalName}')` } - const { logsScriptUrl, rumScriptUrl } = createCrossOriginScriptUrls(servers, options) + const { logsScriptUrl, rumScriptUrl, debuggerScriptUrl } = createCrossOriginScriptUrls(servers, options) if (options.logs) { footer += html`` } + if (options.debugger) { + footer += html`` + } + return basePage({ header, body: options.body, @@ -115,7 +126,7 @@ export function bundleSetup(options: SetupOptions, servers: Servers) { header += setupExtension(options, servers) } - const { logsScriptUrl, rumScriptUrl } = createCrossOriginScriptUrls(servers, options) + const { logsScriptUrl, rumScriptUrl, debuggerScriptUrl } = createCrossOriginScriptUrls(servers, options) if (options.logs) { header += html`` @@ -133,6 +144,15 @@ export function bundleSetup(options: SetupOptions, servers: Servers) { ` } + if (options.debugger) { + header += html` + + + ` + } + return basePage({ header, body: options.body, @@ -168,6 +188,14 @@ export function npmSetup(options: SetupOptions, servers: Servers) { ` } + if (options.debugger) { + header += html`` + } + header += html`` return basePage({ @@ -340,7 +368,10 @@ function isJsonIncompatibleValue(value: unknown): value is JsonIncompatibleValue return typeof value === 'function' || value instanceof RegExp } -export function formatConfiguration(initConfiguration: LogsInitConfiguration | RumInitConfiguration, servers: Servers) { +export function formatConfiguration( + initConfiguration: LogsInitConfiguration | RumInitConfiguration | DebuggerInitConfiguration, + servers: Servers +) { const jsonIncompatibles = new Map() let result = JSON.stringify( @@ -375,5 +406,6 @@ export function createCrossOriginScriptUrls(servers: Servers, options: SetupOpti return { logsScriptUrl: `${servers.crossOrigin.origin}/datadog-logs.js`, rumScriptUrl: `${servers.crossOrigin.origin}/${options.useRumSlim ? 'datadog-rum-slim.js' : 'datadog-rum.js'}`, + debuggerScriptUrl: `${servers.crossOrigin.origin}/datadog-debugger.js`, } } diff --git a/test/e2e/lib/framework/serverApps/mock.ts b/test/e2e/lib/framework/serverApps/mock.ts index 876a9d0bd9..ee6c71b686 100644 --- a/test/e2e/lib/framework/serverApps/mock.ts +++ b/test/e2e/lib/framework/serverApps/mock.ts @@ -15,6 +15,7 @@ export function createMockServerApp(servers: Servers, setup: string, setupOption const { remoteConfiguration, worker } = setupOptions ?? {} const app = express() let largeResponseBytesWritten = 0 + let debuggerProbes: object[] = [] app.use(cors()) app.disable('etag') // disable automatic resource caching @@ -227,10 +228,17 @@ export function createMockServerApp(servers: Servers, setup: string, setupOption res.send(JSON.stringify(remoteConfiguration)) }) + app.post('/api/ui/debugger/probe-delivery', (_req, res) => { + res.json({ nextCursor: '', updates: debuggerProbes, deletions: [] }) + }) + return Object.assign(app, { getLargeResponseWroteSize() { return largeResponseBytesWritten }, + setDebuggerProbes(probes: object[]) { + debuggerProbes = probes + }, }) } diff --git a/test/e2e/lib/helpers/configuration.ts b/test/e2e/lib/helpers/configuration.ts index cfe9437d0e..33e36560f7 100644 --- a/test/e2e/lib/helpers/configuration.ts +++ b/test/e2e/lib/helpers/configuration.ts @@ -25,3 +25,8 @@ export const DEFAULT_LOGS_CONFIGURATION = { telemetryUsageSampleRate: 100, telemetryConfigurationSampleRate: 100, } + +export const DEFAULT_DEBUGGER_CONFIGURATION = { + clientToken: CLIENT_TOKEN, + service: 'browser-sdk-e2e-test', +} diff --git a/test/e2e/lib/types/global.ts b/test/e2e/lib/types/global.ts index 1837e63ecc..e1a1c8c16b 100644 --- a/test/e2e/lib/types/global.ts +++ b/test/e2e/lib/types/global.ts @@ -1,10 +1,12 @@ import type { LogsGlobal } from '@datadog/browser-logs' +import type { DebuggerPublicApi } from '@datadog/browser-debugger' import type { RumGlobal } from '@datadog/browser-rum' declare global { interface Window { DD_LOGS?: LogsGlobal DD_RUM?: RumGlobal + DD_DEBUGGER?: DebuggerPublicApi DD_SOURCE_CODE_CONTEXT?: { [stack: string]: { service: string; version?: string } } } }