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();