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..9ef6193ff0c25 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'); @@ -142,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 => { @@ -219,13 +223,12 @@ 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)); } it('should embed websocket messages', async ({ contextFactory, server, channel }, testInfo) => { @@ -297,8 +300,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);