diff --git a/packages/playwright-core/src/server/har/harTracer.ts b/packages/playwright-core/src/server/har/harTracer.ts index 8c4de82ff1..57605a1772 100644 --- a/packages/playwright-core/src/server/har/harTracer.ts +++ b/packages/playwright-core/src/server/har/harTracer.ts @@ -191,7 +191,7 @@ export class HarTracer { const contentType = event.headers['content-type']; if (contentType) content.mimeType = contentType; - this._storeResponseContent(event.body, content); + this._storeResponseContent(event.body, content, 'other'); if (this._started) this._delegate.onEntryFinished(harEntry); @@ -261,7 +261,7 @@ export class HarTracer { const content = harEntry.response.content; compressionCalculationBarrier.setDecodedBodySize(buffer.length); - this._storeResponseContent(buffer, content); + this._storeResponseContent(buffer, content, request.resourceType()); }).catch(() => { compressionCalculationBarrier.setDecodedBodySize(0); }).then(() => { @@ -296,15 +296,21 @@ export class HarTracer { this._delegate.onEntryFinished(harEntry); } - private _storeResponseContent(buffer: Buffer | undefined, content: har.Content) { + private _storeResponseContent(buffer: Buffer | undefined, content: har.Content, resourceType: string) { if (!buffer) { content.size = 0; return; } content.size = buffer.length; if (this._options.content === 'embedded') { - content.text = buffer.toString('base64'); - content.encoding = 'base64'; + // 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(); + } else { + content.text = buffer.toString('base64'); + content.encoding = 'base64'; + } } else if (this._options.content === 'sha1') { content._sha1 = calculateSha1(buffer) + '.' + (mime.getExtension(content.mimeType) || 'dat'); if (this._started) @@ -529,3 +535,7 @@ function parseCookie(c: string): har.Cookie { } return cookie; } + +function isTextualMimeType(mimeType: string) { + return !!mimeType.match(/^(text\/.*?|application\/(json|(x-)?javascript|xml.*?|ecmascript)|image\/svg(\+xml)?|application\/.*?(\+json|\+xml))(;\s*charset=.*)?$/); +} diff --git a/tests/library/har.spec.ts b/tests/library/har.spec.ts index 1e62e5f9c0..7fcf433591 100644 --- a/tests/library/har.spec.ts +++ b/tests/library/har.spec.ts @@ -248,21 +248,26 @@ it('should include secure set-cookies', async ({ contextFactory, httpsServer }, it('should include content @smoke', async ({ contextFactory, server }, testInfo) => { const { page, getLog } = await pageWithHar(contextFactory, testInfo); await page.goto(server.PREFIX + '/har.html'); + await page.evaluate(() => fetch('/pptr.png').then(r => r.arrayBuffer())); const log = await getLog(); - expect(log.entries[0].response.httpVersion).toBe('HTTP/1.1'); - expect(log.entries[0].response.content.encoding).toBe('base64'); + expect(log.entries[0].response.content.encoding).toBe(undefined); expect(log.entries[0].response.content.mimeType).toBe('text/html; charset=utf-8'); - expect(Buffer.from(log.entries[0].response.content.text, 'base64').toString()).toContain('HAR Page'); + expect(log.entries[0].response.content.text).toContain('HAR Page'); expect(log.entries[0].response.content.size).toBeGreaterThanOrEqual(96); expect(log.entries[0].response.content.compression).toBe(0); - expect(log.entries[1].response.httpVersion).toBe('HTTP/1.1'); - expect(log.entries[1].response.content.encoding).toBe('base64'); + expect(log.entries[1].response.content.encoding).toBe(undefined); expect(log.entries[1].response.content.mimeType).toBe('text/css; charset=utf-8'); - expect(Buffer.from(log.entries[1].response.content.text, 'base64').toString()).toContain('pink'); + expect(log.entries[1].response.content.text).toContain('pink'); expect(log.entries[1].response.content.size).toBeGreaterThanOrEqual(37); expect(log.entries[1].response.content.compression).toBe(0); + + expect(log.entries[2].response.content.encoding).toBe('base64'); + expect(log.entries[2].response.content.mimeType).toBe('image/png'); + expect(Buffer.from(log.entries[2].response.content.text, 'base64').byteLength).toBeGreaterThan(0); + expect(log.entries[2].response.content.size).toBeGreaterThanOrEqual(6000); + expect(log.entries[2].response.content.compression).toBe(0); }); it('should filter by glob', async ({ contextFactory, server }, testInfo) => { @@ -580,7 +585,7 @@ it('should contain http2 for http2 requests', async ({ contextFactory, browserNa const log = await getLog(); expect(log.entries[0].request.httpVersion).toBe('HTTP/2.0'); expect(log.entries[0].response.httpVersion).toBe('HTTP/2.0'); - expect(Buffer.from(log.entries[0].response.content.text, 'base64').toString()).toBe('

Hello World

'); + expect(log.entries[0].response.content.text).toBe('

Hello World

'); server.close(); }); @@ -734,7 +739,7 @@ it('should include API request', async ({ contextFactory, server }, testInfo) => expect(entry.response.status).toBe(200); expect(entry.response.headers.find(h => h.name.toLowerCase() === 'content-type')?.value).toContain('application/json'); expect(entry.response.content.size).toBe(15); - expect(entry.response.content.text).toBe(responseBody.toString('base64')); + expect(entry.response.content.text).toBe(responseBody.toString()); }); it('should not hang on resources served from cache', async ({ contextFactory, server, browserName }, testInfo) => {