diff --git a/packages/playwright-core/src/server/har/harTracer.ts b/packages/playwright-core/src/server/har/harTracer.ts index 5aa4ff800a808..bcb2a8445de0a 100644 --- a/packages/playwright-core/src/server/har/harTracer.ts +++ b/packages/playwright-core/src/server/har/harTracer.ts @@ -14,6 +14,8 @@ * limitations under the License. */ +import { isUtf8 } from 'buffer'; + import mime from 'mime'; import { base64ByteLength } from '@isomorphic/base64'; import { ManualPromise } from '@isomorphic/manualPromise'; @@ -268,7 +270,7 @@ export class HarTracer { content.mimeType = contentType; // Keep the size as -1 "not available" when we did not actually read the body. if (event.body) { - this._storeResponseContent(event.body, content, 'other'); + this._storeResponseContent(event.body, content); if (!this._options.omitSizes) harEntry.response.bodySize = event.body.length; } @@ -380,7 +382,7 @@ export class HarTracer { const content = harEntry.response.content; compressionCalculationBarrier?.setDecodedBodySize(buffer.length); - this._storeResponseContent(buffer, content, request.resourceType()); + this._storeResponseContent(buffer, content); }).catch(() => { compressionCalculationBarrier?.setDecodedBodySize(0); }).then(() => { @@ -545,14 +547,14 @@ export class HarTracer { this._delegate.onEntryStarted(harEntry); } - private _storeResponseContent(buffer: Buffer, content: har.Content, resourceType: string) { + private _storeResponseContent(buffer: Buffer, content: har.Content) { if (!this._options.omitSizes) content.size = buffer.length; 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') { + if (isTextualMimeType(content.mimeType) && isUtf8(buffer)) { content.text = buffer.toString(); } else { content.text = buffer.toString('base64'); diff --git a/tests/library/har.spec.ts b/tests/library/har.spec.ts index 3db50af282f03..32207b15d102c 100644 --- a/tests/library/har.spec.ts +++ b/tests/library/har.spec.ts @@ -368,6 +368,24 @@ 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', { + annotation: { type: 'issue', description: 'https://github.com/microsoft/playwright/issues/43493' }, +}, async ({ contextFactory, server, browserName }, testInfo) => { + it.fixme(browserName === 'webkit', 'WebKit decodes the body of a textual resource before it gets to us'); + const body = Buffer.from([0x80, 0xc3, 0x28, 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(entry => entry.request.url.endsWith('/binary.js'))!.response.content; + expect(content.encoding).toBe('base64'); + expect(Buffer.from(content.text!, 'base64')).toEqual(body); +}); + 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..90e738b870e4b 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, 4934 tests (5331 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}` | 113 | | `HE` | Headful | `library/headful` | 16 | | `HT` | Hit target | `library/hit-target` | 22 | | `IF` | Input files | `page/page-set-input-files` | 21 | @@ -3157,6 +3157,7 @@ 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 | ### HE — Headful