From 539739465392c9b794b4cf628f3a81e170bdf3b1 Mon Sep 17 00:00:00 2001 From: Dmitry Gozman Date: Mon, 20 Jun 2022 13:37:31 -0700 Subject: [PATCH] feature(har): add testOptions.har (#14991) Can now be used with `test.use({ har })`. Also added more tests for latest har features. --- docs/src/test-api/class-testoptions.md | 2 + packages/playwright-test/src/index.ts | 4 + packages/playwright-test/types/test.d.ts | 9 +++ tests/assets/har-sha1-main-response.txt | 1 + tests/assets/har-sha1.har | 96 ++++++++++++++++++++++++ tests/library/browsercontext-har.spec.ts | 26 +++++++ tests/playwright-test/playwright.spec.ts | 19 ++++- utils/generate_types/overrides-test.d.ts | 2 + 8 files changed, 157 insertions(+), 2 deletions(-) create mode 100644 tests/assets/har-sha1-main-response.txt create mode 100644 tests/assets/har-sha1.har diff --git a/docs/src/test-api/class-testoptions.md b/docs/src/test-api/class-testoptions.md index 7c86c20736..f78126770f 100644 --- a/docs/src/test-api/class-testoptions.md +++ b/docs/src/test-api/class-testoptions.md @@ -129,6 +129,8 @@ Options used to create the context, as passed to [`method: Browser.newContext`]. ## property: TestOptions.geolocation = %%-context-option-geolocation-%% +## property: TestOptions.har = %%-js-context-option-har-%% + ## property: TestOptions.hasTouch = %%-context-option-hastouch-%% ## property: TestOptions.headless = %%-browser-option-headless-%% diff --git a/packages/playwright-test/src/index.ts b/packages/playwright-test/src/index.ts index cb4b6990c9..bed948d16e 100644 --- a/packages/playwright-test/src/index.ts +++ b/packages/playwright-test/src/index.ts @@ -141,6 +141,7 @@ export const test = _baseTest.extend({ deviceScaleFactor: [ undefined, { option: true } ], extraHTTPHeaders: [ undefined, { option: true } ], geolocation: [ undefined, { option: true } ], + har: [undefined, { option: true }], hasTouch: [ undefined, { option: true } ], httpCredentials: [ undefined, { option: true } ], ignoreHTTPSErrors: [ undefined, { option: true } ], @@ -168,6 +169,7 @@ export const test = _baseTest.extend({ colorScheme, deviceScaleFactor, extraHTTPHeaders, + har, hasTouch, geolocation, httpCredentials, @@ -199,6 +201,8 @@ export const test = _baseTest.extend({ options.extraHTTPHeaders = extraHTTPHeaders; if (geolocation !== undefined) options.geolocation = geolocation; + if (har !== undefined) + options.har = har; if (hasTouch !== undefined) options.hasTouch = hasTouch; if (httpCredentials !== undefined) diff --git a/packages/playwright-test/types/test.d.ts b/packages/playwright-test/types/test.d.ts index 69f57e71a9..c70a493fcc 100644 --- a/packages/playwright-test/types/test.d.ts +++ b/packages/playwright-test/types/test.d.ts @@ -2492,6 +2492,7 @@ type BrowserName = 'chromium' | 'firefox' | 'webkit'; type BrowserChannel = Exclude; type ColorScheme = Exclude; type ExtraHTTPHeaders = Exclude; +type HAROptions = Exclude; type Proxy = Exclude; type StorageState = Exclude; type ServiceWorkerPolicy = Exclude; @@ -2699,6 +2700,14 @@ export interface PlaywrightTestOptions { */ extraHTTPHeaders: ExtraHTTPHeaders | undefined; geolocation: Geolocation | undefined; + /** + * If specified the network requests that are made in the context will be served from the HAR file. + * + * > NOTE: Playwright will not serve requests intercepted by Service Worker from the HAR file. See + * [this](https://github.com/microsoft/playwright/issues/1090) issue. We recommend disabling Service Workers when using + * request interception. Via `await context.addInitScript(() => delete window.navigator.serviceWorker);` + */ + har: HAROptions | undefined; /** * Specifies if viewport supports touch events. Defaults to false. */ diff --git a/tests/assets/har-sha1-main-response.txt b/tests/assets/har-sha1-main-response.txt new file mode 100644 index 0000000000..dbe9dba55e --- /dev/null +++ b/tests/assets/har-sha1-main-response.txt @@ -0,0 +1 @@ +Hello, world \ No newline at end of file diff --git a/tests/assets/har-sha1.har b/tests/assets/har-sha1.har new file mode 100644 index 0000000000..2b849154b8 --- /dev/null +++ b/tests/assets/har-sha1.har @@ -0,0 +1,96 @@ +{ + "log": { + "version": "1.2", + "creator": { + "name": "Playwright", + "version": "1.23.0-next" + }, + "browser": { + "name": "chromium", + "version": "103.0.5060.33" + }, + "pages": [ + { + "startedDateTime": "2022-06-10T04:27:32.125Z", + "id": "page@b17b177f1c2e66459db3dcbe44636ffd", + "title": "Hey", + "pageTimings": { + "onContentLoad": 70, + "onLoad": 70 + } + } + ], + "entries": [ + { + "_requestref": "request@ee2a0dc164935fcd4d9432d37b245f3c", + "_frameref": "frame@c7467fc0f1f86f09fc3b0d727a3862ea", + "_monotonicTime": 270572145.898, + "startedDateTime": "2022-06-10T04:27:32.146Z", + "time": 8.286, + "request": { + "method": "GET", + "url": "http://no.playwright/", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "Accept", + "value": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9" + }, + { + "name": "Upgrade-Insecure-Requests", + "value": "1" + }, + { + "name": "User-Agent", + "value": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.5060.33 Safari/537.36" + } + ], + "queryString": [], + "headersSize": 326, + "bodySize": 0 + }, + "response": { + "status": 200, + "statusText": "OK", + "httpVersion": "HTTP/1.1", + "cookies": [], + "headers": [ + { + "name": "content-length", + "value": "12" + }, + { + "name": "content-type", + "value": "text/html" + } + ], + "content": { + "size": 12, + "mimeType": "text/html", + "compression": 0, + "_sha1": "har-sha1-main-response.txt" + }, + "headersSize": 64, + "bodySize": 71, + "redirectURL": "", + "_transferSize": 71 + }, + "cache": { + "beforeRequest": null, + "afterRequest": null + }, + "timings": { + "dns": -1, + "connect": -1, + "ssl": -1, + "send": 0, + "wait": 8.286, + "receive": -1 + }, + "pageref": "page@b17b177f1c2e66459db3dcbe44636ffd", + "_securityDetails": {} + } + ] + } +} \ No newline at end of file diff --git a/tests/library/browsercontext-har.spec.ts b/tests/library/browsercontext-har.spec.ts index fb0e26eee6..d6a3ee0540 100644 --- a/tests/library/browsercontext-har.spec.ts +++ b/tests/library/browsercontext-har.spec.ts @@ -166,3 +166,29 @@ it('should reload redirected navigation', async ({ contextFactory, isAndroid, as expect(response.request().url()).toBe('https://www.theverge.com/'); expect(await page.evaluate(() => location.href)).toBe('https://www.theverge.com/'); }); + +it('should fulfill from har with content in a file', async ({ contextFactory, isAndroid, asset }) => { + it.fixme(isAndroid); + + const path = asset('har-sha1.har'); + const context = await contextFactory({ har: { path } }); + const page = await context.newPage(); + await page.goto('http://no.playwright/'); + expect(await page.content()).toBe('Hello, world'); +}); + +it('should round-trip 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 context2 = await contextFactory({ har: { path: harPath, fallback: 'abort' } }); + 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)'); +}); diff --git a/tests/playwright-test/playwright.spec.ts b/tests/playwright-test/playwright.spec.ts index 760bb16729..3cddfcf6ac 100644 --- a/tests/playwright-test/playwright.spec.ts +++ b/tests/playwright-test/playwright.spec.ts @@ -30,7 +30,7 @@ export class VideoPlayer { const output = spawnSync(ffmpeg, ['-i', fileName, '-r', '25', `${fileName}-%03d.png`]).stderr.toString(); const lines = output.split('\n'); const streamLine = lines.find(l => l.trim().startsWith('Stream #0:0')); - const resolutionMatch = streamLine.match(/, (\d+)x(\d+),/); + const resolutionMatch = streamLine!.match(/, (\d+)x(\d+),/); this.videoWidth = parseInt(resolutionMatch![1], 10); this.videoHeight = parseInt(resolutionMatch![2], 10); } @@ -526,7 +526,7 @@ test('should work with video: on-first-retry', async ({ runInlineTest }, testInf expect(result.report.suites[0].specs[1].tests[0].results[1].attachments).toEqual([{ name: 'video', contentType: 'video/webm', - path: path.join(dirRetry, videoFailRetry), + path: path.join(dirRetry, videoFailRetry!), }]); }); @@ -602,3 +602,18 @@ test('should pass fixture defaults to tests', async ({ runInlineTest }) => { expect(result.exitCode).toBe(0); expect(result.passed).toBe(1); }); + +test('should support har option', async ({ runInlineTest, asset }) => { + const result = await runInlineTest({ + 'a.test.ts': ` + const { test } = pwt; + test.use({ har: { path: ${JSON.stringify(asset('har-fulfill.har'))} }}); + test('pass', async ({ page }) => { + await page.goto('http://no.playwright/'); + expect(await page.evaluate('window.value')).toBe('foo'); + }); + `, + }, { workers: 1 }); + expect(result.exitCode).toBe(0); + expect(result.passed).toBe(1); +}); diff --git a/utils/generate_types/overrides-test.d.ts b/utils/generate_types/overrides-test.d.ts index 45adcd1b58..de8562eca2 100644 --- a/utils/generate_types/overrides-test.d.ts +++ b/utils/generate_types/overrides-test.d.ts @@ -173,6 +173,7 @@ type BrowserName = 'chromium' | 'firefox' | 'webkit'; type BrowserChannel = Exclude; type ColorScheme = Exclude; type ExtraHTTPHeaders = Exclude; +type HAROptions = Exclude; type Proxy = Exclude; type StorageState = Exclude; type ServiceWorkerPolicy = Exclude; @@ -215,6 +216,7 @@ export interface PlaywrightTestOptions { deviceScaleFactor: number | undefined; extraHTTPHeaders: ExtraHTTPHeaders | undefined; geolocation: Geolocation | undefined; + har: HAROptions | undefined; hasTouch: boolean | undefined; httpCredentials: HTTPCredentials | undefined; ignoreHTTPSErrors: boolean | undefined;