From ca1df83582e2ff2c98b88679e0bdf2c4323a4d22 Mon Sep 17 00:00:00 2001 From: alok-108 Date: Sun, 18 Oct 2026 00:02:01 +0530 Subject: [PATCH] fix(har): store binary content with a textual mime type as base64 Fixes: https://github.com/microsoft/playwright/issues/43493 --- .../src/server/har/harTracer.ts | 7 ++-- tests/library/har.spec.ts | 33 +++++++++++++++++++ tests/registry.md | 6 ++-- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/packages/playwright-core/src/server/har/harTracer.ts b/packages/playwright-core/src/server/har/harTracer.ts index 5aa4ff800a808..8a5cb551d5701 100644 --- a/packages/playwright-core/src/server/har/harTracer.ts +++ b/packages/playwright-core/src/server/har/harTracer.ts @@ -552,8 +552,11 @@ export class HarTracer { if (this._options.content === 'embed') { // Sometimes, we can receive a font/media file with textual mime type. Browser // still interprets them correctly, but the 'content-type' header is obviously wrong. - if (isTextualMimeType(content.mimeType) && resourceType !== 'font') { - content.text = buffer.toString(); + // The body can also be binary despite a textual mime type, so only store it as text + // when it survives the roundtrip through a string unchanged. + const text = isTextualMimeType(content.mimeType) && resourceType !== 'font' ? buffer.toString() : undefined; + if (text !== undefined && Buffer.from(text).equals(buffer)) { + content.text = text; } else { content.text = buffer.toString('base64'); content.encoding = 'base64'; diff --git a/tests/library/har.spec.ts b/tests/library/har.spec.ts index 3db50af282f03..a8cd7b9d1f56e 100644 --- a/tests/library/har.spec.ts +++ b/tests/library/har.spec.ts @@ -368,6 +368,39 @@ it('HA-021 should include content @smoke', async ({ contextFactory, server }, te expect(log.entries[2].response.content.compression).toBe(0); }); +it('HA-113 should store binary content with a textual mime type as base64', async ({ contextFactory, server }, testInfo) => { + // Not valid utf-8, so it cannot be stored as text without losing data. + const body = Buffer.from([0x80, 0x81, 0x00, 0xc3, 0x28, 0xa0, 0xa1, 0xff, 0x41]); + server.setRoute('/binary.js', (req, res) => { + res.setHeader('Content-Type', 'application/javascript'); + res.end(body); + }); + const { page, getLog } = await pageWithHar(contextFactory, testInfo); + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => fetch('/binary.js').then(r => r.arrayBuffer())); + const log = await getLog(); + + const content = log.entries.find(e => e.request.url.endsWith('/binary.js'))!.response.content; + expect(content.mimeType).toBe('application/javascript'); + expect(content.encoding).toBe('base64'); + expect(Buffer.from(content.text!, 'base64')).toEqual(body); +}); + +it('HA-114 should store valid utf-8 content with a textual mime type as text', async ({ contextFactory, server }, testInfo) => { + server.setRoute('/utf8.js', (req, res) => { + res.setHeader('Content-Type', 'application/javascript'); + res.end('const greeting = "héllo wörld ✓";'); + }); + const { page, getLog } = await pageWithHar(contextFactory, testInfo); + await page.goto(server.EMPTY_PAGE); + await page.evaluate(() => fetch('/utf8.js').then(r => r.arrayBuffer())); + const log = await getLog(); + + const content = log.entries.find(e => e.request.url.endsWith('/utf8.js'))!.response.content; + expect(content.encoding).toBe(undefined); + expect(content.text).toBe('const greeting = "héllo wörld ✓";'); +}); + it('HA-022 should use attach mode for zip extension', async ({ contextFactory, server }, testInfo) => { const { page, getZip } = await pageWithHar(contextFactory, testInfo, { outputPath: 'test.har.zip' }); await page.goto(server.PREFIX + '/har.html'); diff --git a/tests/registry.md b/tests/registry.md index 442c8c679cf7e..acbfd3d4c80ac 100644 --- a/tests/registry.md +++ b/tests/registry.md @@ -26,7 +26,7 @@ subsystem, add a row to the Subsystems table. Then update this file: ## Subsystems -98 subsystems, 4933 tests (5330 with loop copies). +98 subsystems, 4935 tests (5332 with loop copies). | Code | Subsystem | Files | Tests | |---|---|---|---| @@ -78,7 +78,7 @@ subsystem, add a row to the Subsystems table. Then update this file: | `FR` | Frame | `page/{frame-evaluate, frame-frame-element, frame-goto, frame-hierarchy}` | 36 | | `GE` | Geolocation | `library/geolocation` | 9 | | `GN` | Locator and selector generator | `library/{locator-generator, selector-generator}` | 104 | -| `HA` | HAR | `library/{har, browsercontext-har, har-websocket}` | 112 | +| `HA` | HAR | `library/{har, browsercontext-har, har-websocket}` | 114 | | `HE` | Headful | `library/headful` | 16 | | `HT` | Hit target | `library/hit-target` | 22 | | `IF` | Input files | `page/page-set-input-files` | 21 | @@ -3157,6 +3157,8 @@ subsystem, add a row to the Subsystems table. Then update this file: | `HA-110` | `library/har-websocket.spec.ts` | should still allow routeWebSocket to fully mock the connection when capturing HAR | | `HA-111` | `library/har-websocket.spec.ts` | should still allow routeWebSocket to modify messages when capturing HAR | | `HA-112` | `library/har-websocket.spec.ts` | should respect PLAYWRIGHT\_HAR\_NO\_WEBSOCKET\_FRAMES | +| `HA-113` | `library/har.spec.ts` | should store binary content with a textual mime type as base64 | +| `HA-114` | `library/har.spec.ts` | should store valid utf-8 content with a textual mime type as text | ### HE — Headful