diff --git a/packages/playwright-core/src/server/har/harTracer.ts b/packages/playwright-core/src/server/har/harTracer.ts index e6f22b6db00ba..6b38c72e464b5 100644 --- a/packages/playwright-core/src/server/har/harTracer.ts +++ b/packages/playwright-core/src/server/har/harTracer.ts @@ -285,6 +285,7 @@ export class HarTracer { const pageEntry = this._createPageEntryIfNeeded(page); const harEntry = createHarEntry(pageEntry?.id, request.method(), url, request.frame()?.guid, this._options); + harEntry._resourceType = request.resourceType(); this._recordRequestHeadersAndCookies(harEntry, request.headers()); harEntry.request.postData = this._postDataForRequest(request, this._options.content); if (!this._options.omitSizes) diff --git a/tests/library/har.spec.ts b/tests/library/har.spec.ts index 121726b52aa28..dd88e25086800 100644 --- a/tests/library/har.spec.ts +++ b/tests/library/har.spec.ts @@ -964,6 +964,62 @@ it('should support HAR larger than 512MB', async ({ contextFactory, server, brow expect(tail.toString()).toMatch(/\}\s*\}\s*$/); }); +it('should record resource type', async ({ contextFactory, server, asset }, testInfo) => { + server.setRoute('/resource-types.html', (req, res) => { + res.writeHead(200, { 'Content-Type': 'text/html' }); + res.end(` + + + + `); + }); + server.setRoute('/resource-type-stylesheet.css', (req, res) => { + res.writeHead(200, { 'Content-Type': 'text/css' }); + res.end(` + @font-face { + font-family: 'iconfont'; + src: url('/resource-type-font.woff2') format('woff2'); + } + body { font-family: 'iconfont'; } + `); + }); + server.setRoute('/resource-type-font.woff2', (req, res) => { + server.serveFile(req, res, asset('webfont/iconfont.woff2')); + }); + server.setRoute('/resource-type-script.js', (req, res) => { + res.writeHead(200, { 'Content-Type': 'application/javascript' }); + res.end('window.__loaded = true;'); + }); + server.setRoute('/resource-type-image.png', (req, res) => { + server.serveFile(req, res, asset('pptr.png')); + }); + + const { page, getLog } = await pageWithHar(contextFactory, testInfo); + await page.goto(server.PREFIX + '/resource-types.html'); + await page.evaluate(() => fetch('/resource-type-fetch').catch(() => {})); + await page.evaluate(() => new Promise(resolve => { + const xhr = new XMLHttpRequest(); + xhr.open('GET', '/resource-type-xhr'); + xhr.onloadend = () => resolve(); + xhr.send(); + })); + const log = await getLog(); + + const typeForURL = log.entries.reduce((accumulator, entry) => { + accumulator[entry.request.url] = entry._resourceType; + return accumulator; + }, {}); + expect(typeForURL).toMatchObject({ + [server.PREFIX + '/resource-types.html']: 'document', + [server.PREFIX + '/resource-type-stylesheet.css']: 'stylesheet', + [server.PREFIX + '/resource-type-script.js']: 'script', + [server.PREFIX + '/resource-type-image.png']: 'image', + [server.PREFIX + '/resource-type-font.woff2']: 'font', + [server.PREFIX + '/resource-type-fetch']: 'fetch', + [server.PREFIX + '/resource-type-xhr']: 'xhr', + }); +}); + it.describe('tracing.startHar', () => { it('should record a HAR with options', async ({ contextFactory, server }, testInfo) => { const context = await contextFactory();