From fffc0604a1cc76b638a80295b1c5123bdcf2d82c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 12 May 2026 13:18:12 +0000 Subject: [PATCH 1/6] =?UTF-8?q?=F0=9F=91=B7=20Update=20dependency=20@playw?= =?UTF-8?q?right/test=20to=20v1.59.1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package.json | 2 +- yarn.lock | 30 +++++++++++++++--------------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/package.json b/package.json index aaca90b429..8b4d5874cf 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.42", diff --git a/yarn.lock b/yarn.lock index 27ffa8c19c..608a5c8190 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1746,14 +1746,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 @@ -3889,7 +3889,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.42" @@ -9551,27 +9551,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 From 515cc5bedc47a86bbb6056111fb34b86e83f5835 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Wed, 13 May 2026 08:50:58 +0200 Subject: [PATCH 2/6] =?UTF-8?q?=F0=9F=91=B7=20update=20pinned=20proxy=20tr?= =?UTF-8?q?anslation=20for=20Playwright=201.59?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Refresh comments to reference 1.59 client and v1.59.1 protocol diff. - Translate Browser initializer: populate `browserName` from legacy `name` field, which 1.59 now requires on BrowserInitializer. - Synthesise a Debugger channel on BrowserContext: 1.59 requires `BrowserContextInitializer.debugger`, but the 1.40 server has no such channel. Emit a synthetic `__create__` for an empty Debugger and inject the reference into the context initializer. - Inject a best-effort `timestamp` on `console` events from BrowserContext, which 1.59 requires on BrowserContextConsoleEvent. - Change `rewriteServerToClient` to return an array of messages so a single upstream message can produce multiple downstream messages (needed for the synthetic Debugger create). --- test/e2e/scripts/pinnedProxy.ts | 56 +++++++++++++++++++++++++++------ 1 file changed, 47 insertions(+), 9 deletions(-) diff --git a/test/e2e/scripts/pinnedProxy.ts b/test/e2e/scripts/pinnedProxy.ts index 88e75c3fc1..08d4bd2660 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 @@ -102,7 +102,9 @@ httpServer.on('upgrade', (req, socket, head) => { if (rewritten === null) { return } - clientWs.send(rewritten) + for (const out of rewritten) { + clientWs.send(out) + } }) const closeBoth = () => { @@ -146,18 +148,32 @@ function forwardHeader(headers: http.IncomingHttpHeaders, name: string): Record< return typeof value === 'string' ? { [name]: value } : {} } -// Server (1.40) -> Client (1.58) +// 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, requestUrls: Map, routeRequestGuids: Map -): string | null { +): 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 @@ -193,9 +209,31 @@ function rewriteServerToClient( 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) From df0fa40fd9c358f8866f409c5c49316077a54417 Mon Sep 17 00:00:00 2001 From: Thomas Lebeau Date: Wed, 13 May 2026 10:47:55 +0200 Subject: [PATCH 3/6] =?UTF-8?q?=F0=9F=90=9B=20notify=20deferred=20resource?= =?UTF-8?q?=20events=20synchronously=20after=20delay?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The deferred setTimeout path used handleResource which routes through the task queue's requestIdleCallback. That callback can fire after the click action's PAGE_ACTIVITY_END_DELAY window closes, leaving the resource uncounted by its parent action. Notify the lifecycle synchronously once the matching delay elapses so the resource is attributed correctly. --- .../src/domain/resource/resourceCollection.ts | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) 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)) } From ec9483cf16ba048b50ecc8864cde0e7c93b550f4 Mon Sep 17 00:00:00 2001 From: Boris Dibon Date: Thu, 28 May 2026 14:28:50 +0200 Subject: [PATCH 4/6] =?UTF-8?q?=F0=9F=91=B7=20Bump=20CI=20image?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitlab-ci.yml | 2 +- test/e2e/playwright.config.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index d4ea69986d..8b96cf9063 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,7 +1,7 @@ variables: CURRENT_STAGING: staging-22 APP: 'browser-sdk' - CURRENT_CI_IMAGE: 108 + CURRENT_CI_IMAGE: 109 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/test/e2e/playwright.config.ts b/test/e2e/playwright.config.ts index b5b0ca3d26..c5d074371f 100644 --- a/test/e2e/playwright.config.ts +++ b/test/e2e/playwright.config.ts @@ -7,7 +7,7 @@ import type { BrowserConfiguration } from '../browsers.conf' // Single config covering all e2e browser configurations: // -// - chromium / firefox / webkit / android: current Playwright (1.58)-bundled browsers. +// - chromium / firefox / webkit / android: current Playwright (1.59)-bundled browsers. // - chromium-pinned (Chrome 120) / firefox-pinned (FF 119) / webkit-pinned (WK 17.4): // replicate the old BrowserStack matrix locally via a pinned Playwright 1.40.1 // `run-server` and a translation proxy (test/e2e/scripts/pinnedProxy.ts) that bridges From e4d25c3644abe79d73bdf501504f329920813ed2 Mon Sep 17 00:00:00 2001 From: Boris Dibon Date: Thu, 28 May 2026 17:01:41 +0200 Subject: [PATCH 5/6] =?UTF-8?q?=E2=9C=85=20Fix=20collection=20of=20WebKit?= =?UTF-8?q?=2026.4=20domComplete=20timing?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/browser/performanceUtils.spec.ts | 58 +++++++++++++++++++ .../rum-core/src/browser/performanceUtils.ts | 8 ++- 2 files changed, 65 insertions(+), 1 deletion(-) diff --git a/packages/rum-core/src/browser/performanceUtils.spec.ts b/packages/rum-core/src/browser/performanceUtils.spec.ts index f39d8fa491..e9b3e266fe 100644 --- a/packages/rum-core/src/browser/performanceUtils.spec.ts +++ b/packages/rum-core/src/browser/performanceUtils.spec.ts @@ -1,4 +1,5 @@ import { type RelativeTime } from '@datadog/browser-core' +import { registerCleanupTask } from '@datadog/browser-core/test' import { createPerformanceEntry, mockGlobalPerformanceBuffer } from '../../test' import type { RumPerformanceNavigationTiming } from './performanceObservable' import { RumPerformanceEntryType } from './performanceObservable' @@ -51,6 +52,63 @@ describe('getNavigationEntry', () => { expect(navigationEntry.transferSize).toEqual(jasmine.any(Number)) } }) + + describe('when navigation entry has broken DOM timings (WebKit 26.4)', () => { + let originalSupportedEntryTypes: string[] | undefined + let originalTiming: PerformanceTiming + + beforeEach(() => { + originalSupportedEntryTypes = PerformanceObserver.supportedEntryTypes as string[] + Object.defineProperty(PerformanceObserver, 'supportedEntryTypes', { + get: () => [...(originalSupportedEntryTypes || []), RumPerformanceEntryType.NAVIGATION], + configurable: true, + }) + + mockGlobalPerformanceBuffer([ + createPerformanceEntry(RumPerformanceEntryType.NAVIGATION, { + domComplete: 0 as RelativeTime, + domContentLoadedEventEnd: 0 as RelativeTime, + domInteractive: 0 as RelativeTime, + loadEventEnd: 32 as RelativeTime, + responseStart: 6 as RelativeTime, + }), + ]) + + originalTiming = performance.timing + const navigationStart = originalTiming.navigationStart + Object.defineProperty(performance, 'timing', { + configurable: true, + value: { + ...originalTiming, + navigationStart, + domComplete: navigationStart + 456, + domContentLoadedEventEnd: navigationStart + 345, + domInteractive: navigationStart + 234, + loadEventEnd: navigationStart + 567, + responseStart: navigationStart + 123, + }, + }) + + registerCleanupTask(() => { + Object.defineProperty(performance, 'timing', { configurable: true, value: originalTiming }) + if (originalSupportedEntryTypes !== undefined) { + Object.defineProperty(PerformanceObserver, 'supportedEntryTypes', { + get: () => originalSupportedEntryTypes, + configurable: true, + }) + } + }) + }) + + it('falls back to deprecated performance timing', () => { + const navigationEntry = getNavigationEntry() + + expect(navigationEntry.domComplete).toBe(456 as RelativeTime) + expect(navigationEntry.domContentLoadedEventEnd).toBe(345 as RelativeTime) + expect(navigationEntry.domInteractive).toBe(234 as RelativeTime) + expect(navigationEntry.loadEventEnd).toBe(567 as RelativeTime) + }) + }) }) describe('findLcpResourceEntry', () => { diff --git a/packages/rum-core/src/browser/performanceUtils.ts b/packages/rum-core/src/browser/performanceUtils.ts index 298b4b16cb..da66ea2a6f 100644 --- a/packages/rum-core/src/browser/performanceUtils.ts +++ b/packages/rum-core/src/browser/performanceUtils.ts @@ -4,12 +4,18 @@ import type { RelevantNavigationTiming } from '../domain/view/viewMetrics/trackN import type { RumPerformanceNavigationTiming } from './performanceObservable' import { RumPerformanceEntryType, supportPerformanceTimingEvent } from './performanceObservable' +function hasBrokenDomTimings(entry: Pick) { + // WebKit 26.4 reports loadEventEnd/responseStart but leaves DOM timing fields at 0. + // performance.timing still has the correct values. + return entry.loadEventEnd > 0 && entry.domComplete <= 0 +} + export function getNavigationEntry(): RumPerformanceNavigationTiming { if (supportPerformanceTimingEvent(RumPerformanceEntryType.NAVIGATION)) { const navigationEntry = performance.getEntriesByType( RumPerformanceEntryType.NAVIGATION )[0] as unknown as RumPerformanceNavigationTiming - if (navigationEntry) { + if (navigationEntry && !hasBrokenDomTimings(navigationEntry)) { return navigationEntry } } From aadd15a2654a6805e454bca193d2ddb4e1d0500d Mon Sep 17 00:00:00 2001 From: Boris Dibon Date: Fri, 29 May 2026 11:52:19 +0200 Subject: [PATCH 6/6] =?UTF-8?q?=F0=9F=91=8C=20Use=20positive=20predicate?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- packages/rum-core/src/browser/performanceUtils.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/rum-core/src/browser/performanceUtils.ts b/packages/rum-core/src/browser/performanceUtils.ts index da66ea2a6f..28119fbe93 100644 --- a/packages/rum-core/src/browser/performanceUtils.ts +++ b/packages/rum-core/src/browser/performanceUtils.ts @@ -4,10 +4,10 @@ import type { RelevantNavigationTiming } from '../domain/view/viewMetrics/trackN import type { RumPerformanceNavigationTiming } from './performanceObservable' import { RumPerformanceEntryType, supportPerformanceTimingEvent } from './performanceObservable' -function hasBrokenDomTimings(entry: Pick) { +function isValidNavigationEntry(entry: Pick) { // WebKit 26.4 reports loadEventEnd/responseStart but leaves DOM timing fields at 0. // performance.timing still has the correct values. - return entry.loadEventEnd > 0 && entry.domComplete <= 0 + return !(entry.loadEventEnd > 0 && entry.domComplete <= 0) } export function getNavigationEntry(): RumPerformanceNavigationTiming { @@ -15,7 +15,7 @@ export function getNavigationEntry(): RumPerformanceNavigationTiming { const navigationEntry = performance.getEntriesByType( RumPerformanceEntryType.NAVIGATION )[0] as unknown as RumPerformanceNavigationTiming - if (navigationEntry && !hasBrokenDomTimings(navigationEntry)) { + if (navigationEntry && isValidNavigationEntry(navigationEntry)) { return navigationEntry } }