From 883e4d449acea11fbb2462312884b2ba85386d5b Mon Sep 17 00:00:00 2001 From: Pavel Feldman Date: Mon, 20 Jun 2022 17:22:32 -0700 Subject: [PATCH] test(har): add more har tests (#14997) --- .../dispatchers/localUtilsDispatcher.ts | 50 +++++------ tests/library/browsercontext-har.spec.ts | 90 +++++++++++++++++++ 2 files changed, 110 insertions(+), 30 deletions(-) diff --git a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts index 84baa133aa..717ba3e68f 100644 --- a/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts +++ b/packages/playwright-core/src/server/dispatchers/localUtilsDispatcher.ts @@ -194,44 +194,34 @@ class HarBackend { const harLog = this._harFile.log; const visited = new Set(); while (true) { - const entries = harLog.entries.filter(entry => entry.request.url === url && entry.request.method === method); + const entries: HAREntry[] = []; + for (const candidate of harLog.entries) { + if (candidate.request.url !== url || candidate.request.method !== method) + continue; + if (method === 'POST' && postData && candidate.request.postData) { + const buffer = await this._loadContent(candidate.request.postData); + if (!buffer.equals(postData)) + continue; + } + entries.push(candidate); + } + if (!entries.length) return; - let entry: HAREntry | undefined; + let entry = entries[0]; + // Disambiguate using headers - then one with most matching headers wins. if (entries.length > 1) { - // Disambiguating requests - - // 1. Disambiguate using postData - this covers GraphQL - if (!entry && postData) { - for (const candidate of entries) { - if (!candidate.request.postData) - continue; - const buffer = await this._loadContent(candidate.request.postData); - if (buffer.equals(postData)) { - entry = candidate; - break; - } - } + const list: { candidate: HAREntry, matchingHeaders: number }[] = []; + for (const candidate of entries) { + const matchingHeaders = countMatchingHeaders(candidate.request.headers, headers); + list.push({ candidate, matchingHeaders }); } - - // Last. Disambiguate using headers - then one with most matching headers wins. - if (!entry) { - const list: { candidate: HAREntry, matchingHeaders: number }[] = []; - for (const candidate of entries) { - const matchingHeaders = countMatchingHeaders(candidate.request.headers, headers); - list.push({ candidate, matchingHeaders }); - } - list.sort((a, b) => b.matchingHeaders - a.matchingHeaders); - entry = list[0].candidate; - } - - } else { - entry = entries[0]; + list.sort((a, b) => b.matchingHeaders - a.matchingHeaders); + entry = list[0].candidate; } - if (visited.has(entry)) throw new Error(`Found redirect cycle for ${url}`); diff --git a/tests/library/browsercontext-har.spec.ts b/tests/library/browsercontext-har.spec.ts index d6a3ee0540..070d2a62f8 100644 --- a/tests/library/browsercontext-har.spec.ts +++ b/tests/library/browsercontext-har.spec.ts @@ -16,6 +16,8 @@ import { browserTest as it, expect } from '../config/browserTest'; import fs from 'fs'; +import path from 'path'; +import extractZip from '../../packages/playwright-core/bundles/zip/node_modules/extract-zip'; it('should fulfill from har, matching the method and following redirects', async ({ contextFactory, isAndroid, asset }) => { it.fixme(isAndroid); @@ -192,3 +194,91 @@ it('should round-trip har.zip', async ({ contextFactory, isAndroid, server }, te expect(await page2.content()).toContain('hello, world!'); await expect(page2.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)'); }); + +it('should round-trip extracted har.zip', async ({ contextFactory, isAndroid, server }, testInfo) => { + it.fixme(isAndroid); + + const harPath = testInfo.outputPath('har.zip'); + const context1 = await contextFactory({ recordHar: { path: harPath } }); + const page1 = await context1.newPage(); + await page1.goto(server.PREFIX + '/one-style.html'); + await context1.close(); + + const harDir = testInfo.outputPath('hardir'); + await extractZip(harPath, { dir: harDir }); + + const context2 = await contextFactory({ har: { path: path.join(harDir, 'har.har') } }); + const page2 = await context2.newPage(); + await page2.goto(server.PREFIX + '/one-style.html'); + expect(await page2.content()).toContain('hello, world!'); + await expect(page2.locator('body')).toHaveCSS('background-color', 'rgb(255, 192, 203)'); +}); + +it('should round-trip har with postData', async ({ contextFactory, isAndroid, server }, testInfo) => { + it.fixme(isAndroid); + server.setRoute('/echo', async (req, res) => { + const body = await req.postBody; + res.end(body.toString()); + }); + + const harPath = testInfo.outputPath('har.zip'); + const context1 = await contextFactory({ recordHar: { path: harPath } }); + const page1 = await context1.newPage(); + await page1.goto(server.EMPTY_PAGE); + const fetchFunction = async (body: string) => { + const response = await fetch('/echo', { method: 'POST', body }); + return await response.text(); + }; + + expect(await page1.evaluate(fetchFunction, '1')).toBe('1'); + expect(await page1.evaluate(fetchFunction, '2')).toBe('2'); + expect(await page1.evaluate(fetchFunction, '3')).toBe('3'); + await context1.close(); + + const context2 = await contextFactory({ har: { path: harPath } }); + const page2 = await context2.newPage(); + await page2.goto(server.EMPTY_PAGE); + expect(await page2.evaluate(fetchFunction, '1')).toBe('1'); + expect(await page2.evaluate(fetchFunction, '2')).toBe('2'); + expect(await page2.evaluate(fetchFunction, '3')).toBe('3'); + expect(await page2.evaluate(fetchFunction, '4').catch(e => e)).toBeTruthy(); +}); + +it('should disambiguate by header', async ({ contextFactory, isAndroid, server }, testInfo) => { + it.fixme(isAndroid); + + server.setRoute('/echo', async (req, res) => { + res.end(req.headers['baz']); + }); + + const harPath = testInfo.outputPath('har.zip'); + const context1 = await contextFactory({ recordHar: { path: harPath } }); + const page1 = await context1.newPage(); + await page1.goto(server.EMPTY_PAGE); + + const fetchFunction = async (bazValue: string) => { + const response = await fetch('/echo', { + method: 'POST', + body: '', + headers: { + foo: 'foo-value', + bar: 'bar-value', + baz: bazValue, + } + }); + return await response.text(); + }; + + expect(await page1.evaluate(fetchFunction, 'baz1')).toBe('baz1'); + expect(await page1.evaluate(fetchFunction, 'baz2')).toBe('baz2'); + expect(await page1.evaluate(fetchFunction, 'baz3')).toBe('baz3'); + await context1.close(); + + const context2 = await contextFactory({ har: { path: harPath } }); + const page2 = await context2.newPage(); + await page2.goto(server.EMPTY_PAGE); + expect(await page2.evaluate(fetchFunction, 'baz1')).toBe('baz1'); + expect(await page2.evaluate(fetchFunction, 'baz2')).toBe('baz2'); + expect(await page2.evaluate(fetchFunction, 'baz3')).toBe('baz3'); + expect(await page2.evaluate(fetchFunction, 'baz4')).toBe('baz1'); +});