From e442e1551214abf1ee797f3045a195ab848381ca Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 4 Aug 2026 13:31:22 -0700 Subject: [PATCH 1/2] test: unflake har-websocket timing assertions - tolerate 100ms clock skew between browser-reported wall times and Date.now() (flaked on Windows bots only, where the browser clock and the Node clock can disagree at the millisecond level) - allow the recorded websocket duration to slightly under-run the nominal setTimeout delays - report zero HAR entry duration instead of -1 when all websocket frames share a single coarse-clock timestamp --- .../src/server/har/harTracer.ts | 4 ++-- tests/library/har-websocket.spec.ts | 21 ++++++++++++------- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/packages/playwright-core/src/server/har/harTracer.ts b/packages/playwright-core/src/server/har/harTracer.ts index c888a33307450..4817c94112edc 100644 --- a/packages/playwright-core/src/server/har/harTracer.ts +++ b/packages/playwright-core/src/server/har/harTracer.ts @@ -481,9 +481,9 @@ export class HarTracer { oldestWallTimeMs = wallTimeMs; if (wallTimeMs > newestWallTimeMs) newestWallTimeMs = wallTimeMs; - if (oldestWallTimeMs === newestWallTimeMs) - return; + // On coarse clocks all frames may share a single timestamp, in which case + // the duration is reported as zero rather than left unknown. harEntry.time = newestWallTimeMs - oldestWallTimeMs; }; diff --git a/tests/library/har-websocket.spec.ts b/tests/library/har-websocket.spec.ts index 20f780bd585a0..3f517cfebf8cd 100644 --- a/tests/library/har-websocket.spec.ts +++ b/tests/library/har-websocket.spec.ts @@ -61,6 +61,11 @@ function responseHeadersSize(headers: { name: string, value: string }[]): number return result; } +// Browser-reported message times are derived from a wall-clock baseline plus +// monotonic timestamps in the browser process, so they can drift by a few +// milliseconds relative to Date.now() in the test process. +const clockSkewMs = 100; + function messageSize(message: string | number[]): number { // The payload is short enough that they only need the minimum frame header size. if (message.length <= 125) @@ -119,8 +124,8 @@ it('should include websocket handshake headers and status', async ({ contextFact expect(wsEntry.response.headersSize).toBe(responseHeadersSize(wsEntry.response.headers)); const wallTimeMs = new Date(wsEntry.startedDateTime).getTime(); - expect(wallTimeMs).toBeGreaterThanOrEqual(beforeMs); - expect(wallTimeMs).toBeLessThanOrEqual(afterMs); + expect(wallTimeMs).toBeGreaterThanOrEqual(beforeMs - clockSkewMs); + expect(wallTimeMs).toBeLessThanOrEqual(afterMs + clockSkewMs); const requestHeaderNames = wsEntry.request.headers.map(h => h.name.toLowerCase()); expect(requestHeaderNames).toContain('upgrade'); @@ -219,13 +224,15 @@ async function testWebSocketMessages(contextFactory, server, testInfo, content, // message times cannot be compared against the host wall clock. if (channel !== 'webkit-wsl') { for (const m of messages) { - expect(m.time).toBeGreaterThanOrEqual(beforeMs - 1); - expect(m.time).toBeLessThanOrEqual(afterMs + 1); + expect(m.time).toBeGreaterThanOrEqual(beforeMs - clockSkewMs); + expect(m.time).toBeLessThanOrEqual(afterMs + clockSkewMs); } } expect(messages[0].time).toBeLessThanOrEqual(messages[1].time); expect(wsEntry.time).toBeGreaterThanOrEqual(messages[messages.length - 1].time - messages[0].time); - expect(wsEntry.time).toBeGreaterThanOrEqual(delayMs * (incomingCount + outgoingCount)); + // setTimeout may fire marginally early and browser-reported timestamps are + // coarse on some platforms, so the measured span can under-run the nominal delays. + expect(wsEntry.time).toBeGreaterThanOrEqual(delayMs * (incomingCount + outgoingCount) - clockSkewMs); } it('should embed websocket messages', async ({ contextFactory, server, channel }, testInfo) => { @@ -297,8 +304,8 @@ it('should attach websocket messages for a still open websocket after stopping', // message times cannot be compared against the host wall clock. if (channel !== 'webkit-wsl') { for (const m of messages) { - expect(m.time).toBeGreaterThanOrEqual(beforeMs - 1); - expect(m.time).toBeLessThanOrEqual(afterMs + 1); + expect(m.time).toBeGreaterThanOrEqual(beforeMs - clockSkewMs); + expect(m.time).toBeLessThanOrEqual(afterMs + clockSkewMs); } } expect(messages[0].time).toBeLessThanOrEqual(messages[1].time); From d26b8531e88991df0bf62845db8fc38aee01091f Mon Sep 17 00:00:00 2001 From: Yury Semikhatsky Date: Tue, 4 Aug 2026 14:51:14 -0700 Subject: [PATCH 2/2] test: drop websocket duration lower-bound check --- tests/library/har-websocket.spec.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/tests/library/har-websocket.spec.ts b/tests/library/har-websocket.spec.ts index 3f517cfebf8cd..9ef6193ff0c25 100644 --- a/tests/library/har-websocket.spec.ts +++ b/tests/library/har-websocket.spec.ts @@ -147,7 +147,6 @@ async function testWebSocketMessages(contextFactory, server, testInfo, content, const outgoingText = ['y'.repeat(125), 'y'.repeat(126), 'y'.repeat(2 ** 16)]; const outgoingBinary = [(new Array(125)).fill(0x02), (new Array(126)).fill(0x02), (new Array(2 ** 16)).fill(0x02)]; const incomingCount = incomingText.length + incomingBinary.length; - const outgoingCount = outgoingText.length + outgoingBinary.length; const delayMs = 100; server.onceWebSocketConnection(async ws => { @@ -230,9 +229,6 @@ async function testWebSocketMessages(contextFactory, server, testInfo, content, } expect(messages[0].time).toBeLessThanOrEqual(messages[1].time); expect(wsEntry.time).toBeGreaterThanOrEqual(messages[messages.length - 1].time - messages[0].time); - // setTimeout may fire marginally early and browser-reported timestamps are - // coarse on some platforms, so the measured span can under-run the nominal delays. - expect(wsEntry.time).toBeGreaterThanOrEqual(delayMs * (incomingCount + outgoingCount) - clockSkewMs); } it('should embed websocket messages', async ({ contextFactory, server, channel }, testInfo) => {