From d32bfadce4c13c7a8e9cbb0fbde12ee7298e77ed Mon Sep 17 00:00:00 2001 From: mehmet turac Date: Thu, 18 Jun 2026 22:24:31 +0300 Subject: [PATCH] fix(trace-viewer): correct websocket frame times --- .../src/server/chromium/crNetworkManager.ts | 4 +-- .../src/server/webkit/webview/wvPage.ts | 4 +-- .../src/server/webkit/wkPage.ts | 4 +-- tests/library/trace-viewer.spec.ts | 35 +++++++++++++++++++ 4 files changed, 41 insertions(+), 6 deletions(-) diff --git a/packages/playwright-core/src/server/chromium/crNetworkManager.ts b/packages/playwright-core/src/server/chromium/crNetworkManager.ts index 3720aee560635..9620171f92b74 100644 --- a/packages/playwright-core/src/server/chromium/crNetworkManager.ts +++ b/packages/playwright-core/src/server/chromium/crNetworkManager.ts @@ -549,7 +549,7 @@ export class CRNetworkManager { _onWebSocketWillSendHandshakeRequest(event: Protocol.Network.webSocketWillSendHandshakeRequestPayload) { const wallTimeMs = event.wallTime * 1000; - this._timestampBaselineForWebSocket.set(event.requestId, wallTimeMs - event.timestamp); + this._timestampBaselineForWebSocket.set(event.requestId, wallTimeMs - event.timestamp * 1000); this._page!.frameManager.onWebSocketRequest(event.requestId, headersObjectToArray(event.request.headers, '\n'), wallTimeMs); } @@ -559,7 +559,7 @@ export class CRNetworkManager { } _timestampToWallTimeMsForWebSocket(requestId: string, timestamp: number): number { - return this._timestampBaselineForWebSocket.get(requestId)! + timestamp; + return this._timestampBaselineForWebSocket.get(requestId)! + timestamp * 1000; } private _maybeUpdateRequestSession(sessionInfo: SessionInfo, request: InterceptableRequest) { diff --git a/packages/playwright-core/src/server/webkit/webview/wvPage.ts b/packages/playwright-core/src/server/webkit/webview/wvPage.ts index 4d249e705ae4f..1969512faa528 100644 --- a/packages/playwright-core/src/server/webkit/webview/wvPage.ts +++ b/packages/playwright-core/src/server/webkit/webview/wvPage.ts @@ -1016,7 +1016,7 @@ export class WVPage implements PageDelegate { _onWebSocketWillSendHandshakeRequest(event: Protocol.Network.webSocketWillSendHandshakeRequestPayload) { const wallTimeMs = event.walltime * 1000; - this._timestampBaselineForWebSocket.set(event.requestId, wallTimeMs - event.timestamp); + this._timestampBaselineForWebSocket.set(event.requestId, wallTimeMs - event.timestamp * 1000); this._page.frameManager.onWebSocketRequest(event.requestId, headersObjectToArray(event.request.headers), wallTimeMs); } @@ -1026,7 +1026,7 @@ export class WVPage implements PageDelegate { } _timestampToWallTimeMsForWebSocket(requestId: string, timestamp: number): number { - return this._timestampBaselineForWebSocket.get(requestId)! + timestamp; + return this._timestampBaselineForWebSocket.get(requestId)! + timestamp * 1000; } shouldToggleStyleSheetToSyncAnimations(): boolean { diff --git a/packages/playwright-core/src/server/webkit/wkPage.ts b/packages/playwright-core/src/server/webkit/wkPage.ts index 37813bcf70269..83fc2b1ef02df 100644 --- a/packages/playwright-core/src/server/webkit/wkPage.ts +++ b/packages/playwright-core/src/server/webkit/wkPage.ts @@ -1209,7 +1209,7 @@ export class WKPage implements PageDelegate { _onWebSocketWillSendHandshakeRequest(event: Protocol.Network.webSocketWillSendHandshakeRequestPayload) { const wallTimeMs = event.walltime * 1000; - this._timestampBaselineForWebSocket.set(event.requestId, wallTimeMs - event.timestamp); + this._timestampBaselineForWebSocket.set(event.requestId, wallTimeMs - event.timestamp * 1000); this._page.frameManager.onWebSocketRequest(event.requestId, headersObjectToArray(event.request.headers), wallTimeMs); } @@ -1219,7 +1219,7 @@ export class WKPage implements PageDelegate { } _timestampToWallTimeMsForWebSocket(requestId: string, timestamp: number): number { - return this._timestampBaselineForWebSocket.get(requestId)! + timestamp; + return this._timestampBaselineForWebSocket.get(requestId)! + timestamp * 1000; } async _grantPermissions(origin: string, permissions: string[]) { diff --git a/tests/library/trace-viewer.spec.ts b/tests/library/trace-viewer.spec.ts index e8bc8e1d359c3..b2cbc18cfac79 100644 --- a/tests/library/trace-viewer.spec.ts +++ b/tests/library/trace-viewer.spec.ts @@ -727,6 +727,41 @@ test('should show websocket messages', { await expect(wsList.getByRole('option').nth(3)).toContainText('Binary'); }); +test('should show websocket message times in milliseconds', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/41360' } +}, async ({ page, server, runAndTrace }) => { + server.onceWebSocketConnection(ws => { + ws.on('message', message => ws.send(`echo:${message}`)); + }); + + const traceViewer = await runAndTrace(async () => { + await page.goto(server.EMPTY_PAGE); + await page.evaluate(async url => { + const ws = new WebSocket(url); + const waitForMessage = () => new Promise(resolve => ws.addEventListener('message', resolve, { once: true })); + await new Promise(resolve => ws.addEventListener('open', resolve, { once: true })); + ws.send('first'); + await waitForMessage(); + await new Promise(resolve => setTimeout(resolve, 1500)); + ws.send('second'); + await waitForMessage(); + ws.close(); + await new Promise(resolve => ws.addEventListener('close', resolve, { once: true })); + }, `ws://${server.HOST}/ws`); + }); + + await traceViewer.showNetworkTab(); + await traceViewer.networkRequests.filter({ hasText: 'ws' }).filter({ hasText: 'websocket' }).click(); + + const messagesTab = traceViewer.networkTab.getByRole('tabpanel', { name: 'Messages' }); + await traceViewer.networkTab.getByRole('tab', { name: 'Messages' }).click(); + const wsList = messagesTab.getByRole('listbox', { name: 'WebSocket messages' }); + await expect(wsList.getByRole('option')).toHaveCount(4); + await expect(wsList.getByRole('option').nth(2)).toContainText('second'); + const secondMessageTime = await wsList.getByRole('option').nth(2).locator('.network-websocket-message-time').textContent(); + expect(parseMillis(secondMessageTime!)).toBeGreaterThan(1000); +}); + test('should show snapshot URL and copy button', async ({ page, runAndTrace, server }) => { const traceViewer = await runAndTrace(async () => { await page.goto(server.EMPTY_PAGE);