diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index eb6b8f28be..da83e2008b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ variables: CURRENT_STAGING: staging-20 APP: 'browser-sdk' - CURRENT_CI_IMAGE: 107 + CURRENT_CI_IMAGE: 108 BUILD_STABLE_REGISTRY: 'registry.ddbuild.io' CI_IMAGE: '$BUILD_STABLE_REGISTRY/ci/$APP:$CURRENT_CI_IMAGE' GIT_REPOSITORY: 'git@github.com:DataDog/browser-sdk.git' diff --git a/package.json b/package.json index 3ac93e383e..a2772c9f71 100644 --- a/package.json +++ b/package.json @@ -45,7 +45,7 @@ "devDependencies": { "@eslint/js": "9.39.4", "@jsdevtools/coverage-istanbul-loader": "3.0.5", - "@playwright/test": "1.58.2", + "@playwright/test": "1.59.1", "@swc/core": "1.15.33", "@types/busboy": "1.5.4", "@types/chrome": "0.1.40", diff --git a/packages/rum-core/src/domain/resource/resourceCollection.ts b/packages/rum-core/src/domain/resource/resourceCollection.ts index 994aa24f70..2f8d905734 100644 --- a/packages/rum-core/src/domain/resource/resourceCollection.ts +++ b/packages/rum-core/src/domain/resource/resourceCollection.ts @@ -62,12 +62,19 @@ export function startResourceCollection(lifeCycle: LifeCycle, configuration: Rum // (notably on Firefox), so the matching REQUEST_COMPLETED isn't in the registry yet. // Defer the lookup to give the request time to land before we look it up. // + // Publish synchronously after the delay rather than via the task queue: the queue's + // requestIdleCallback can fire after the click action's PAGE_ACTIVITY_END_DELAY window + // (notably on newer Chromium scheduling), leaving the request uncounted by its parent + // action. + // // Note: we could clear the timeout on stop(), but this requires a bit of bookkeeping that // is not necessary right now. We could reevaluate in the future. - setTimeout( - () => handleResource(() => assembleResource(entry, requestRegistry.getMatchingRequest(entry), configuration)), - REQUEST_MATCHING_DELAY - ) + setTimeout(() => { + const rawEvent = assembleResource(entry, requestRegistry.getMatchingRequest(entry), configuration) + if (rawEvent) { + lifeCycle.notify(LifeCycleEventType.RAW_RUM_EVENT_COLLECTED, rawEvent) + } + }, REQUEST_MATCHING_DELAY) } else { handleResource(() => assembleResource(entry, undefined, configuration)) } diff --git a/test/e2e/scripts/pinnedProxy.ts b/test/e2e/scripts/pinnedProxy.ts index 8d4ecdf164..35ffcec13d 100644 --- a/test/e2e/scripts/pinnedProxy.ts +++ b/test/e2e/scripts/pinnedProxy.ts @@ -1,15 +1,15 @@ -// WebSocket translation proxy that lets a recent @playwright/test client (1.58) drive an +// WebSocket translation proxy that lets a recent @playwright/test client (1.59) drive an // older `playwright run-server` (1.40). Two layers of translation: // // 1) HTTP upgrade — the 1.40 server's User-Agent version check rejects mismatched clients // with HTTP 428. We rewrite the upgrade request's User-Agent so the check passes. // -// 2) JSON-RPC — once connected, the 1.58 client validates server messages strictly and -// sends commands using the 1.58 schema. We patch __create__ initializers (server→client) +// 2) JSON-RPC — once connected, the recent client validates server messages strictly and +// sends commands using the recent schema. We patch __create__ initializers (server→client) // and command parameters (client→server) where the schemas diverge between versions. // // Patches were derived from a diff of packages/protocol/src/protocol.yml between v1.40.1 -// and v1.58.2 — only the divergences exercised by this repo's e2e tests are translated. +// and v1.59.1 — only the divergences exercised by this repo's e2e tests are translated. // // Usage: node pinnedProxy.ts --listen 5400 --upstream 127.0.0.1:5401 @@ -98,7 +98,9 @@ httpServer.on('upgrade', (req, socket, head) => { if (rewritten === null) { return } - clientWs.send(rewritten) + for (const out of rewritten) { + clientWs.send(out) + } }) const closeBoth = () => { @@ -142,13 +144,27 @@ function forwardHeader(headers: http.IncomingHttpHeaders, name: string): Record< return typeof value === 'string' ? { [name]: value } : {} } -// Server (1.40) -> Client (1.58) -function rewriteServerToClient(text: string, guidTypes: Map): string | null { +// Server (1.40) -> Client (1.58). Returns one or more messages to forward to the client, or +// null to drop the upstream message. Returning multiple messages allows synthesising channels +// that newer client schemas require but the older server doesn't emit (e.g. Debugger). +function rewriteServerToClient(text: string, guidTypes: Map): string[] | null { let msg: JsonRpcMessage try { msg = JSON.parse(text) as JsonRpcMessage } catch { - return text + return [text] + } + // BrowserContextConsoleEvent requires `timestamp` (tFloat, ms since epoch) in 1.59. The 1.40 + // server emits `console` without that field — inject a best-effort wall time. + if ( + msg.method === 'console' && + msg.params && + msg.params.timestamp === undefined && + msg.guid !== undefined && + guidTypes.get(msg.guid) === 'BrowserContext' + ) { + msg.params.timestamp = Date.now() + return [JSON.stringify(msg)] } if (msg.method === '__create__' && msg.params) { const type = msg.params.type @@ -171,9 +187,31 @@ function rewriteServerToClient(text: string, guidTypes: Map): st if (type === 'Request' && init.hasResponse === undefined) { init.hasResponse = false } + // BrowserInitializer requires `browserName` in 1.59. In 1.40 the `name` field already + // held the browser type ("chromium" | "firefox" | "webkit"), so copy it over. + if (type === 'Browser' && init.browserName === undefined && typeof init.name === 'string') { + init.browserName = init.name + } + // BrowserContextInitializer requires a `debugger` Debugger channel in 1.59. The 1.40 server + // has no Debugger channel, so synthesise one (parented to the same Browser as the context) + // and inject the reference. DebuggerInitializer is `{}` in 1.59, so an empty initializer is + // valid. The client only uses BrowserContext.debugger when debug controller features are + // requested, which our tests don't do. + if (type === 'BrowserContext' && init.debugger === undefined && msg.params.guid) { + const debuggerGuid = `debugger@synthetic-${msg.params.guid}` + guidTypes.set(debuggerGuid, 'Debugger') + const debuggerCreate = { + guid: msg.guid, + method: '__create__', + params: { type: 'Debugger', initializer: {}, guid: debuggerGuid }, + } + init.debugger = { guid: debuggerGuid } + msg.params.initializer = init + return [JSON.stringify(debuggerCreate), JSON.stringify(msg)] + } msg.params.initializer = init } - return JSON.stringify(msg) + return [JSON.stringify(msg)] } // Client (1.58) -> Server (1.40) diff --git a/yarn.lock b/yarn.lock index eb9ee24962..cecbe22cb4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1697,14 +1697,14 @@ __metadata: languageName: node linkType: hard -"@playwright/test@npm:1.58.2": - version: 1.58.2 - resolution: "@playwright/test@npm:1.58.2" +"@playwright/test@npm:1.59.1": + version: 1.59.1 + resolution: "@playwright/test@npm:1.59.1" dependencies: - playwright: "npm:1.58.2" + playwright: "npm:1.59.1" bin: playwright: cli.js - checksum: 10c0/2164c03ad97c3653ff02e8818a71f3b2bbc344ac07924c9d8e31cd57505d6d37596015a41f51396b3ed8de6840f59143eaa9c21bf65515963da20740119811da + checksum: 10c0/8c2d94a860d3c254a0b114df2f888ad0a0e9310f45b6059bd5d4da196d965cadf6922267cef0881cfa9784d4bef6d78363d2c2d94caa64be67ff644c41162137 languageName: node linkType: hard @@ -3835,7 +3835,7 @@ __metadata: dependencies: "@eslint/js": "npm:9.39.4" "@jsdevtools/coverage-istanbul-loader": "npm:3.0.5" - "@playwright/test": "npm:1.58.2" + "@playwright/test": "npm:1.59.1" "@swc/core": "npm:1.15.33" "@types/busboy": "npm:1.5.4" "@types/chrome": "npm:0.1.40" @@ -9497,27 +9497,27 @@ __metadata: languageName: node linkType: hard -"playwright-core@npm:1.58.2": - version: 1.58.2 - resolution: "playwright-core@npm:1.58.2" +"playwright-core@npm:1.59.1": + version: 1.59.1 + resolution: "playwright-core@npm:1.59.1" bin: playwright-core: cli.js - checksum: 10c0/5aa15b2b764e6ffe738293a09081a6f7023847a0dbf4cd05fe10eed2e25450d321baf7482f938f2d2eb330291e197fa23e57b29a5b552b89927ceb791266225b + checksum: 10c0/d41a74d9681ce3beb3d5239e9ed577710b4ad099a6ca2476219c6599d51e9cb4b80bd72ed82c528da6a5d929c18ae3b872cf02bb83f78fa1c2cb9199c501abee languageName: node linkType: hard -"playwright@npm:1.58.2": - version: 1.58.2 - resolution: "playwright@npm:1.58.2" +"playwright@npm:1.59.1": + version: 1.59.1 + resolution: "playwright@npm:1.59.1" dependencies: fsevents: "npm:2.3.2" - playwright-core: "npm:1.58.2" + playwright-core: "npm:1.59.1" dependenciesMeta: fsevents: optional: true bin: playwright: cli.js - checksum: 10c0/d060d9b7cc124bd8b5dffebaab5e84f6b34654a553758fe7b19cc598dfbee93f6ecfbdc1832b40a6380ae04eade86ef3285ba03aa0b136799e83402246dc0727 + checksum: 10c0/dfe38396e616e5c4f98825ce90037bb96e477c5a2bd9258a24854f8ce72a8a41427b19098863866f85aa0216e70287dd537c4438d761aca93995e31ae099c533 languageName: node linkType: hard